From 602b232a06ede86999aa362a12eb28cbc782dc1d Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Wed, 3 Jul 2013 17:04:33 -0400 Subject: [PATCH] Add PAC File support for proxy configuration PAC (Proxy auto-config) files contain a single javascript function, FindProxyForURL(url, host). It gets called to determine what proxy should be used for a specific request. This adds PAC support to the system. The ProxyProperties has been modified to hold the PAC file when one is present. The Proxy method setHttpProxySystemProperty has been modified to insert a PacProxySelector as the default ProxySelector when it is required. This new ProxySelector makes calls to the ConnectivityService to parse the PAC file. The ConnectivityService and the WifiConfigStore have been modified to support saving the extra PAC file data. The ConnectivityService now has a class attached (PacProxyNative) that interfaces to the native calls for PAC files. The parsing of the PAC file is handled by libpac (which is being added to external/) which utilizes libv8 to parse the javascript. As a fallback to applications that don't use the java ProxySelector, the proxy is setup to point to a local proxy server that will handle the pac parsing. bug:10182711 Change-Id: I5eb8df893c632fd3e1b732385cb7720ad646f401 --- Android.mk | 3 +- core/java/android/app/ActivityThread.java | 4 +- .../android/app/ApplicationThreadNative.java | 7 +- core/java/android/app/IApplicationThread.java | 3 +- core/java/android/net/PacProxySelector.java | 80 +++++++ core/java/android/net/Proxy.java | 50 ++-- core/java/android/net/ProxyProperties.java | 45 +++- core/java/android/provider/Settings.java | 15 ++ packages/services/PacProcessor/Android.mk | 41 ++++ .../services/PacProcessor/IProxyService.cpp | 97 ++++++++ .../services/PacProcessor/IProxyService.h | 59 +++++ .../services/PacProcessor/ProxyService.cpp | 96 ++++++++ packages/services/PacProcessor/ProxyService.h | 33 +++ .../services/PacProcessor/main_pacserver.cpp | 43 ++++ packages/services/Proxy/Android.mk | 14 ++ packages/services/Proxy/AndroidManifest.xml | 25 ++ .../Proxy/com/android/net/IProxyService.aidl | 16 ++ .../services/Proxy/res/values/strings.xml | 22 ++ .../com/android/proxyhandler/ProxyServer.java | 219 +++++++++++++++++ .../android/proxyhandler/ProxyService.java | 68 ++++++ .../proxyhandler/ProxyServiceReceiver.java | 22 ++ .../android/proxyhandler/SocketConnect.java | 59 +++++ .../android/server/ConnectivityService.java | 40 +++- .../server/am/ActivityManagerService.java | 4 +- .../server/connectivity/PacManager.java | 226 ++++++++++++++++++ .../android/net/wifi/WifiConfigStore.java | 19 ++ .../android/net/wifi/WifiConfiguration.java | 5 +- 27 files changed, 1269 insertions(+), 46 deletions(-) create mode 100644 core/java/android/net/PacProxySelector.java create mode 100644 packages/services/PacProcessor/Android.mk create mode 100644 packages/services/PacProcessor/IProxyService.cpp create mode 100644 packages/services/PacProcessor/IProxyService.h create mode 100644 packages/services/PacProcessor/ProxyService.cpp create mode 100644 packages/services/PacProcessor/ProxyService.h create mode 100644 packages/services/PacProcessor/main_pacserver.cpp create mode 100644 packages/services/Proxy/Android.mk create mode 100644 packages/services/Proxy/AndroidManifest.xml create mode 100644 packages/services/Proxy/com/android/net/IProxyService.aidl create mode 100644 packages/services/Proxy/res/values/strings.xml create mode 100644 packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java create mode 100644 packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java create mode 100644 packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java create mode 100644 packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java create mode 100644 services/java/com/android/server/connectivity/PacManager.java diff --git a/Android.mk b/Android.mk index 4b1fa58517aa6..7e34c84b84583 100644 --- a/Android.mk +++ b/Android.mk @@ -257,7 +257,8 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ - wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl + wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ + packages/services/Proxy/com/android/net/IProxyService.aidl \ # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d9f9d61886d79..5300ecaf1688e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -793,8 +793,8 @@ public final class ActivityThread { InetAddress.clearDnsCache(); } - public void setHttpProxy(String host, String port, String exclList) { - Proxy.setHttpProxySystemProperty(host, port, exclList); + public void setHttpProxy(String host, String port, String exclList, String pacFileUrl) { + Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } public void processInBackground() { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 6f18e84d68557..1465de202419a 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -338,7 +338,8 @@ public abstract class ApplicationThreadNative extends Binder final String proxy = data.readString(); final String port = data.readString(); final String exclList = data.readString(); - setHttpProxy(proxy, port, exclList); + final String pacFileUrl = data.readString(); + setHttpProxy(proxy, port, exclList, pacFileUrl); return true; } @@ -1001,12 +1002,14 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public void setHttpProxy(String proxy, String port, String exclList) throws RemoteException { + public void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeString(proxy); data.writeString(port); data.writeString(exclList); + data.writeString(pacFileUrl); mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 8e882df40ad21..61c499a14d01f 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -101,7 +101,8 @@ public interface IApplicationThread extends IInterface { void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; void clearDnsCache() throws RemoteException; - void setHttpProxy(String proxy, String port, String exclList) throws RemoteException; + void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException; void processInBackground() throws RemoteException; void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java new file mode 100644 index 0000000000000..be3a31dce10bd --- /dev/null +++ b/core/java/android/net/PacProxySelector.java @@ -0,0 +1,80 @@ + +package android.net; + +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.net.IProxyService; +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +/** + * @hide + */ +public class PacProxySelector extends ProxySelector { + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + private IProxyService mProxyService; + + public PacProxySelector() { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + } + + @Override + public List select(URI uri) { + String response = null; + String urlString; + try { + urlString = uri.toURL().toString(); + } catch (MalformedURLException e) { + urlString = uri.getHost(); + } + try { + response = mProxyService.resolvePacFile(uri.getHost(), urlString); + } catch (RemoteException e) { + e.printStackTrace(); + } + + return parseResponse(response); + } + + private static List parseResponse(String response) { + String[] split = response.split(";"); + List ret = Lists.newArrayList(); + for (String s : split) { + String trimmed = s.trim(); + if (trimmed.equals("DIRECT")) { + ret.add(java.net.Proxy.NO_PROXY); + } else if (trimmed.startsWith("PROXY ")) { + String[] hostPort = trimmed.substring(6).split(":"); + String host = hostPort[0]; + int port; + try { + port = Integer.parseInt(hostPort[1]); + } catch (Exception e) { + port = 8080; + } + ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port))); + } + } + if (ret.size() == 0) { + ret.add(java.net.Proxy.NO_PROXY); + } + return ret; + } + + @Override + public void connectFailed(URI uri, SocketAddress address, IOException failure) { + + } + +} diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index a408ea0c5fef5..5b38f5793a740 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -18,38 +18,25 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; -import android.net.ProxyProperties; -import android.os.Handler; -import android.os.SystemProperties; import android.text.TextUtils; -import android.provider.Settings; import android.util.Log; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.ProxySelector; -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.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import junit.framework.Assert; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; import org.apache.http.protocol.HttpContext; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * A convenience class for accessing the user and default proxy * settings. @@ -60,6 +47,9 @@ public final class Proxy { private static final boolean DEBUG = false; private static final String TAG = "Proxy"; + private static final ProxySelector sDefaultProxySelector; + private static PacProxySelector sPacProxySelector; + /** * Used to notify an app that's caching the default connection proxy * that either the default connection or its proxy has changed. @@ -96,6 +86,7 @@ public final class Proxy { static { HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + sDefaultProxySelector = ProxySelector.getDefault(); } /** @@ -325,16 +316,19 @@ public final class Proxy { String host = null; String port = null; String exclList = null; + String pacFileUrl = null; if (p != null) { host = p.getHost(); port = Integer.toString(p.getPort()); exclList = p.getExclusionList(); + pacFileUrl = p.getPacFileUrl(); } - setHttpProxySystemProperty(host, port, exclList); + setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } /** @hide */ - public static final void setHttpProxySystemProperty(String host, String port, String exclList) { + public static final void setHttpProxySystemProperty(String host, String port, String exclList, + String pacFileUrl) { if (exclList != null) exclList = exclList.replace(",", "|"); if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); if (host != null) { @@ -358,5 +352,13 @@ public final class Proxy { System.clearProperty("http.nonProxyHosts"); System.clearProperty("https.nonProxyHosts"); } + if ((pacFileUrl != null) && !TextUtils.isEmpty(pacFileUrl)) { + if (sPacProxySelector == null) { + sPacProxySelector = new PacProxySelector(); + } + ProxySelector.setDefault(sPacProxySelector); + } else { + ProxySelector.setDefault(sDefaultProxySelector); + } } } diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 9c4772b90f5cd..76aea9f58e854 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -20,9 +20,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Locale; @@ -38,17 +36,30 @@ public class ProxyProperties implements Parcelable { private String mExclusionList; private String[] mParsedExclusionList; + private String mPacFileUrl; + public static final String LOCAL_EXCL_LIST = ""; + public static final int LOCAL_PORT = 8182; + public static final String LOCAL_HOST = "localhost"; + public ProxyProperties(String host, int port, String exclList) { mHost = host; mPort = port; setExclusionList(exclList); } + public ProxyProperties(String pacFileUrl) { + mHost = LOCAL_HOST; + mPort = LOCAL_PORT; + setExclusionList(LOCAL_EXCL_LIST); + mPacFileUrl = pacFileUrl; + } + private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; mExclusionList = exclList; mParsedExclusionList = parsedExclList; + mPacFileUrl = null; } // copy constructor instead of clone @@ -56,6 +67,7 @@ public class ProxyProperties implements Parcelable { if (source != null) { mHost = source.getHost(); mPort = source.getPort(); + mPacFileUrl = source.getPacFileUrl(); mExclusionList = source.getExclusionList(); mParsedExclusionList = source.mParsedExclusionList; } @@ -69,6 +81,10 @@ public class ProxyProperties implements Parcelable { return inetSocketAddress; } + public String getPacFileUrl() { + return mPacFileUrl; + } + public String getHost() { return mHost; } @@ -130,7 +146,10 @@ public class ProxyProperties implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (mHost != null) { + if (mPacFileUrl != null) { + sb.append("PAC Script: "); + sb.append(mPacFileUrl); + } else if (mHost != null) { sb.append("["); sb.append(mHost); sb.append("] "); @@ -148,6 +167,14 @@ public class ProxyProperties implements Parcelable { public boolean equals(Object o) { if (!(o instanceof ProxyProperties)) return false; ProxyProperties p = (ProxyProperties)o; + // If PAC URL is present in either then they must be equal. + // Other parameters will only be for fall back. + if (!TextUtils.isEmpty(mPacFileUrl)) { + return mPacFileUrl.equals(p.getPacFileUrl()); + } + if (!TextUtils.isEmpty(p.getPacFileUrl())) { + return false; + } if (mExclusionList != null && !mExclusionList.equals(p.getExclusionList())) return false; if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { return false; @@ -181,6 +208,13 @@ public class ProxyProperties implements Parcelable { * @hide */ public void writeToParcel(Parcel dest, int flags) { + if (mPacFileUrl != null) { + dest.writeByte((byte)1); + dest.writeString(mPacFileUrl); + return; + } else { + dest.writeByte((byte)0); + } if (mHost != null) { dest.writeByte((byte)1); dest.writeString(mHost); @@ -201,7 +235,10 @@ public class ProxyProperties implements Parcelable { public ProxyProperties createFromParcel(Parcel in) { String host = null; int port = 0; - if (in.readByte() == 1) { + if (in.readByte() != 0) { + return new ProxyProperties(in.readString()); + } + if (in.readByte() != 0) { host = in.readString(); port = in.readInt(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b2526417aeb59..130123fb9d2cb 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5291,6 +5291,14 @@ public final class Settings { */ public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay"; + /** + * The series of successively longer delays used in retrying to download PAC file. + * Last delay is used between successful PAC downloads. + * + * @hide + */ + public static final String PAC_CHANGE_DELAY = "pac_change_delay"; + /** * Setting to turn off captive portal detection. Feature is enabled by * default and the setting needs to be set to 0 to disable it. @@ -5385,6 +5393,13 @@ public final class Settings { public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; + /** + * The location PAC File for the proxy. + * @hide + */ + public static final String + GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; + /** * Enables the UI setting to allow the user to specify the global HTTP * proxy and associated exclusion list. diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk new file mode 100644 index 0000000000000..e4afde69eb222 --- /dev/null +++ b/packages/services/PacProcessor/Android.mk @@ -0,0 +1,41 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main_pacserver.cpp \ + ProxyService.cpp \ + IProxyService.cpp + +LOCAL_C_INCLUDES += \ + external/chromium-libpac/src + +LOCAL_SHARED_LIBRARIES := \ + libutils \ + liblog \ + libpac \ + libbinder \ + libstlport + +LOCAL_MODULE := pacserver +LOCAL_MODULE_TAGS := optional + +include external/stlport/libstlport.mk + +include $(BUILD_EXECUTABLE) diff --git a/packages/services/PacProcessor/IProxyService.cpp b/packages/services/PacProcessor/IProxyService.cpp new file mode 100644 index 0000000000000..3707d8554b349 --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.cpp @@ -0,0 +1,97 @@ +#define LOG_TAG "ProxyTesting" + +#include +#include +#include +#include +#include +#include "IProxyService.h" + +#include + +#include + +using namespace android; + +String16 BpProxyService::resolveProxies(String16 host, String16 url) { + String16 ret; + return ret; +} + +void BpProxyService::setPacFile(String16& scriptContents) { + +} + +void BpProxyService::startPacSystem() { + +} +void BpProxyService::stopPacSystem() { + +} + +IMPLEMENT_META_INTERFACE(ProxyService, "com.android.net.IProxyService"); + +status_t BnProxyService::onTransact( + uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) { + int returnInt = 0; + switch (code) { + case RESOLVE_PROXIES: + { + CHECK_INTERFACE(IProxyService, data, reply); + String16 host = data.readString16(); + String16 url = data.readString16(); + String16 response = resolveProxies(host, url); + reply->writeNoException(); + reply->writeString16(response); + return NO_ERROR; + } break; + case SET_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + String16 pacFile = data.readString16(); + setPacFile(pacFile); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + case START_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + startPacSystem(); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + case STOP_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + stopPacSystem(); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +int BnProxyService::getCallingUid() { + return IPCThreadState::self()->getCallingUid(); +} + +bool BnProxyService::notSystemUid() { + return getCallingUid() != AID_SYSTEM; +} diff --git a/packages/services/PacProcessor/IProxyService.h b/packages/services/PacProcessor/IProxyService.h new file mode 100644 index 0000000000000..57c527b5944e5 --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.h @@ -0,0 +1,59 @@ +#ifndef IPROXY_SERVICE_H +#define IPROXY_SERVICE_H + +#include +#include + +namespace android { +class IProxyService : public IInterface { +public: + /** + * Keep up-to-date with + * frameworks/base/packages/services/Proxy/com/android/net/IProxyService.aidl + */ + enum { + RESOLVE_PROXIES = IBinder::FIRST_CALL_TRANSACTION, + SET_PAC, + START_PAC, + STOP_PAC, + }; +public: + DECLARE_META_INTERFACE(ProxyService); + +public: + + virtual String16 resolveProxies(String16 host, String16 url) = 0; + + virtual void setPacFile(String16& scriptContents) = 0; + + virtual void startPacSystem() = 0; + virtual void stopPacSystem() = 0; +private: +}; + +class BpProxyService : public BpInterface { +public: + BpProxyService(const sp& impl) : BpInterface(impl) {} + + virtual String16 resolveProxies(String16 host, String16 url); + + virtual void setPacFile(String16& scriptContents); + + virtual void startPacSystem(); + virtual void stopPacSystem(); +}; + +class BnProxyService : public BnInterface { +public: + virtual status_t onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); + +private: + int getCallingUid(); + + bool notSystemUid(); +}; +} + + +#endif //IPROXY_SERVICE_H diff --git a/packages/services/PacProcessor/ProxyService.cpp b/packages/services/PacProcessor/ProxyService.cpp new file mode 100644 index 0000000000000..7084a47fe4351 --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.cpp @@ -0,0 +1,96 @@ +#define LOG_TAG "ProxyService" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ProxyService.h" + +using namespace net; + +using namespace android; + +class ProxyErrorLogger : public ProxyErrorListener { +protected: + ~ProxyErrorLogger() { + + } +public: + void AlertMessage(String16 message) { + String8 str(message); + ALOGD("Alert: %s", str.string()); + } + void ErrorMessage(String16 message) { + String8 str(message); + ALOGE("Error: %s", str.string()); + } +}; + +void ProxyService::instantiate() { + ALOGV("instantiate"); + defaultServiceManager()->addService(String16("com.android.net.IProxyService"), + new ProxyService()); +} + +ProxyService::ProxyService() { + hasSetScript = false; +} + +ProxyService::~ProxyService() { + stopPacSystem(); +} + +String16 ProxyService::resolveProxies(String16 host, String16 url) { + ALOGV("resolve"); + String16 blankRet; + if (proxyResolver != NULL) { + if (hasSetScript) { + String16 ret; + if (proxyResolver->GetProxyForURL(url, host, &ret) != OK) { + return blankRet; + } + return ret; + } else { + ALOGD("Unable to resolve PAC when no script is set!"); + } + } else { + ALOGE("Cannot parse while resolver not initialized!"); + } + return blankRet; +} + +void ProxyService::setPacFile(String16& scriptContents) { + ALOGV("set"); + if (proxyResolver != NULL) { + if (proxyResolver->SetPacScript(scriptContents) != OK) { + ALOGD("Unable to initialize PAC - Resolving will not work"); + } else { + hasSetScript = true; + } + } else { + ALOGE("PAC script set while resolver not initialized!"); + } +} + +void ProxyService::startPacSystem() { + ALOGV("start"); + // Stop in case redundant start call + stopPacSystem(); + + proxyResolver = new ProxyResolverV8(ProxyResolverJSBindings::CreateDefault(), + new ProxyErrorLogger()); + hasSetScript = false; +} + +void ProxyService::stopPacSystem() { + ALOGV("stop"); + if (proxyResolver != NULL) { + delete proxyResolver; + proxyResolver = NULL; + } +} diff --git a/packages/services/PacProcessor/ProxyService.h b/packages/services/PacProcessor/ProxyService.h new file mode 100644 index 0000000000000..a0861b2d620ea --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.h @@ -0,0 +1,33 @@ +#ifndef PROXY_SERVICE_H +#define PROXY_SERVICE_H + +#include +#include "IProxyService.h" +#include "proxy_resolver_v8.h" + +namespace android { + +class ProxyService : public BnProxyService { +public: + static void instantiate(); + +private: + ProxyService(); + virtual ~ProxyService(); + +public: + String16 resolveProxies(String16 host, String16 url); + + void setPacFile(String16& scriptContents); + + void startPacSystem(); + void stopPacSystem(); + +private: + net::ProxyResolverV8* proxyResolver; + bool hasSetScript; +}; + +} + +#endif //PROXY_SERVICE_H diff --git a/packages/services/PacProcessor/main_pacserver.cpp b/packages/services/PacProcessor/main_pacserver.cpp new file mode 100644 index 0000000000000..19588b5074c28 --- /dev/null +++ b/packages/services/PacProcessor/main_pacserver.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "pacserver" +//#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include "ProxyService.h" +#include "proxy_resolver_v8.h" +#include + +using namespace android; + +int main(int argc, char** argv) +{ + sp proc(ProcessState::self()); + sp sm = defaultServiceManager(); + + printf("1\n"); + ALOGV("ServiceManager: %p", sm.get()); + ProxyService::instantiate(); + printf("1\n"); + + ProcessState::self()->startThreadPool(); + printf("1\n"); + IPCThreadState::self()->joinThreadPool(); +} diff --git a/packages/services/Proxy/Android.mk b/packages/services/Proxy/Android.mk new file mode 100644 index 0000000000000..d5546b27bf445 --- /dev/null +++ b/packages/services/Proxy/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := ProxyHandler +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml new file mode 100644 index 0000000000000..02475c0f7d9e7 --- /dev/null +++ b/packages/services/Proxy/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/services/Proxy/com/android/net/IProxyService.aidl b/packages/services/Proxy/com/android/net/IProxyService.aidl new file mode 100644 index 0000000000000..7e9ed799c7c39 --- /dev/null +++ b/packages/services/Proxy/com/android/net/IProxyService.aidl @@ -0,0 +1,16 @@ +package com.android.net; + +/** @hide */ +interface IProxyService +{ + /** + * Keep up-to-date with + * frameworks/base/packages/services/PacProcessor/IProxyService.h + */ + String resolvePacFile(String host, String url); + + int setPacFile(String scriptContents); + + int startPacSystem(); + int stopPacSystem(); +} diff --git a/packages/services/Proxy/res/values/strings.xml b/packages/services/Proxy/res/values/strings.xml new file mode 100644 index 0000000000000..6188d79839224 --- /dev/null +++ b/packages/services/Proxy/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + ProxyHandler + + diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java new file mode 100644 index 0000000000000..5d8689e8edc58 --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -0,0 +1,219 @@ + +package com.android.proxyhandler; + +import android.net.ProxyProperties; +import android.util.Log; + +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @hide + */ +public class ProxyServer extends Thread { + + private static final String CONNECT = "CONNECT"; + private static final String HTTP_OK = "HTTP/1.1 200 OK\n"; + + private static final String TAG = "ProxyServer"; + + private ExecutorService threadExecutor; + + public boolean mIsRunning = false; + + private ServerSocket serverSocket; + private ProxyProperties mProxy; + + private class ProxyConnection implements Runnable { + private Socket connection; + + private ProxyConnection(Socket connection) { + this.connection = connection; + } + + @Override + public void run() { + try { + android.net.Proxy.setHttpProxySystemProperty(mProxy); + + String requestLine = getLine(connection.getInputStream()); + if (requestLine == null) { + connection.close(); + return; + } + String[] splitLine = requestLine.split(" "); + if (splitLine.length < 3) { + connection.close(); + return; + } + String requestType = splitLine[0]; + String urlString = splitLine[1]; + + String host = ""; + int port = 80; + + if (requestType.equals(CONNECT)) { + String[] hostPortSplit = urlString.split(":"); + host = hostPortSplit[0]; + try { + port = Integer.parseInt(hostPortSplit[1]); + } catch (NumberFormatException nfe) { + port = 443; + } + urlString = "Https://" + host + ":" + port; + } else { + try { + URI url = new URI(urlString); + host = url.getHost(); + port = url.getPort(); + if (port < 0) { + port = 80; + } + } catch (URISyntaxException e) { + connection.close(); + return; + } + } + + List list = Lists.newArrayList(); + try { + list = ProxySelector.getDefault().select(new URI(urlString)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + Socket server = null; + for (Proxy proxy : list) { + try { + if (!proxy.equals(Proxy.NO_PROXY)) { + // Only Inets created by PacProxySelector. + InetSocketAddress inetSocketAddress = + (InetSocketAddress)list.get(0).address(); + server = new Socket(inetSocketAddress.getAddress(), + inetSocketAddress.getPort()); + sendLine(server, requestLine); + } else { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + } catch (IOException ioe) { + + } + if (server != null) { + break; + } + } + if (server == null) { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + // Pass data back and forth until complete. + SocketConnect.connect(connection, server); + } catch (IOException e) { + Log.d(TAG, "Problem Proxying", e); + } + try { + connection.close(); + } catch (IOException ioe) { + + } + } + + private String getLine(InputStream inputStream) throws IOException { + StringBuffer buffer = new StringBuffer(); + int byteBuffer = inputStream.read(); + if (byteBuffer < 0) return ""; + do { + if (byteBuffer != '\r') { + buffer.append((char)byteBuffer); + } + byteBuffer = inputStream.read(); + } while ((byteBuffer != '\n') && (byteBuffer >= 0)); + + return buffer.toString(); + } + + private void sendLine(Socket socket, String line) throws IOException { + OutputStream os = socket.getOutputStream(); + os.write(line.getBytes()); + os.write('\r'); + os.write('\n'); + os.flush(); + } + } + + public ProxyServer() { + threadExecutor = Executors.newCachedThreadPool(); + } + + @Override + public void run() { + try { + serverSocket = new ServerSocket(ProxyService.PORT); + + serverSocket.setReuseAddress(true); + + while (mIsRunning) { + try { + ProxyConnection parser = new ProxyConnection(serverSocket.accept()); + + threadExecutor.execute(parser); + } catch (IOException e) { + e.printStackTrace(); + } + } + } catch (SocketException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + mIsRunning = false; + } + + public synchronized void startServer() { + mIsRunning = true; + start(); + } + + public synchronized void stopServer() { + mIsRunning = false; + if (serverSocket != null) { + try { + serverSocket.close(); + serverSocket = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void setProxy(ProxyProperties proxy) { + mProxy = proxy; + } +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java new file mode 100644 index 0000000000000..0aea5ee1a401d --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java @@ -0,0 +1,68 @@ +package com.android.proxyhandler; + +import android.app.Service; +import android.content.Intent; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; + +/** + * @hide + */ +public class ProxyService extends Service { + + private static ProxyServer server = null; + + /** Keep these values up-to-date with PacManager.java */ + public static final String KEY_PROXY = "keyProxy"; + public static final String HOST = "localhost"; + public static final int PORT = 8182; + public static final String EXCL_LIST = ""; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + handleCommand(intent); + } + return START_STICKY; + } + + private void handleCommand(Intent intent) { + Bundle bundle = intent.getExtras(); + ProxyProperties proxy = null; + if ((bundle != null) && bundle.containsKey(Proxy.EXTRA_PROXY_INFO)) { + proxy = bundle.getParcelable(Proxy.EXTRA_PROXY_INFO); + if ((proxy != null) && !TextUtils.isEmpty(proxy.getPacFileUrl())) { + startProxy(proxy); + } else { + stopSelf(); + } + } else { + stopSelf(); + } + } + + + private void startProxy(ProxyProperties proxy) { + if (server == null) { + server = new ProxyServer(); + server.startServer(); + } + server.setProxy(proxy); + } + + @Override + public void onDestroy() { + if (server != null) { + server.stopServer(); + server = null; + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} \ No newline at end of file diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java new file mode 100644 index 0000000000000..f5c2ca54ab67a --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java @@ -0,0 +1,22 @@ +package com.android.proxyhandler; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Proxy; +import android.os.Bundle; + +public class ProxyServiceReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Intent service = new Intent(context, ProxyService.class); + Bundle bundle = intent.getExtras(); + if (bundle != null) { + service.putExtra(Proxy.EXTRA_PROXY_INFO, + bundle.getParcelable(Proxy.EXTRA_PROXY_INFO)); + } + context.startService(service); + } + +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java new file mode 100644 index 0000000000000..0d7df7f28cd5b --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java @@ -0,0 +1,59 @@ +package com.android.proxyhandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * @hide + */ +public class SocketConnect extends Thread { + + private InputStream from; + private OutputStream to; + + public SocketConnect(Socket from, Socket to) throws IOException { + this.from = from.getInputStream(); + this.to = to.getOutputStream(); + start(); + } + + @Override + public void run() { + final byte[] buffer = new byte[512]; + + try { + while (true) { + int r = from.read(buffer); + if (r < 0) { + break; + } + to.write(buffer, 0, r); + } + from.close(); + to.close(); + } catch (IOException io) { + + } + } + + public static void connect(Socket first, Socket second) { + try { + SocketConnect sc1 = new SocketConnect(first, second); + SocketConnect sc2 = new SocketConnect(second, first); + try { + sc1.join(); + } catch (InterruptedException e) { + } + try { + sc2.join(); + } catch (InterruptedException e) { + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + +} diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 17ae85f39afdc..3ae2eb542c9b7 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -56,13 +56,11 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Uri; import android.net.LinkProperties.CompareResult; import android.net.MobileDataStateTracker; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; import android.net.NetworkQuotaInfo; import android.net.NetworkState; import android.net.NetworkStateTracker; @@ -70,6 +68,7 @@ import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; +import android.net.Uri; import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -102,6 +101,7 @@ import android.util.SparseIntArray; import android.util.Xml; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; @@ -110,9 +110,11 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; +import com.android.net.IProxyService; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.PacManager; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; @@ -120,8 +122,6 @@ import com.android.server.net.LockdownVpnTracker; import com.google.android.collect.Lists; import com.google.android.collect.Sets; -import com.android.internal.annotations.GuardedBy; - import dalvik.system.DexClassLoader; import org.xmlpull.v1.XmlPullParser; @@ -370,6 +370,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // track the global proxy. private ProxyProperties mGlobalProxy = null; + private PacManager mPacManager = null; + private SettingsObserver mSettingsObserver; NetworkConfig[] mNetConfigs; @@ -631,6 +633,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mDataConnectionStats = new DataConnectionStats(mContext); mDataConnectionStats.startMonitoring(); + + mPacManager = new PacManager(mContext); } /** @@ -3168,13 +3172,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { // of proxy info to all the JVMs. // enforceAccessPermission(); synchronized (mProxyLock) { - if (mGlobalProxy != null) return mGlobalProxy; - return (mDefaultProxyDisabled ? null : mDefaultProxy); + ProxyProperties ret = mGlobalProxy; + if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy; + return ret; } } public void setGlobalProxy(ProxyProperties proxyProperties) { enforceConnectivityInternalPermission(); + synchronized (mProxyLock) { if (proxyProperties == mGlobalProxy) return; if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; @@ -3183,11 +3189,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { String host = ""; int port = 0; String exclList = ""; - if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + String pacFileUrl = ""; + if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || + !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { mGlobalProxy = new ProxyProperties(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionList(); + if (proxyProperties.getPacFileUrl() != null) { + pacFileUrl = proxyProperties.getPacFileUrl(); + } } else { mGlobalProxy = null; } @@ -3198,6 +3209,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port); Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList); + Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl); } finally { Binder.restoreCallingIdentity(token); } @@ -3215,8 +3227,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0); String exclList = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); - if (!TextUtils.isEmpty(host)) { - ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC); + if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) { + ProxyProperties proxyProperties; + if (!TextUtils.isEmpty(pacFileUrl)) { + proxyProperties = new ProxyProperties(pacFileUrl); + } else { + proxyProperties = new ProxyProperties(host, port, exclList); + } synchronized (mProxyLock) { mGlobalProxy = proxyProperties; } @@ -3234,7 +3252,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void handleApplyDefaultProxy(ProxyProperties proxy) { - if (proxy != null && TextUtils.isEmpty(proxy.getHost())) { + if (proxy != null && TextUtils.isEmpty(proxy.getHost()) + && TextUtils.isEmpty(proxy.getPacFileUrl())) { proxy = null; } synchronized (mProxyLock) { @@ -3276,6 +3295,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendProxyBroadcast(ProxyProperties proxy) { if (proxy == null) proxy = new ProxyProperties("", 0, ""); + mPacManager.setCurrentProxyScriptUrl(proxy); if (DBG) log("sending Proxy Broadcast for " + proxy); Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 6fa18228507ce..2d2d4682ebc5a 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1184,17 +1184,19 @@ public final class ActivityManagerService extends ActivityManagerNative String host = ""; String port = ""; String exclList = ""; + String pacFileUrl = null; if (proxy != null) { host = proxy.getHost(); port = Integer.toString(proxy.getPort()); exclList = proxy.getExclusionList(); + pacFileUrl = proxy.getPacFileUrl(); } synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); if (r.thread != null) { try { - r.thread.setHttpProxy(host, port, exclList); + r.thread.setHttpProxy(host, port, exclList, pacFileUrl); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java new file mode 100644 index 0000000000000..189c6261c39a7 --- /dev/null +++ b/services/java/com/android/server/connectivity/PacManager.java @@ -0,0 +1,226 @@ +package com.android.server.connectivity; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.ProxyProperties; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.net.IProxyService; + + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.params.ConnRouteParams; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.ProxySelector; +import java.net.URL; +import java.net.URLConnection; + +/** + * @hide + */ +public class PacManager implements Runnable { + public static final int NO_ERROR = 0; + public static final int PERMISSION_DENIED = 1; + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + + + private static final String TAG = "PACManager"; + + private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH"; + + private static final String DEFAULT_DELAYS = "8 32 120 14400 43200"; + private static final int DELAY_1 = 0; + private static final int DELAY_4 = 3; + private static final int DELAY_LONG = 4; + + /** Keep these values up-to-date with ProxyService.java */ + public static final String KEY_PROXY = "keyProxy"; + private String mCurrentPac; + private volatile String mPacUrl; + + private AlarmManager mAlarmManager; + private IProxyService mProxyService; + private PendingIntent mPacRefreshIntent; + private Context mContext; + + private int mCurrentDelay; + + class PacRefreshIntentReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + new Thread(PacManager.this).start(); + } + } + + public PacManager(Context context) { + mContext = context; + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + + mPacRefreshIntent = PendingIntent.getBroadcast( + context, 0, new Intent(ACTION_PAC_REFRESH), 0); + context.registerReceiver(new PacRefreshIntentReceiver(), + new IntentFilter(ACTION_PAC_REFRESH)); + } + + private AlarmManager getAlarmManager() { + if (mAlarmManager == null) { + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + } + return mAlarmManager; + } + + public void setCurrentProxyScriptUrl(ProxyProperties proxy) { + if (!TextUtils.isEmpty(proxy.getPacFileUrl())) { + try { + mProxyService.startPacSystem(); + mPacUrl = proxy.getPacFileUrl(); + mCurrentDelay = DELAY_1; + getAlarmManager().cancel(mPacRefreshIntent); + new Thread(this).start(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e); + } + } else { + try { + mProxyService.stopPacSystem(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + /** + * Does a post and reports back the status code. + * + * @throws IOException + */ + public static String get(String urlString) throws IOException { + URL url = new URL(urlString); + URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY); + BufferedReader in = new BufferedReader(new InputStreamReader( + urlConnection.getInputStream())); + String inputLine; + String resp = ""; + while ((inputLine = in.readLine()) != null) { + resp = resp + inputLine + "\n"; + } + in.close(); + return resp; + } + + private static String toString(InputStream content) throws IOException { + StringBuffer buffer = new StringBuffer(); + String line; + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content)); + + while ((line = bufferedReader.readLine()) != null) { + if (buffer.length() != 0) { + buffer.append('\n'); + } + buffer.append(line); + } + + return buffer.toString(); + } + + @Override + public void run() { + String file; + try { + file = get(mPacUrl); + } catch (IOException ioe) { + file = null; + } + if (file != null) { + if (!file.equals(mCurrentPac)) { + setCurrentProxyScript(file); + } + longSchedule(); + } else { + reschedule(); + } + } + + private int getNextDelay(int currentDelay) { + if (++currentDelay > DELAY_4) { + return DELAY_4; + } + return currentDelay; + } + + private void longSchedule() { + mCurrentDelay = DELAY_1; + setDownloadIn(DELAY_LONG); + } + + private void reschedule() { + mCurrentDelay = getNextDelay(mCurrentDelay); + setDownloadIn(mCurrentDelay); + } + + private String getPacChangeDelay() { + final ContentResolver cr = mContext.getContentResolver(); + + /** Check system properties for the default value then use secure settings value, if any. */ + String defaultDelay = SystemProperties.get( + "conn." + Settings.Global.PAC_CHANGE_DELAY, + DEFAULT_DELAYS); + String val = Settings.Global.getString(cr, Settings.Global.PAC_CHANGE_DELAY); + return (val == null) ? defaultDelay : val; + } + + private long getDownloadDelay(int delayIndex) { + String[] list = getPacChangeDelay().split(" "); + if (delayIndex < list.length) { + return Long.parseLong(list[delayIndex]); + } + return 0; + } + + private void setDownloadIn(int delayIndex) { + long delay = getDownloadDelay(delayIndex); + long timeTillTrigger = 1000 * delay + SystemClock.elapsedRealtime(); + getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent); + } + + private boolean setCurrentProxyScript(String script) { + try { + if (mProxyService.setPacFile(script) != NO_ERROR) { + Log.e(TAG, "Unable to parse proxy script."); + return false; + } + mCurrentPac = script; + } catch (RemoteException e) { + Log.e(TAG, "Unable to set PAC file", e); + } + return true; + } +} diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index 9418de1f7c6a8..ad700aa8e1f7c 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -141,6 +141,7 @@ class WifiConfigStore { private static final String PROXY_SETTINGS_KEY = "proxySettings"; private static final String PROXY_HOST_KEY = "proxyHost"; private static final String PROXY_PORT_KEY = "proxyPort"; + private static final String PROXY_PAC_FILE = "proxyPac"; private static final String EXCLUSION_LIST_KEY = "exclusionList"; private static final String EOS = "eos"; @@ -771,6 +772,14 @@ class WifiConfigStore { out.writeUTF(exclusionList); writeToFile = true; break; + case PAC: + ProxyProperties proxyPacProperties = linkProperties.getHttpProxy(); + out.writeUTF(PROXY_SETTINGS_KEY); + out.writeUTF(config.proxySettings.toString()); + out.writeUTF(PROXY_PAC_FILE); + out.writeUTF(proxyPacProperties.getPacFileUrl()); + writeToFile = true; + break; case NONE: out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); @@ -838,6 +847,7 @@ class WifiConfigStore { ProxySettings proxySettings = ProxySettings.NONE; LinkProperties linkProperties = new LinkProperties(); String proxyHost = null; + String pacFileUrl = null; int proxyPort = -1; String exclusionList = null; String key; @@ -879,6 +889,8 @@ class WifiConfigStore { proxyHost = in.readUTF(); } else if (key.equals(PROXY_PORT_KEY)) { proxyPort = in.readInt(); + } else if (key.equals(PROXY_PAC_FILE)) { + pacFileUrl = in.readUTF(); } else if (key.equals(EXCLUSION_LIST_KEY)) { exclusionList = in.readUTF(); } else if (key.equals(EOS)) { @@ -920,6 +932,12 @@ class WifiConfigStore { new ProxyProperties(proxyHost, proxyPort, exclusionList); linkProperties.setHttpProxy(proxyProperties); break; + case PAC: + config.proxySettings = proxySettings; + ProxyProperties proxyPacProperties = + new ProxyProperties(pacFileUrl); + linkProperties.setHttpProxy(proxyPacProperties); + break; case NONE: config.proxySettings = proxySettings; break; @@ -1247,6 +1265,7 @@ class WifiConfigStore { switch (newConfig.proxySettings) { case STATIC: + case PAC: ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy(); ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy(); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index bd8f0eb2d041d..de377ee5aa329 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -312,7 +312,10 @@ public class WifiConfiguration implements Parcelable { STATIC, /* no proxy details are assigned, this is used to indicate * that any existing proxy settings should be retained */ - UNASSIGNED + UNASSIGNED, + /* Use a Pac based proxy. + */ + PAC } /** * @hide