1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import android.content.BroadcastReceiver; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.IntentFilter; 11 import android.net.Proxy; 12 import android.net.Uri; 13 import android.os.Build; 14 import android.text.TextUtils; 15 import android.util.Log; 16 17 import org.chromium.base.CalledByNative; 18 import org.chromium.base.JNINamespace; 19 import org.chromium.base.NativeClassQualifiedName; 20 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 24 /** 25 * This class partners with native ProxyConfigServiceAndroid to listen for 26 * proxy change notifications from Android. 27 */ 28 @JNINamespace("net") 29 public class ProxyChangeListener { 30 private static final String TAG = "ProxyChangeListener"; 31 private static boolean sEnabled = true; 32 33 private long mNativePtr; 34 private Context mContext; 35 private ProxyReceiver mProxyReceiver; 36 private Delegate mDelegate; 37 38 private static class ProxyConfig { 39 public ProxyConfig(String host, int port, String pacUrl, String[] exclusionList) { 40 mHost = host; 41 mPort = port; 42 mPacUrl = pacUrl; 43 mExclusionList = exclusionList; 44 } 45 public final String mHost; 46 public final int mPort; 47 public final String mPacUrl; 48 public final String[] mExclusionList; 49 } 50 51 /** 52 * The delegate for ProxyChangeListener. Use for testing. 53 */ 54 public interface Delegate { 55 public void proxySettingsChanged(); 56 } 57 58 private ProxyChangeListener(Context context) { 59 mContext = context; 60 } 61 62 public static void setEnabled(boolean enabled) { 63 sEnabled = enabled; 64 } 65 66 public void setDelegateForTesting(Delegate delegate) { 67 mDelegate = delegate; 68 } 69 70 @CalledByNative 71 public static ProxyChangeListener create(Context context) { 72 return new ProxyChangeListener(context); 73 } 74 75 @CalledByNative 76 public static String getProperty(String property) { 77 return System.getProperty(property); 78 } 79 80 @CalledByNative 81 public void start(long nativePtr) { 82 assert mNativePtr == 0; 83 mNativePtr = nativePtr; 84 registerReceiver(); 85 } 86 87 @CalledByNative 88 public void stop() { 89 mNativePtr = 0; 90 unregisterReceiver(); 91 } 92 93 private class ProxyReceiver extends BroadcastReceiver { 94 @Override 95 public void onReceive(Context context, Intent intent) { 96 if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) { 97 proxySettingsChanged(extractNewProxy(intent)); 98 } 99 } 100 101 // Extract a ProxyConfig object from the supplied Intent's extra data 102 // bundle. The android.net.ProxyProperties class is not exported from 103 // the Android SDK, so we have to use reflection to get at it and invoke 104 // methods on it. If we fail, return an empty proxy config (meaning 105 // 'direct'). 106 // TODO(sgurun): once android.net.ProxyInfo is public, rewrite this. 107 private ProxyConfig extractNewProxy(Intent intent) { 108 try { 109 final String getHostName = "getHost"; 110 final String getPortName = "getPort"; 111 final String getPacFileUrl = "getPacFileUrl"; 112 final String getExclusionList = "getExclusionList"; 113 String className; 114 String proxyInfo; 115 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 116 className = "android.net.ProxyProperties"; 117 proxyInfo = "proxy"; 118 } else { 119 className = "android.net.ProxyInfo"; 120 proxyInfo = "android.intent.extra.PROXY_INFO"; 121 } 122 123 Object props = intent.getExtras().get(proxyInfo); 124 if (props == null) { 125 return null; 126 } 127 128 Class<?> cls = Class.forName(className); 129 Method getHostMethod = cls.getDeclaredMethod(getHostName); 130 Method getPortMethod = cls.getDeclaredMethod(getPortName); 131 Method getExclusionListMethod = cls.getDeclaredMethod(getExclusionList); 132 133 String host = (String) getHostMethod.invoke(props); 134 int port = (Integer) getPortMethod.invoke(props); 135 136 String[] exclusionList; 137 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 138 String s = (String) getExclusionListMethod.invoke(props); 139 exclusionList = s.split(","); 140 } else { 141 exclusionList = (String[]) getExclusionListMethod.invoke(props); 142 } 143 // TODO(xunjieli): rewrite this once the API is public. 144 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { 145 Method getPacFileUrlMethod = 146 cls.getDeclaredMethod(getPacFileUrl); 147 String pacFileUrl = (String) getPacFileUrlMethod.invoke(props); 148 if (!TextUtils.isEmpty(pacFileUrl)) { 149 return new ProxyConfig(host, port, pacFileUrl, exclusionList); 150 } 151 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { 152 Method getPacFileUrlMethod = 153 cls.getDeclaredMethod(getPacFileUrl); 154 Uri pacFileUrl = (Uri) getPacFileUrlMethod.invoke(props); 155 if (!Uri.EMPTY.equals(pacFileUrl)) { 156 return new ProxyConfig(host, port, pacFileUrl.toString(), exclusionList); 157 } 158 } 159 return new ProxyConfig(host, port, null, exclusionList); 160 } catch (ClassNotFoundException ex) { 161 Log.e(TAG, "Using no proxy configuration due to exception:" + ex); 162 return null; 163 } catch (NoSuchMethodException ex) { 164 Log.e(TAG, "Using no proxy configuration due to exception:" + ex); 165 return null; 166 } catch (IllegalAccessException ex) { 167 Log.e(TAG, "Using no proxy configuration due to exception:" + ex); 168 return null; 169 } catch (InvocationTargetException ex) { 170 Log.e(TAG, "Using no proxy configuration due to exception:" + ex); 171 return null; 172 } catch (NullPointerException ex) { 173 Log.e(TAG, "Using no proxy configuration due to exception:" + ex); 174 return null; 175 } 176 } 177 } 178 179 private void proxySettingsChanged(ProxyConfig cfg) { 180 if (!sEnabled) { 181 return; 182 } 183 if (mDelegate != null) { 184 mDelegate.proxySettingsChanged(); 185 } 186 if (mNativePtr == 0) { 187 return; 188 } 189 // Note that this code currently runs on a MESSAGE_LOOP_UI thread, but 190 // the C++ code must run the callbacks on the network thread. 191 if (cfg != null) { 192 nativeProxySettingsChangedTo(mNativePtr, cfg.mHost, cfg.mPort, cfg.mPacUrl, 193 cfg.mExclusionList); 194 } else { 195 nativeProxySettingsChanged(mNativePtr); 196 } 197 } 198 199 private void registerReceiver() { 200 if (mProxyReceiver != null) { 201 return; 202 } 203 IntentFilter filter = new IntentFilter(); 204 filter.addAction(Proxy.PROXY_CHANGE_ACTION); 205 mProxyReceiver = new ProxyReceiver(); 206 mContext.getApplicationContext().registerReceiver(mProxyReceiver, filter); 207 } 208 209 private void unregisterReceiver() { 210 if (mProxyReceiver == null) { 211 return; 212 } 213 mContext.unregisterReceiver(mProxyReceiver); 214 mProxyReceiver = null; 215 } 216 217 /** 218 * See net/proxy/proxy_config_service_android.cc 219 */ 220 @NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate") 221 private native void nativeProxySettingsChangedTo(long nativePtr, 222 String host, 223 int port, 224 String pacUrl, 225 String[] exclusionList); 226 @NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate") 227 private native void nativeProxySettingsChanged(long nativePtr); 228 } 229