Home | History | Annotate | Download | only in net
      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