diff options
| author | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-06 12:57:44 +0000 |
|---|---|---|
| committer | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-06 12:57:44 +0000 |
| commit | 45ab7abc0b8ae4d3a78637c158d98cd06b0a8f7a (patch) | |
| tree | f8fc5afc301734c421d361212329cc51ce2f7afc | |
| parent | 6d478b1e77dba59521c5eac87bad1e1f6055219d (diff) | |
| download | chromium_src-45ab7abc0b8ae4d3a78637c158d98cd06b0a8f7a.zip chromium_src-45ab7abc0b8ae4d3a78637c158d98cd06b0a8f7a.tar.gz chromium_src-45ab7abc0b8ae4d3a78637c158d98cd06b0a8f7a.tar.bz2 | |
Upstream Android proxy config service.
This change adds in an Android-specific system proxy provider called
ProxyConfigServiceAndroid. This is created as required from C++. In
turn ProxyConfigServiceAndroid creates a java object which provides
the link to Android's proxy changed broadcast, as well as the
functionality to read the Java system properties where the current
proxy settings are located.
Review URL: https://chromiumcodereview.appspot.com/10206014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@140748 0039d316-1c4b-4281-b951-d872f2087c98
| -rwxr-xr-x | base/android/jni_generator/jni_generator.py | 1 | ||||
| -rw-r--r-- | net/android/java/org/chromium/net/ProxyChangeListener.java | 91 | ||||
| -rw-r--r-- | net/android/net_jni_registrar.cc | 4 | ||||
| -rwxr-xr-x | net/android/tools/proxy_test_cases.py | 343 | ||||
| -rw-r--r-- | net/net.gyp | 7 | ||||
| -rw-r--r-- | net/proxy/proxy_config_service_android.cc | 334 | ||||
| -rw-r--r-- | net/proxy/proxy_config_service_android.h | 81 | ||||
| -rw-r--r-- | net/proxy/proxy_config_service_android_unittest.cc | 353 | ||||
| -rw-r--r-- | net/proxy/proxy_service.cc | 6 |
9 files changed, 1217 insertions, 3 deletions
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py index bf8b971..a444fdd 100755 --- a/base/android/jni_generator/jni_generator.py +++ b/base/android/jni_generator/jni_generator.py @@ -186,7 +186,6 @@ def JavaParamToJni(param): 'Lorg/chromium/media/MediaPlayerListener', 'Lorg/chromium/net/NetworkChangeNotifier', 'Lorg/chromium/net/ProxyChangeListener', - 'Lorg/chromium/net/NetworkChangeNotifier', ] if param == 'byte[][]': return '[[B' diff --git a/net/android/java/org/chromium/net/ProxyChangeListener.java b/net/android/java/org/chromium/net/ProxyChangeListener.java new file mode 100644 index 0000000..fb07a35 --- /dev/null +++ b/net/android/java/org/chromium/net/ProxyChangeListener.java @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Proxy; +import org.chromium.base.CalledByNative; + +// This class partners with native ProxyConfigServiceAndroid to listen for +// proxy change notifications from Android. +class ProxyChangeListener { + private static final String TAG = "ProxyChangeListener"; + + private int mNativePtr; + private Context mContext; + private ProxyReceiver mProxyReceiver; + + private ProxyChangeListener(Context context) { + mContext = context; + } + + @CalledByNative + static public ProxyChangeListener create(Context context) { + return new ProxyChangeListener(context); + } + + @CalledByNative + static public String getProperty(String property) { + return System.getProperty(property); + } + + @CalledByNative + public void start(int nativePtr) { + assert mNativePtr == 0; + mNativePtr = nativePtr; + registerReceiver(); + } + + @CalledByNative + public void stop() { + mNativePtr = 0; + unregisterReceiver(); + } + + private class ProxyReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) { + proxySettingsChanged(); + } + } + } + + private void proxySettingsChanged() { + if (mNativePtr == 0) + return; + + // Note that this code currently runs on a MESSAGE_LOOP_UI thread, but + // the C++ code must run the callbacks on the network thread. + nativeProxySettingsChanged(mNativePtr); + } + + private void registerReceiver() { + if (mProxyReceiver != null) { + return; + } + IntentFilter filter = new IntentFilter(); + filter.addAction(Proxy.PROXY_CHANGE_ACTION); + mProxyReceiver = new ProxyReceiver(); + mContext.getApplicationContext().registerReceiver(mProxyReceiver, filter); + } + + private void unregisterReceiver() { + if (mProxyReceiver == null) { + return; + } + mContext.unregisterReceiver(mProxyReceiver); + mProxyReceiver = null; + } + + /** + * See net/proxy/proxy_config_service_android.cc + */ + private native void nativeProxySettingsChanged( + int nativePtr /* net::ProxyConfigServiceAndroid::JNIDelegate */); +} diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc index 951b1fb..b475cac3 100644 --- a/net/android/net_jni_registrar.cc +++ b/net/android/net_jni_registrar.cc @@ -7,8 +7,9 @@ #include "base/basictypes.h" #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" -#include "net/android/network_library.h" #include "net/android/network_change_notifier_android.h" +#include "net/android/network_library.h" +#include "net/proxy/proxy_config_service_android.h" namespace net { namespace android { @@ -16,6 +17,7 @@ namespace android { static base::android::RegistrationMethod kNetRegisteredMethods[] = { { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary }, { "NetworkChangeNotifier", net::android::NetworkChangeNotifier::Register }, + { "ProxyConfigService", net::ProxyConfigServiceAndroid::Register }, }; bool RegisterJni(JNIEnv* env) { diff --git a/net/android/tools/proxy_test_cases.py b/net/android/tools/proxy_test_cases.py new file mode 100755 index 0000000..0950902 --- /dev/null +++ b/net/android/tools/proxy_test_cases.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generator script for proxy tests. + +See AndroidProxySelectorTest.java +and net/proxy/proxy_config_service_android_unittest.cc + +To generate C++, run this script without arguments. +To generate Java, run this script with -j argument. + +Note that this generator is not run as part of the build process because +we are assuming that these test cases will not change often. +""" + +import optparse + +test_cases = [ + { + "name": "NoProxy", + "description" : "Test direct mapping when no proxy defined.", + "properties" : { + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPort", + "description" : "Test http.proxyHost and http.proxyPort works.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostOnly", + "description" : "We should get the default port (80) for proxied hosts.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:80", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyPortOnly", + "description" : + "http.proxyPort only should not result in any hosts being proxied.", + "properties" : { + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT" + } + }, + { + "name": "HttpNonProxyHosts1", + "description" : "Test that HTTP non proxy hosts are mapped correctly", + "properties" : { + "http.nonProxyHosts" : "slashdot.org", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts2", + "description" : "Test that | pattern works.", + "properties" : { + "http.nonProxyHosts" : "slashdot.org|freecode.net", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + "http://freecode.net/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts3", + "description" : "Test that * pattern works.", + "properties" : { + "http.nonProxyHosts" : "*example.com", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "http://www.example.com/" : "DIRECT", + "http://slashdot.org/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpNonProxyHosts", + "description" : "Test that FTP non proxy hosts are mapped correctly", + "properties" : { + "ftp.nonProxyHosts" : "slashdot.org", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpProxyHostAndPort", + "description" : "Test ftp.proxyHost and ftp.proxyPort works.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "FtpProxyHostOnly", + "description" : "Test ftp.proxyHost and default port.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:80", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostAndPort", + "description" : "Test https.proxyHost and https.proxyPort works.", + "properties" : { + "https.proxyHost" : "httpproxy.com", + "https.proxyPort" : "8080", + }, + "mappings" : { + "https://example.com/" : "HTTPS httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostOnly", + "description" : "Test https.proxyHost and default port.", + "properties" : { + "https.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "https://example.com/" : "HTTPS httpproxy.com:443", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostIPv6", + "description" : "Test IPv6 https.proxyHost and default port.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:80", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPortIPv6", + "description" : "Test IPv6 http.proxyHost and http.proxyPort works.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:8080", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndInvalidPort", + "description" : "Test invalid http.proxyPort does not crash.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "65536", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "DefaultProxyExplictPort", + "description" : + "Default http proxy is used if a scheme-specific one is not found.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "proxyPort" : "8080", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:8080", + "https://example.com/" : "HTTPS defaultproxy.com:8080", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "DefaultProxyDefaultPort", + "description" : "Check that the default proxy port is as expected.", + "properties" : { + "proxyHost" : "defaultproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "HTTPS defaultproxy.com:443", + } + }, + { + "name": "FallbackToSocks", + "description" : "SOCKS proxy is used if scheme-specific one is not found.", + "properties" : { + "http.proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com" + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "SOCKS5 socksproxy.com:1080", + "ftp://example.com" : "SOCKS5 socksproxy.com:1080", + } + }, + { + "name": "SocksExplicitPort", + "description" : "SOCKS proxy port is used if specified", + "properties" : { + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "SOCKS5 socksproxy.com:9000", + } + }, + { + "name": "HttpProxySupercedesSocks", + "description" : "SOCKS proxy is ignored if default HTTP proxy defined.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + } + }, +] + +class GenerateCPlusPlus: + """Generate C++ test cases""" + + def Generate(self): + for test_case in test_cases: + print ("TEST_F(ProxyConfigServiceAndroidTest, %s) {" % test_case["name"]) + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print "}" + print "" + + def _GenerateDescription(self, description): + print " // %s" % description + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " AddProperty(\"%s\", \"%s\");" % (key, properties[key]) + print " ProxySettingsChanged();" + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + print " TestMapping(\"%s\", \"%s\");" % (url, mappings[url]) + + +class GenerateJava: + """Generate Java test cases""" + + def Generate(self): + for test_case in test_cases: + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + print " @SmallTest" + print " public void test%s() throws Exception {" % test_case["name"] + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print " }" + print "" + + def _GenerateDescription(self, description): + print " /**" + print " * %s" % description + print " *" + print " * @throws Exception" + print " */" + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " System.setProperty(\"%s\", \"%s\");" % ( + key, properties[key]) + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + print " checkMapping(\"%s\", \"%s\");" % (url, mappings[url]) + + +def main(): + parser = optparse.OptionParser() + parser.add_option("-j", "--java", + action="store_true", dest="java"); + (options, args) = parser.parse_args(); + if options.java: + generator = GenerateJava() + else: + generator = GenerateCPlusPlus() + generator.Generate() + +if __name__ == '__main__': + main() diff --git a/net/net.gyp b/net/net.gyp index d813c78..4066148 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -553,6 +553,8 @@ 'proxy/proxy_config.cc', 'proxy/proxy_config.h', 'proxy/proxy_config_service.h', + 'proxy/proxy_config_service_android.cc', + 'proxy/proxy_config_service_android.h', 'proxy/proxy_config_service_fixed.cc', 'proxy/proxy_config_service_fixed.h', 'proxy/proxy_config_service_linux.cc', @@ -1174,8 +1176,8 @@ 'http/mock_gssapi_library_posix.h', 'http/mock_http_cache.cc', 'http/mock_http_cache.h', - 'http/mock_sspi_library_win.h', 'http/mock_sspi_library_win.cc', + 'http/mock_sspi_library_win.h', 'http/url_security_manager_unittest.cc', 'proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc', 'proxy/dhcp_proxy_script_fetcher_factory_unittest.cc', @@ -1183,6 +1185,7 @@ 'proxy/multi_threaded_proxy_resolver_unittest.cc', 'proxy/network_delegate_error_observer_unittest.cc', 'proxy/proxy_bypass_rules_unittest.cc', + 'proxy/proxy_config_service_android_unittest.cc', 'proxy/proxy_config_service_linux_unittest.cc', 'proxy/proxy_config_service_win_unittest.cc', 'proxy/proxy_config_unittest.cc', @@ -1830,10 +1833,12 @@ 'java_sources': [ 'android/java/org/chromium/net/AndroidNetworkLibrary.java', 'android/java/org/chromium/net/NetworkChangeNotifier.java', + 'android/java/org/chromium/net/ProxyChangeListener.java', ], 'jni_headers': [ '<(SHARED_INTERMEDIATE_DIR)/net/jni/android_network_library_jni.h', '<(SHARED_INTERMEDIATE_DIR)/net/jni/network_change_notifier_jni.h', + '<(SHARED_INTERMEDIATE_DIR)/net/jni/proxy_change_listener_jni.h', ], }, 'includes': [ '../build/jni_generator.gypi' ], diff --git a/net/proxy/proxy_config_service_android.cc b/net/proxy/proxy_config_service_android.cc new file mode 100644 index 0000000..0bb75b8 --- /dev/null +++ b/net/proxy/proxy_config_service_android.cc @@ -0,0 +1,334 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/proxy/proxy_config_service_android.h" + +#include <sys/system_properties.h> + +#include "base/android/jni_string.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/sequenced_task_runner.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "googleurl/src/url_parse.h" +#include "jni/proxy_change_listener_jni.h" +#include "net/base/host_port_pair.h" +#include "net/proxy/proxy_config.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertJavaStringToUTF8; +using base::android::CheckException; +using base::android::ClearException; +using base::android::GetMethodID; +using base::android::ScopedJavaGlobalRef; + +namespace net { + +namespace { + +typedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback; + +// Returns whether the provided string was successfully converted to a port. +bool ConvertStringToPort(const std::string& port, int* output) { + url_parse::Component component(0, port.size()); + int result = url_parse::ParsePort(port.c_str(), component); + if (result == url_parse::PORT_INVALID || + result == url_parse::PORT_UNSPECIFIED) + return false; + *output = result; + return true; +} + +ProxyServer ConstructProxyServer(ProxyServer::Scheme scheme, + const std::string& proxy_host, + const std::string& proxy_port) { + DCHECK(!proxy_host.empty()); + int port_as_int = 0; + if (proxy_port.empty()) + port_as_int = ProxyServer::GetDefaultPortForScheme(scheme); + else if (!ConvertStringToPort(proxy_port, &port_as_int)) + return ProxyServer(); + DCHECK(port_as_int > 0); + return ProxyServer( + scheme, + HostPortPair(proxy_host, static_cast<uint16>(port_as_int))); +} + +ProxyServer LookupProxy(const std::string& prefix, + const GetPropertyCallback& get_property, + ProxyServer::Scheme scheme) { + DCHECK(!prefix.empty()); + std::string proxy_host = get_property.Run(prefix + ".proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run(prefix + ".proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + // Fall back to default proxy, if any. + proxy_host = get_property.Run("proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + return ProxyServer(); +} + +ProxyServer LookupSocksProxy(const GetPropertyCallback& get_property) { + std::string proxy_host = get_property.Run("socksProxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("socksProxyPort"); + return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host, + proxy_port); + } + return ProxyServer(); +} + +void AddBypassRules(const std::string& scheme, + const GetPropertyCallback& get_property, + ProxyBypassRules* bypass_rules) { + // The format of a hostname pattern is a list of hostnames that are separated + // by | and that use * as a wildcard. For example, setting the + // http.nonProxyHosts property to *.android.com|*.kernel.org will cause + // requests to http://developer.android.com to be made without a proxy. + std::string non_proxy_hosts = + get_property.Run(scheme + ".nonProxyHosts"); + if (non_proxy_hosts.empty()) + return; + StringTokenizer tokenizer(non_proxy_hosts, "|"); + while (tokenizer.GetNext()) { + std::string token = tokenizer.token(); + std::string pattern; + TrimWhitespaceASCII(token, TRIM_ALL, &pattern); + if (pattern.empty()) + continue; + // '?' is not one of the specified pattern characters above. + DCHECK_EQ(std::string::npos, pattern.find('?')); + bypass_rules->AddRuleForHostname(scheme, pattern, -1); + } +} + +// Returns true if a valid proxy was found. +bool GetProxyRules(const GetPropertyCallback& get_property, + ProxyConfig::ProxyRules* rules) { + // See libcore/luni/src/main/java/java/net/ProxySelectorImpl.java for the + // equivalent Android implementation. + rules->type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME; + rules->proxy_for_http = LookupProxy("http", get_property, + ProxyServer::SCHEME_HTTP); + rules->proxy_for_https = LookupProxy("https", get_property, + ProxyServer::SCHEME_HTTPS); + rules->proxy_for_ftp = LookupProxy("ftp", get_property, + ProxyServer::SCHEME_HTTP); + rules->fallback_proxy = LookupSocksProxy(get_property); + rules->bypass_rules.Clear(); + AddBypassRules("ftp", get_property, &rules->bypass_rules); + AddBypassRules("http", get_property, &rules->bypass_rules); + AddBypassRules("https", get_property, &rules->bypass_rules); + return rules->proxy_for_http.is_valid() || + rules->proxy_for_https.is_valid() || + rules->proxy_for_ftp.is_valid() || + rules->fallback_proxy.is_valid(); +}; + +void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property, + ProxyConfig* config) { + if (!GetProxyRules(get_property, &config->proxy_rules())) + *config = ProxyConfig::CreateDirect(); +} + +std::string GetJavaProperty(const std::string& property) { + // Use Java System.getProperty to get configuration information. + // TODO(pliard): Conversion to/from UTF8 ok here? + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, property); + ScopedJavaLocalRef<jstring> result = + Java_ProxyChangeListener_getProperty(env, str.obj()); + return result.is_null() ? + std::string() : ConvertJavaStringToUTF8(env, result.obj()); +} + +} // namespace + +class ProxyConfigServiceAndroid::Delegate + : public base::RefCountedThreadSafe<Delegate> { + public: + Delegate(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + const GetPropertyCallback& get_property_callback) + : ALLOW_THIS_IN_INITIALIZER_LIST(jni_delegate_(this)), + network_task_runner_(network_task_runner), + jni_task_runner_(jni_task_runner), + get_property_callback_(get_property_callback) { + } + + void SetupJNI() { + DCHECK(OnJNIThread()); + JNIEnv* env = AttachCurrentThread(); + if (java_proxy_change_listener_.is_null()) { + java_proxy_change_listener_.Reset( + Java_ProxyChangeListener_create( + env, base::android::GetApplicationContext())); + CHECK(!java_proxy_change_listener_.is_null()); + } + Java_ProxyChangeListener_start( + env, + java_proxy_change_listener_.obj(), + reinterpret_cast<jint>(&jni_delegate_)); + } + + void FetchInitialConfig() { + DCHECK(OnJNIThread()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Delegate::SetNewConfigOnNetworkThread, this, proxy_config)); + } + + void Shutdown() { + if (OnJNIThread()) { + ShutdownOnJNIThread(); + } else { + jni_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Delegate::ShutdownOnJNIThread, this)); + } + } + + // Called only on the network thread. + void AddObserver(Observer* observer) { + DCHECK(OnNetworkThread()); + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + DCHECK(OnNetworkThread()); + observers_.RemoveObserver(observer); + } + + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) { + DCHECK(OnNetworkThread()); + if (!config) + return ProxyConfigService::CONFIG_UNSET; + *config = proxy_config_; + return ProxyConfigService::CONFIG_VALID; + } + + // Called on the JNI thread. + void ProxySettingsChanged() { + DCHECK(OnJNIThread()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, + base::Bind( + &Delegate::SetNewConfigOnNetworkThread, this, proxy_config)); + } + + private: + friend class base::RefCountedThreadSafe<Delegate>; + + class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate { + public: + explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {} + + // ProxyConfigServiceAndroid::JNIDelegate overrides. + virtual void ProxySettingsChanged(JNIEnv*, jobject) OVERRIDE { + delegate_->ProxySettingsChanged(); + } + + private: + Delegate* const delegate_; + }; + + virtual ~Delegate() {} + + void ShutdownOnJNIThread() { + if (java_proxy_change_listener_.is_null()) + return; + JNIEnv* env = AttachCurrentThread(); + Java_ProxyChangeListener_stop(env, java_proxy_change_listener_.obj()); + } + + // Called on the network thread. + void SetNewConfigOnNetworkThread(const ProxyConfig& proxy_config) { + DCHECK(OnNetworkThread()); + proxy_config_ = proxy_config; + FOR_EACH_OBSERVER(Observer, observers_, + OnProxyConfigChanged(proxy_config, + ProxyConfigService::CONFIG_VALID)); + } + + bool OnJNIThread() const { + return jni_task_runner_->RunsTasksOnCurrentThread(); + } + + bool OnNetworkThread() const { + return network_task_runner_->RunsTasksOnCurrentThread(); + } + + ScopedJavaGlobalRef<jobject> java_proxy_change_listener_; + + JNIDelegateImpl jni_delegate_; + ObserverList<Observer> observers_; + scoped_refptr<base::SequencedTaskRunner> network_task_runner_; + scoped_refptr<base::SequencedTaskRunner> jni_task_runner_; + GetPropertyCallback get_property_callback_; + ProxyConfig proxy_config_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, base::Bind(&GetJavaProperty))) { + delegate_->SetupJNI(); + delegate_->FetchInitialConfig(); +} + +ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() { + delegate_->Shutdown(); +} + +// static +bool ProxyConfigServiceAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void ProxyConfigServiceAndroid::AddObserver(Observer* observer) { + delegate_->AddObserver(observer); +} + +void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) { + delegate_->RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability +ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) { + return delegate_->GetLatestProxyConfig(config); +} + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + GetPropertyCallback get_property_callback) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, get_property_callback)) { + delegate_->FetchInitialConfig(); +} + +void ProxyConfigServiceAndroid::ProxySettingsChanged() { + delegate_->ProxySettingsChanged(); +} + +} // namespace net diff --git a/net/proxy/proxy_config_service_android.h b/net/proxy/proxy_config_service_android.h new file mode 100644 index 0000000..30ebd11 --- /dev/null +++ b/net/proxy/proxy_config_service_android.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ +#define NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ +#pragma once + +#include <string> + +#include "base/android/jni_android.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy/proxy_config_service.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace net { + +class ProxyConfig; + +class NET_EXPORT ProxyConfigServiceAndroid : public ProxyConfigService { + public: + // Callback that returns the value of the property identified by the provided + // key. If it was not found, an empty string is returned. Note that this + // interface does not let you distinguish an empty property from a + // non-existing property. This callback is invoked on the JNI thread. + typedef base::Callback<std::string (const std::string& property)> + GetPropertyCallback; + + // Separate class whose instance is owned by the Delegate class implemented in + // the .cc file. + class JNIDelegate { + public: + virtual ~JNIDelegate() {} + + // Called from Java (on JNI thread) to signal that the proxy settings have + // changed. + virtual void ProxySettingsChanged(JNIEnv*, jobject) = 0; + }; + + ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner); + + virtual ~ProxyConfigServiceAndroid(); + + // Register JNI bindings. + static bool Register(JNIEnv* env); + + // ProxyConfigService: + // Called only on the network thread. + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE; + + private: + friend class ProxyConfigServiceAndroidTestBase; + class Delegate; + + + // For tests. + ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + GetPropertyCallback get_property_callback); + + // For tests. + void ProxySettingsChanged(); + + scoped_refptr<Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceAndroid); +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ diff --git a/net/proxy/proxy_config_service_android_unittest.cc b/net/proxy/proxy_config_service_android_unittest.cc new file mode 100644 index 0000000..84449f0 --- /dev/null +++ b/net/proxy/proxy_config_service_android_unittest.cc @@ -0,0 +1,353 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> +#include <string> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_config_service_android.h" +#include "net/proxy/proxy_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class TestObserver : public ProxyConfigService::Observer { + public: + TestObserver() : availability_(ProxyConfigService::CONFIG_UNSET) {} + + // ProxyConfigService::Observer: + virtual void OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) OVERRIDE { + config_ = config; + availability_ = availability; + } + + ProxyConfigService::ConfigAvailability availability() const { + return availability_; + } + + const ProxyConfig& config() const { + return config_; + } + + private: + ProxyConfig config_; + ProxyConfigService::ConfigAvailability availability_; +}; + +} // namespace + +typedef std::map<std::string, std::string> StringMap; + +class ProxyConfigServiceAndroidTestBase : public testing::Test { + protected: + // Note that the current thread's message loop is initialized by the test + // suite (see net/base/net_test_suite.cc). + ProxyConfigServiceAndroidTestBase(const StringMap& initial_configuration) + : configuration_(initial_configuration), + message_loop_(MessageLoop::current()), + service_( + message_loop_->message_loop_proxy(), + message_loop_->message_loop_proxy(), + base::Bind(&ProxyConfigServiceAndroidTestBase::GetProperty, + base::Unretained(this))) {} + + virtual ~ProxyConfigServiceAndroidTestBase() {} + + // testing::Test: + virtual void SetUp() OVERRIDE { + message_loop_->RunAllPending(); + service_.AddObserver(&observer_); + } + + virtual void TearDown() OVERRIDE { + service_.RemoveObserver(&observer_); + } + + void ClearConfiguration() { + configuration_.clear(); + } + + void AddProperty(const std::string& key, const std::string& value) { + configuration_[key] = value; + } + + std::string GetProperty(const std::string& key) { + StringMap::const_iterator it = configuration_.find(key); + if (it == configuration_.end()) + return std::string(); + return it->second; + } + + void ProxySettingsChanged() { + service_.ProxySettingsChanged(); + message_loop_->RunAllPending(); + } + + void TestMapping(const std::string& url, const std::string& expected) { + ProxyConfigService::ConfigAvailability availability; + ProxyConfig proxy_config; + availability = service_.GetLatestProxyConfig(&proxy_config); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, availability); + ProxyInfo proxy_info; + proxy_config.proxy_rules().Apply(GURL(url), &proxy_info); + EXPECT_EQ(expected, proxy_info.ToPacString()); + } + + StringMap configuration_; + TestObserver observer_; + MessageLoop* const message_loop_; + ProxyConfigServiceAndroid service_; +}; + +class ProxyConfigServiceAndroidTest : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidTest() + : ProxyConfigServiceAndroidTestBase(StringMap()) {} +}; + +class ProxyConfigServiceAndroidWithInitialConfigTest + : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidWithInitialConfigTest() + : ProxyConfigServiceAndroidTestBase(MakeInitialConfiguration()) {} + + private: + StringMap MakeInitialConfiguration() { + StringMap initial_configuration; + initial_configuration["http.proxyHost"] = "httpproxy.com"; + initial_configuration["http.proxyPort"] = "8080"; + return initial_configuration; + } +}; + +TEST_F(ProxyConfigServiceAndroidTest, TestChangePropertiesNotification) { + // Set up a non-empty configuration + AddProperty("http.proxyHost", "localhost"); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_FALSE(observer_.config().proxy_rules().empty()); + + // Set up an empty configuration + ClearConfiguration(); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_TRUE(observer_.config().proxy_rules().empty()); +} + +TEST_F(ProxyConfigServiceAndroidWithInitialConfigTest, TestInitialConfig) { + // Make sure that the initial config is set. + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + + // Override the initial configuration. + ClearConfiguration(); + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); +} + +// !! The following test cases are automatically generated from +// !! net/android/tools/proxy_test_cases.py. +// !! Please edit that file instead of editing the test cases below and +// !! update also the corresponding Java unit tests in +// !! AndroidProxySelectorTest.java + +TEST_F(ProxyConfigServiceAndroidTest, NoProxy) { + // Test direct mapping when no proxy defined. + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPort) { + // Test http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostOnly) { + // We should get the default port (80) for proxied hosts. + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyPortOnly) { + // http.proxyPort only should not result in any hosts being proxied. + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts1) { + // Test that HTTP non proxy hosts are mapped correctly + AddProperty("http.nonProxyHosts", "slashdot.org"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts2) { + // Test that | pattern works. + AddProperty("http.nonProxyHosts", "slashdot.org|freecode.net"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://freecode.net/", "DIRECT"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts3) { + // Test that * pattern works. + AddProperty("http.nonProxyHosts", "*example.com"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("http://slashdot.org/", "PROXY httpproxy.com:8080"); + TestMapping("http://www.example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpNonProxyHosts) { + // Test that FTP non proxy hosts are mapped correctly + AddProperty("ftp.nonProxyHosts", "slashdot.org"); + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostAndPort) { + // Test ftp.proxyHost and ftp.proxyPort works. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostOnly) { + // Test ftp.proxyHost and default port. + AddProperty("ftp.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:80"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostAndPort) { + // Test https.proxyHost and https.proxyPort works. + AddProperty("https.proxyHost", "httpproxy.com"); + AddProperty("https.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "HTTPS httpproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostOnly) { + // Test https.proxyHost and default port. + AddProperty("https.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "HTTPS httpproxy.com:443"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostIPv6) { + // Test IPv6 https.proxyHost and default port. + AddProperty("http.proxyHost", "a:b:c::d:1"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:80"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPortIPv6) { + // Test IPv6 http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndInvalidPort) { + // Test invalid http.proxyPort does not crash. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "65536"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyExplictPort) { + // Default http proxy is used if a scheme-specific one is not found. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:8080"); + TestMapping("https://example.com/", "HTTPS defaultproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyDefaultPort) { + // Check that the default proxy port is as expected. + AddProperty("proxyHost", "defaultproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "HTTPS defaultproxy.com:443"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FallbackToSocks) { + // SOCKS proxy is used if scheme-specific one is not found. + AddProperty("http.proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "SOCKS5 socksproxy.com:1080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, SocksExplicitPort) { + // SOCKS proxy port is used if specified + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "SOCKS5 socksproxy.com:9000"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxySupercedesSocks) { + // SOCKS proxy is ignored if default HTTP proxy defined. + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); +} + +} // namespace net diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index 3cd82a5..35ccfbd 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -41,6 +41,8 @@ #include "net/proxy/proxy_resolver_mac.h" #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) #include "net/proxy/proxy_config_service_linux.h" +#elif defined(OS_ANDROID) +#include "net/proxy/proxy_config_service_android.h" #endif using base::TimeDelta; @@ -1453,6 +1455,10 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService( static_cast<MessageLoopForIO*>(file_loop)); return linux_config_service; +#elif defined(OS_ANDROID) + return new ProxyConfigServiceAndroid( + io_thread_task_runner, + MessageLoopForUI::current()->message_loop_proxy()); #else LOG(WARNING) << "Failed to choose a system proxy settings fetcher " "for this platform."; |
