Global Proxy changes to proxy class.
Change-Id: Ib2da33670b1da33c0b35b60f4fcbd0bc084e616a
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 22c30a5..188a4aa 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -16,32 +16,168 @@
package android.net;
-import org.apache.http.HttpHost;
-
import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.util.Log;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import junit.framework.Assert;
+import org.apache.http.HttpHost;
+
/**
* A convenience class for accessing the user and default proxy
* settings.
*/
-final public class Proxy {
+public final class Proxy {
// Set to true to enable extra debugging.
- static final private boolean DEBUG = false;
+ private static final boolean DEBUG = false;
- static final public String PROXY_CHANGE_ACTION =
+ public static final String PROXY_CHANGE_ACTION =
"android.intent.action.PROXY_CHANGE";
+ private static ReadWriteLock sProxyInfoLock = new ReentrantReadWriteLock();
+
+ private static SettingsObserver sGlobalProxyChangedObserver = null;
+
+ private static ProxySpec sGlobalProxySpec = null;
+
+ // Hostname / IP REGEX validation
+ // Matches blank input, ips, and domain names
+ private static final String NAME_IP_REGEX =
+ "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
+
+ private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
+
+ private static final Pattern HOSTNAME_PATTERN;
+
+ private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX
+ + ")+(,(.?" + NAME_IP_REGEX + "))*$";
+
+ private static final Pattern EXCLLIST_PATTERN;
+
+ static {
+ HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+ EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
+ }
+
+ private static class ProxySpec {
+ String[] exclusionList = null;
+ InetSocketAddress proxyAddress = null;
+ public ProxySpec() { };
+ }
+
+ private static boolean isURLInExclusionListReadLocked(String url, String[] exclusionList) {
+ if (exclusionList == null) {
+ return false;
+ }
+ Uri u = Uri.parse(url);
+ String urlDomain = u.getHost();
+ // If the domain is defined as ".android.com" or "android.com", we wish to match
+ // http://android.com as well as http://xxx.android.com , but not
+ // http://myandroid.com . This code works out the logic.
+ for (String excludedDomain : exclusionList) {
+ String dotDomain = "." + excludedDomain;
+ if (urlDomain.equals(excludedDomain)) {
+ return true;
+ }
+ if (urlDomain.endsWith(dotDomain)) {
+ return true;
+ }
+ }
+ // No match
+ return false;
+ }
+
+ private static String parseHost(String proxySpec) {
+ int i = proxySpec.indexOf(':');
+ if (i == -1) {
+ if (DEBUG) {
+ Assert.assertTrue(proxySpec.length() == 0);
+ }
+ return null;
+ }
+ return proxySpec.substring(0, i);
+ }
+
+ private static int parsePort(String proxySpec) {
+ int i = proxySpec.indexOf(':');
+ if (i == -1) {
+ if (DEBUG) {
+ Assert.assertTrue(proxySpec.length() == 0);
+ }
+ return -1;
+ }
+ if (DEBUG) {
+ Assert.assertTrue(i < proxySpec.length());
+ }
+ return Integer.parseInt(proxySpec.substring(i+1));
+ }
+
+ /**
+ * Return the proxy object to be used for the URL given as parameter.
+ * @param ctx A Context used to get the settings for the proxy host.
+ * @param url A URL to be accessed. Used to evaluate exclusion list.
+ * @return Proxy (java.net) object containing the host name. If the
+ * user did not set a hostname it returns the default host.
+ * A null value means that no host is to be used.
+ * {@hide}
+ */
+ public static final java.net.Proxy getProxy(Context ctx, String url) {
+ sProxyInfoLock.readLock().lock();
+ try {
+ if (sGlobalProxyChangedObserver == null) {
+ registerContentObserversReadLocked(ctx);
+ parseGlobalProxyInfoReadLocked(ctx);
+ }
+ if (sGlobalProxySpec != null) {
+ // Proxy defined - Apply exclusion rules
+ if (isURLInExclusionListReadLocked(url, sGlobalProxySpec.exclusionList)) {
+ // Return no proxy
+ return java.net.Proxy.NO_PROXY;
+ }
+ java.net.Proxy retProxy =
+ new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress);
+ sProxyInfoLock.readLock().unlock();
+ if (isLocalHost(url)) {
+ return java.net.Proxy.NO_PROXY;
+ }
+ sProxyInfoLock.readLock().lock();
+ return retProxy;
+ } else {
+ // If network is WiFi, return no proxy.
+ // Otherwise, return the Mobile Operator proxy.
+ if (!isNetworkWifi(ctx)) {
+ java.net.Proxy retProxy = getDefaultProxy(url);
+ sProxyInfoLock.readLock().unlock();
+ if (isLocalHost(url)) {
+ return java.net.Proxy.NO_PROXY;
+ }
+ sProxyInfoLock.readLock().lock();
+ return retProxy;
+ } else {
+ return java.net.Proxy.NO_PROXY;
+ }
+ }
+ } finally {
+ sProxyInfoLock.readLock().unlock();
+ }
+ }
+
+ // TODO: deprecate this function
/**
* Return the proxy host set by the user.
* @param ctx A Context used to get the settings for the proxy host.
@@ -49,58 +185,53 @@
* name it returns the default host. A null value means that no
* host is to be used.
*/
- static final public String getHost(Context ctx) {
- ContentResolver contentResolver = ctx.getContentResolver();
- Assert.assertNotNull(contentResolver);
- String host = Settings.Secure.getString(
- contentResolver,
- Settings.Secure.HTTP_PROXY);
- if (host != null) {
- int i = host.indexOf(':');
- if (i == -1) {
- if (DEBUG) {
- Assert.assertTrue(host.length() == 0);
- }
- return null;
+ public static final String getHost(Context ctx) {
+ sProxyInfoLock.readLock().lock();
+ try {
+ if (sGlobalProxyChangedObserver == null) {
+ registerContentObserversReadLocked(ctx);
+ parseGlobalProxyInfoReadLocked(ctx);
}
- return host.substring(0, i);
+ if (sGlobalProxySpec != null) {
+ InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+ return sa.getHostName();
+ }
+ return getDefaultHost();
+ } finally {
+ sProxyInfoLock.readLock().unlock();
}
- return getDefaultHost();
}
+ // TODO: deprecate this function
/**
* Return the proxy port set by the user.
* @param ctx A Context used to get the settings for the proxy port.
* @return The port number to use or -1 if no proxy is to be used.
*/
- static final public int getPort(Context ctx) {
- ContentResolver contentResolver = ctx.getContentResolver();
- Assert.assertNotNull(contentResolver);
- String host = Settings.Secure.getString(
- contentResolver,
- Settings.Secure.HTTP_PROXY);
- if (host != null) {
- int i = host.indexOf(':');
- if (i == -1) {
- if (DEBUG) {
- Assert.assertTrue(host.length() == 0);
- }
- return -1;
+ public static final int getPort(Context ctx) {
+ sProxyInfoLock.readLock().lock();
+ try {
+ if (sGlobalProxyChangedObserver == null) {
+ registerContentObserversReadLocked(ctx);
+ parseGlobalProxyInfoReadLocked(ctx);
}
- if (DEBUG) {
- Assert.assertTrue(i < host.length());
+ if (sGlobalProxySpec != null) {
+ InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+ return sa.getPort();
}
- return Integer.parseInt(host.substring(i+1));
+ return getDefaultPort();
+ } finally {
+ sProxyInfoLock.readLock().unlock();
}
- return getDefaultPort();
}
+ // TODO: deprecate this function
/**
* Return the default proxy host specified by the carrier.
* @return String containing the host name or null if there is no proxy for
* this carrier.
*/
- static final public String getDefaultHost() {
+ public static final String getDefaultHost() {
String host = SystemProperties.get("net.gprs.http-proxy");
if (host != null) {
Uri u = Uri.parse(host);
@@ -111,12 +242,13 @@
}
}
+ // TODO: deprecate this function
/**
* Return the default proxy port specified by the carrier.
* @return The port number to be used with the proxy host or -1 if there is
* no proxy for this carrier.
*/
- static final public int getDefaultPort() {
+ public static final int getDefaultPort() {
String host = SystemProperties.get("net.gprs.http-proxy");
if (host != null) {
Uri u = Uri.parse(host);
@@ -126,6 +258,20 @@
}
}
+ private static final java.net.Proxy getDefaultProxy(String url) {
+ // TODO: This will go away when information is collected from ConnectivityManager...
+ // There are broadcast of network proxies, so they are parse manually.
+ String host = SystemProperties.get("net.gprs.http-proxy");
+ if (host != null) {
+ Uri u = Uri.parse(host);
+ return new java.net.Proxy(java.net.Proxy.Type.HTTP,
+ new InetSocketAddress(u.getHost(), u.getPort()));
+ } else {
+ return java.net.Proxy.NO_PROXY;
+ }
+ }
+
+ // TODO: remove this function / deprecate
/**
* Returns the preferred proxy to be used by clients. This is a wrapper
* around {@link android.net.Proxy#getHost()}. Currently no proxy will
@@ -138,26 +284,23 @@
* android.permission.ACCESS_NETWORK_STATE
* @return The preferred proxy to be used by clients, or null if there
* is no proxy.
- *
* {@hide}
*/
- static final public HttpHost getPreferredHttpHost(Context context,
+ public static final HttpHost getPreferredHttpHost(Context context,
String url) {
- if (!isLocalHost(url) && !isNetworkWifi(context)) {
- final String proxyHost = Proxy.getHost(context);
- if (proxyHost != null) {
- return new HttpHost(proxyHost, Proxy.getPort(context), "http");
- }
+ java.net.Proxy prefProxy = getProxy(context, url);
+ if (prefProxy.equals(java.net.Proxy.NO_PROXY)) {
+ return null;
+ } else {
+ InetSocketAddress sa = (InetSocketAddress)prefProxy.address();
+ return new HttpHost(sa.getHostName(), sa.getPort(), "http");
}
-
- return null;
}
- static final private boolean isLocalHost(String url) {
+ private static final boolean isLocalHost(String url) {
if (url == null) {
return false;
}
-
try {
final URI uri = URI.create(url);
final String host = uri.getHost();
@@ -174,15 +317,13 @@
} catch (IllegalArgumentException iex) {
// Ignore (URI.create)
}
-
return false;
}
- static final private boolean isNetworkWifi(Context context) {
+ private static final boolean isNetworkWifi(Context context) {
if (context == null) {
return false;
}
-
final ConnectivityManager connectivity = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
@@ -192,7 +333,120 @@
return true;
}
}
-
return false;
}
-};
+
+ private static class SettingsObserver extends ContentObserver {
+
+ private Context mContext;
+
+ SettingsObserver(Context ctx) {
+ super(new Handler());
+ mContext = ctx;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ sProxyInfoLock.readLock().lock();
+ parseGlobalProxyInfoReadLocked(mContext);
+ sProxyInfoLock.readLock().unlock();
+ }
+ }
+
+ private static final void registerContentObserversReadLocked(Context ctx) {
+ Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY);
+ Uri uriGlobalExclList =
+ Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);
+
+ // No lock upgrading (from read to write) allowed
+ sProxyInfoLock.readLock().unlock();
+ sProxyInfoLock.writeLock().lock();
+ sGlobalProxyChangedObserver = new SettingsObserver(ctx);
+ // Downgrading locks (from write to read) is allowed
+ sProxyInfoLock.readLock().lock();
+ sProxyInfoLock.writeLock().unlock();
+ ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false,
+ sGlobalProxyChangedObserver);
+ ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false,
+ sGlobalProxyChangedObserver);
+ }
+
+ private static final void parseGlobalProxyInfoReadLocked(Context ctx) {
+ ContentResolver contentResolver = ctx.getContentResolver();
+ String proxyHost = Settings.Secure.getString(
+ contentResolver,
+ Settings.Secure.HTTP_PROXY);
+ if (proxyHost == null) {
+ return;
+ }
+ String exclusionListSpec = Settings.Secure.getString(
+ contentResolver,
+ Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);
+ String host = parseHost(proxyHost);
+ int port = parsePort(proxyHost);
+ if (proxyHost != null) {
+ sGlobalProxySpec = new ProxySpec();
+ sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port);
+ if (exclusionListSpec != null) {
+ String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(",");
+ String[] processedEntries = new String[exclusionListEntries.length];
+ for (int i = 0; i < exclusionListEntries.length; i++) {
+ String entry = exclusionListEntries[i].trim();
+ if (entry.startsWith(".")) {
+ entry = entry.substring(1);
+ }
+ processedEntries[i] = entry;
+ }
+ sProxyInfoLock.readLock().unlock();
+ sProxyInfoLock.writeLock().lock();
+ sGlobalProxySpec.exclusionList = processedEntries;
+ } else {
+ sProxyInfoLock.readLock().unlock();
+ sProxyInfoLock.writeLock().lock();
+ sGlobalProxySpec.exclusionList = null;
+ }
+ } else {
+ sProxyInfoLock.readLock().unlock();
+ sProxyInfoLock.writeLock().lock();
+ sGlobalProxySpec = null;
+ }
+ sProxyInfoLock.readLock().lock();
+ sProxyInfoLock.writeLock().unlock();
+ }
+
+ /**
+ * Validate syntax of hostname, port and exclusion list entries
+ * {@hide}
+ */
+ public static void validate(String hostname, String port, String exclList) {
+ Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+ Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
+
+ if (!match.matches()) {
+ throw new IllegalArgumentException();
+ }
+
+ if (!listMatch.matches()) {
+ throw new IllegalArgumentException();
+ }
+
+ if (hostname.length() > 0 && port.length() == 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (port.length() > 0) {
+ if (hostname.length() == 0) {
+ throw new IllegalArgumentException();
+ }
+ int portVal = -1;
+ try {
+ portVal = Integer.parseInt(port);
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException();
+ }
+ if (portVal <= 0 || portVal > 0xFFFF) {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4ec5363..a4f9f4e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2321,11 +2321,26 @@
public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
/**
- * Host name and port for a user-selected proxy.
+ * Host name and port for global proxy.
*/
public static final String HTTP_PROXY = "http_proxy";
/**
+ * Exclusion list for global proxy. This string contains a list of comma-separated
+ * domains where the global proxy does not apply. Domains should be listed in a comma-
+ * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com"
+ * @hide
+ */
+ public static final String HTTP_PROXY_EXCLUSION_LIST = "http_proxy_exclusion_list";
+
+ /**
+ * Enables the UI setting to allow the user to specify the global HTTP proxy
+ * and associated exclusion list.
+ * @hide
+ */
+ public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy";
+
+ /**
* Whether the package installer should allow installation of apps downloaded from
* sources other than the Android Market (vending machine).
*