diff options
author | Jason Monk <jmonk@google.com> | 2013-07-03 17:04:33 -0400 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2013-08-07 21:01:39 -0400 |
commit | 602b232a06ede86999aa362a12eb28cbc782dc1d (patch) | |
tree | 07a2168ba91425f81bd7ac9c9311adb78114cc13 | |
parent | 55db1e1218971105e68ba9d451b2e0b1e9e5f9fb (diff) | |
download | frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.zip frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.tar.gz frameworks_base-602b232a06ede86999aa362a12eb28cbc782dc1d.tar.bz2 |
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
27 files changed, 1268 insertions, 45 deletions
@@ -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 d9f9d61..5300eca 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 6f18e84..1465de2 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 8e882df..61c499a 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 0000000..be3a31d --- /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<Proxy> 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<Proxy> parseResponse(String response) { + String[] split = response.split(";"); + List<Proxy> 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 a408ea0..5b38f57 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 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.protocol.HttpContext; + 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.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; - /** * 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 9c4772b..76aea9f 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 b252641..130123f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5292,6 +5292,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. * @@ -5386,6 +5394,13 @@ public final class Settings { 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 0000000..e4afde6 --- /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 0000000..3707d85 --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.cpp @@ -0,0 +1,97 @@ +#define LOG_TAG "ProxyTesting" + +#include <stdint.h> +#include <sys/types.h> +#include <binder/Parcel.h> +#include <binder/IPCThreadState.h> +#include <utils/Errors.h> +#include "IProxyService.h" + +#include <utils/Log.h> + +#include <private/android_filesystem_config.h> + +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 0000000..57c527b --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.h @@ -0,0 +1,59 @@ +#ifndef IPROXY_SERVICE_H +#define IPROXY_SERVICE_H + +#include <binder/IInterface.h> +#include <binder/IBinder.h> + +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<IProxyService> { +public: + BpProxyService(const sp<IBinder>& impl) : BpInterface<IProxyService>(impl) {} + + virtual String16 resolveProxies(String16 host, String16 url); + + virtual void setPacFile(String16& scriptContents); + + virtual void startPacSystem(); + virtual void stopPacSystem(); +}; + +class BnProxyService : public BnInterface<IProxyService> { +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 0000000..7084a47 --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.cpp @@ -0,0 +1,96 @@ +#define LOG_TAG "ProxyService" +#include <utils/Log.h> + +#include <errno.h> +#include <utils/threads.h> +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> +#include <sys/stat.h> +#include <proxy_resolver_v8.h> +#include <sstream> + +#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 0000000..a0861b2 --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.h @@ -0,0 +1,33 @@ +#ifndef PROXY_SERVICE_H +#define PROXY_SERVICE_H + +#include <binder/IInterface.h> +#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 0000000..19588b5 --- /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 <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> +#include "ProxyService.h" +#include "proxy_resolver_v8.h" +#include <stdio.h> + +using namespace android; + +int main(int argc, char** argv) +{ + sp<ProcessState> proc(ProcessState::self()); + sp<IServiceManager> 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 0000000..d5546b2 --- /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 0000000..02475c0 --- /dev/null +++ b/packages/services/Proxy/AndroidManifest.xml @@ -0,0 +1,25 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.proxyhandler" + coreApp="true"> + + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:persistent="true" + android:label="@string/app_label" + android:process="com.android.proxyhandler"> + + <service android:name=".ProxyService" + android:exported="true"> + </service> + + <receiver android:name=".ProxyServiceReceiver"> + + <intent-filter> + <action android:name="android.intent.action.PROXY_CHANGE" /> + </intent-filter> + </receiver> + + </application> +</manifest> 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 0000000..7e9ed79 --- /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 0000000..6188d79 --- /dev/null +++ b/packages/services/Proxy/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2009, 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. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">ProxyHandler</string> + +</resources> 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 0000000..5d8689e --- /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<Proxy> 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 0000000..0aea5ee --- /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 0000000..f5c2ca5 --- /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 0000000..0d7df7f --- /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 17ae85f..3ae2eb5 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 6fa1822..2d2d468 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 0000000..189c626 --- /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 9418de1..ad700aa 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 bd8f0eb..de377ee 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 |