Created
January 14, 2026 19:54
-
-
Save Kr328/914397abee1b811cb699e25e88216c84 to your computer and use it in GitHub Desktop.
A simple helper to get global proxy in Android. (Deprecated: use Settings.Global instead to get better performance and compatibility)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.kisara.kisara; | |
| import android.annotation.SuppressLint; | |
| import android.content.Context; | |
| import android.net.ConnectivityManager; | |
| import android.net.ProxyInfo; | |
| import android.os.Build; | |
| import android.os.IBinder; | |
| import android.util.Log; | |
| import androidx.annotation.NonNull; | |
| import androidx.annotation.Nullable; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.util.Arrays; | |
| import java.util.stream.Collectors; | |
| import dalvik.system.PathClassLoader; | |
| final class ProxyHelper { | |
| private static final String TAG = "ProxyHelper"; | |
| private static boolean getGlobalProxyAvailable = true; | |
| private static ReflectFunction<ConnectivityManager, ProxyInfo> getGlobalProxyFunc = null; | |
| @SuppressLint("PrivateApi") | |
| private static ReflectFunction<ConnectivityManager, ProxyInfo> findGetGlobalProxyFunction() throws ReflectiveOperationException { | |
| if (Build.VERSION.SDK_INT >= 28) { | |
| long beginAt = System.nanoTime(); | |
| final IBinder proxy = (IBinder) HiddenApiClassLoader | |
| .load("android.os.ServiceManager") | |
| .getMethod("getService", String.class) | |
| .invoke(null, Context.CONNECTIVITY_SERVICE); | |
| final Object stub = HiddenApiClassLoader | |
| .load("android.net.IConnectivityManager$Stub") | |
| .getMethod("asInterface", android.os.IBinder.class) | |
| .invoke(null, proxy); | |
| if (stub == null) { | |
| throw new IllegalStateException("failed to create connectivity stub"); | |
| } | |
| final Method method = stub.getClass().getMethod("getGlobalProxy"); | |
| final long elapsed = System.nanoTime() - beginAt; | |
| Log.d(TAG, "getGlobalProxy: " + method + " in " + elapsed + " ns"); | |
| return (c) -> (ProxyInfo) method.invoke(stub); | |
| } else { | |
| @SuppressWarnings("JavaReflectionMemberAccess") final Method method = ConnectivityManager.class | |
| .getDeclaredMethod("getGlobalProxy"); | |
| method.setAccessible(true); | |
| Log.d(TAG, "getGlobalProxy: " + method); | |
| return (c) -> (ProxyInfo) method.invoke(c); | |
| } | |
| } | |
| @SuppressLint("PrivateApi") | |
| public static @Nullable ProxyInfo getGlobalProxy(@NonNull ConnectivityManager connectivity) { | |
| if (!getGlobalProxyAvailable) { | |
| return null; | |
| } | |
| ReflectFunction<ConnectivityManager, ProxyInfo> get = getGlobalProxyFunc; | |
| if (get == null) { | |
| try { | |
| get = getGlobalProxyFunc = findGetGlobalProxyFunction(); | |
| } catch (Throwable e) { | |
| Log.w(TAG, "no available method to get global proxy", e); | |
| getGlobalProxyAvailable = false; | |
| return null; | |
| } | |
| } | |
| try { | |
| return get.apply(connectivity); | |
| } catch (InvocationTargetException e) { | |
| final Throwable cause = e.getTargetException(); | |
| if (cause instanceof RuntimeException) { | |
| throw (RuntimeException) cause; | |
| } else { | |
| throw new RuntimeException(cause); | |
| } | |
| } catch (ReflectiveOperationException e) { | |
| Log.w(TAG, "failed to invoke get global proxy", e); | |
| getGlobalProxyAvailable = false; | |
| return null; | |
| } catch (Throwable e) { | |
| Log.w(TAG, "unexpected error", e); | |
| getGlobalProxyAvailable = false; | |
| return null; | |
| } | |
| } | |
| private interface ReflectFunction<T, R> { | |
| R apply(T t) throws ReflectiveOperationException; | |
| } | |
| private static class HiddenApiClassLoader extends PathClassLoader { | |
| public HiddenApiClassLoader(boolean full) { | |
| super(findConnectivityClassPath(full), ClassLoader.getSystemClassLoader()); | |
| } | |
| private static String findConnectivityClassPath(final boolean framework) { | |
| final String bootClassPath = System.getProperty("java.boot.class.path", ""); | |
| assert bootClassPath != null; | |
| if (framework) { | |
| String frameworkClassPath = Arrays.stream(bootClassPath.split(":")) | |
| .filter(s -> s.contains("framework")) | |
| .collect(Collectors.joining(":")); | |
| Log.v(TAG, "framework class path: " + frameworkClassPath); | |
| return frameworkClassPath; | |
| } else { | |
| String filteredClassPath = Arrays.stream(bootClassPath.split(":")) | |
| .filter(s -> s.contains("connectivity")) | |
| .collect(Collectors.joining(":")); | |
| Log.v(TAG, "filtered class path: " + filteredClassPath); | |
| return filteredClassPath; | |
| } | |
| } | |
| public static Class<?> load(final String name) throws ClassNotFoundException { | |
| try { | |
| return Filtered.loader.loadClass(name); | |
| } catch (ClassNotFoundException e) { | |
| return Full.loader.loadClass(name); | |
| } | |
| } | |
| @Override | |
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |
| if (name.startsWith("android.net.IConnectivityManager")) { | |
| return findClass(name); | |
| } | |
| return super.loadClass(name, resolve); | |
| } | |
| private static class Full { | |
| private static final ClassLoader loader = new HiddenApiClassLoader(true); | |
| } | |
| private static class Filtered { | |
| private static final ClassLoader loader = new HiddenApiClassLoader(false); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment