Merge "Sync activity picker to latest dialog style." into honeycomb
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index 9d589cf..eb599b5 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -105,7 +105,7 @@
volatile int32_t head; // server's current front buffer
volatile int32_t available; // number of dequeue-able buffers
volatile int32_t queued; // number of buffers waiting for post
- volatile int32_t inUse; // buffer currently in use by SF
+ volatile int32_t reserved1;
volatile status_t status; // surface's status code
// not part of the conditions
@@ -275,7 +275,6 @@
int32_t identity);
ssize_t retireAndLock();
- status_t unlock(int buffer);
void setStatus(status_t status);
status_t reallocateAll();
status_t reallocateAllExcept(int buffer);
@@ -356,12 +355,6 @@
inline const char* name() const { return "BuffersAvailableCondition"; }
};
- struct UnlockUpdate : public UpdateBase {
- const int lockedBuffer;
- inline UnlockUpdate(SharedBufferBase* sbb, int lockedBuffer);
- inline ssize_t operator()();
- };
-
struct RetireUpdate : public UpdateBase {
const int numBuffers;
inline RetireUpdate(SharedBufferBase* sbb, int numBuffers);
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 27f65bc..30b45f7 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -38,6 +38,15 @@
AKEY_EVENT_FLAG_START_TRACKING = 0x40000000
};
+enum {
+ /*
+ * Indicates that an input device has switches.
+ * This input source flag is hidden from the API because switches are only used by the system
+ * and applications have no way to interact with them.
+ */
+ AINPUT_SOURCE_SWITCH = 0x80000000,
+};
+
/*
* Maximum number of pointers supported per motion event.
* Smallest number of pointers is 1.
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index af11f97..7505d53 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -58,7 +58,6 @@
void SharedBufferStack::init(int32_t i)
{
- inUse = -2;
status = NO_ERROR;
identity = i;
}
@@ -199,9 +198,9 @@
SharedBufferStack& stack( *mSharedStack );
snprintf(buffer, SIZE,
"%s[ head=%2d, available=%2d, queued=%2d ] "
- "reallocMask=%08x, inUse=%2d, identity=%d, status=%d",
+ "reallocMask=%08x, identity=%d, status=%d",
prefix, stack.head, stack.available, stack.queued,
- stack.reallocMask, stack.inUse, stack.identity, stack.status);
+ stack.reallocMask, stack.identity, stack.status);
result.append(buffer);
result.append("\n");
return result;
@@ -302,22 +301,6 @@
return NO_ERROR;
}
-SharedBufferServer::UnlockUpdate::UnlockUpdate(
- SharedBufferBase* sbb, int lockedBuffer)
- : UpdateBase(sbb), lockedBuffer(lockedBuffer) {
-}
-ssize_t SharedBufferServer::UnlockUpdate::operator()() {
- if (stack.inUse != lockedBuffer) {
- LOGE("unlocking %d, but currently locked buffer is %d "
- "(identity=%d, token=%d)",
- lockedBuffer, stack.inUse,
- stack.identity, stack.token);
- return BAD_VALUE;
- }
- android_atomic_write(-1, &stack.inUse);
- return NO_ERROR;
-}
-
SharedBufferServer::RetireUpdate::RetireUpdate(
SharedBufferBase* sbb, int numBuffers)
: UpdateBase(sbb), numBuffers(numBuffers) {
@@ -327,9 +310,6 @@
if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
- // Preventively lock the current buffer before updating queued.
- android_atomic_write(stack.headBuf, &stack.inUse);
-
// Decrement the number of queued buffers
int32_t queued;
do {
@@ -345,7 +325,6 @@
head = (head + 1) % numBuffers;
const int8_t headBuf = stack.index[head];
stack.headBuf = headBuf;
- android_atomic_write(headBuf, &stack.inUse);
// head is only modified here, so we don't need to use cmpxchg
android_atomic_write(head, &stack.head);
@@ -546,13 +525,6 @@
return buf;
}
-status_t SharedBufferServer::unlock(int buf)
-{
- UnlockUpdate update(this, buf);
- status_t err = updateCondition( update );
- return err;
-}
-
void SharedBufferServer::setStatus(status_t status)
{
if (status < NO_ERROR) {
@@ -694,12 +666,6 @@
stack.head = 0;
stack.headBuf = 0;
- // If one of the buffers is in use it must be the head buffer, which we are
- // renaming to buffer 0.
- if (stack.inUse > 0) {
- stack.inUse = 0;
- }
-
// Free the buffers from the end of the list that are no longer needed.
for (int i = newNumBuffers; i < mNumBuffers; i++) {
mBufferList.remove(i);
diff --git a/libs/ui/EGLUtils.cpp b/libs/ui/EGLUtils.cpp
index 1663313..f24a71d 100644
--- a/libs/ui/EGLUtils.cpp
+++ b/libs/ui/EGLUtils.cpp
@@ -66,12 +66,6 @@
if (outConfig == NULL)
return BAD_VALUE;
- int err;
- PixelFormatInfo fbFormatInfo;
- if ((err = getPixelFormatInfo(PixelFormat(format), &fbFormatInfo)) < 0) {
- return err;
- }
-
// Get all the "potential match" configs...
if (eglGetConfigs(dpy, NULL, 0, &numConfigs) == EGL_FALSE)
return BAD_VALUE;
@@ -81,23 +75,14 @@
free(configs);
return BAD_VALUE;
}
-
- const int fbSzA = fbFormatInfo.getSize(PixelFormatInfo::INDEX_ALPHA);
- const int fbSzR = fbFormatInfo.getSize(PixelFormatInfo::INDEX_RED);
- const int fbSzG = fbFormatInfo.getSize(PixelFormatInfo::INDEX_GREEN);
- const int fbSzB = fbFormatInfo.getSize(PixelFormatInfo::INDEX_BLUE);
int i;
EGLConfig config = NULL;
for (i=0 ; i<n ; i++) {
- EGLint r,g,b,a;
- EGLConfig curr = configs[i];
- eglGetConfigAttrib(dpy, curr, EGL_RED_SIZE, &r);
- eglGetConfigAttrib(dpy, curr, EGL_GREEN_SIZE, &g);
- eglGetConfigAttrib(dpy, curr, EGL_BLUE_SIZE, &b);
- eglGetConfigAttrib(dpy, curr, EGL_ALPHA_SIZE, &a);
- if (fbSzA == a && fbSzR == r && fbSzG == g && fbSzB == b) {
- config = curr;
+ EGLint nativeVisualId = 0;
+ eglGetConfigAttrib(dpy, configs[i], EGL_NATIVE_VISUAL_ID, &nativeVisualId);
+ if (nativeVisualId>0 && format == nativeVisualId) {
+ config = configs[i];
break;
}
}
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index ce84683..33ef1fc 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -61,7 +61,7 @@
const size_t c = list.size();
for (size_t i=0 ; i<c ; i++) {
const alloc_rec_t& rec(list.valueAt(i));
- snprintf(buffer, SIZE, "%10p: %7.2f KiB | %4u (%4u) x %4u | %2d | 0x%08x\n",
+ snprintf(buffer, SIZE, "%10p: %7.2f KiB | %4u (%4u) x %4u | %8X | 0x%08x\n",
list.keyAt(i), rec.size/1024.0f,
rec.w, rec.s, rec.h, rec.format, rec.usage);
result.append(buffer);
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index fde68f6..3730739 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -691,22 +691,6 @@
}
}
-void Layer::finishPageFlip()
-{
- ClientRef::Access sharedClient(mUserClientRef);
- SharedBufferServer* lcblk(sharedClient.get());
- if (lcblk) {
- int buf = mBufferManager.getActiveBufferIndex();
- if (buf >= 0) {
- status_t err = lcblk->unlock( buf );
- LOGE_IF(err!=NO_ERROR,
- "layer %p, buffer=%d wasn't locked!",
- this, buf);
- }
- }
-}
-
-
void Layer::dump(String8& result, char* buffer, size_t SIZE) const
{
LayerBaseClient::dump(result, buffer, SIZE);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 5444d2f..2908119 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -75,7 +75,6 @@
virtual uint32_t doTransaction(uint32_t transactionFlags);
virtual void lockPageFlip(bool& recomputeVisibleRegions);
virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
- virtual void finishPageFlip();
virtual bool needsBlending() const { return mNeedsBlending; }
virtual bool needsDithering() const { return mNeedsDithering; }
virtual bool needsFiltering() const;
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 0c1fcf9..464841b 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -273,10 +273,6 @@
}
}
-void LayerBase::finishPageFlip()
-{
-}
-
void LayerBase::invalidate()
{
if ((android_atomic_or(1, &mInvalidate)&1) == 0) {
@@ -534,6 +530,12 @@
result.append(buffer);
}
+void LayerBase::shortDump(String8& result, char* scratch, size_t size) const
+{
+ LayerBase::dump(result, scratch, size);
+}
+
+
// ---------------------------------------------------------------------------
int32_t LayerBaseClient::sIdentity = 1;
@@ -585,6 +587,12 @@
result.append(buffer);
}
+
+void LayerBaseClient::shortDump(String8& result, char* scratch, size_t size) const
+{
+ LayerBaseClient::dump(result, scratch, size);
+}
+
// ---------------------------------------------------------------------------
LayerBaseClient::Surface::Surface(
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index f6c49fc..1a34f52 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -174,11 +174,6 @@
virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
/**
- * finishPageFlip - called after all surfaces have drawn.
- */
- virtual void finishPageFlip();
-
- /**
* needsBlending - true if this surface needs blending
*/
virtual bool needsBlending() const { return false; }
@@ -211,6 +206,7 @@
/** always call base class first */
virtual void dump(String8& result, char* scratch, size_t size) const;
+ virtual void shortDump(String8& result, char* scratch, size_t size) const;
enum { // flags for doTransaction()
@@ -324,6 +320,7 @@
protected:
virtual void dump(String8& result, char* scratch, size_t size) const;
+ virtual void shortDump(String8& result, char* scratch, size_t size) const;
private:
mutable Mutex mLock;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 65ad956..694af70 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -394,14 +394,10 @@
logger.log(GraphicLog::SF_SWAP_BUFFERS, index);
postFramebuffer();
- logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index);
- unlockClients();
-
logger.log(GraphicLog::SF_REPAINT_DONE, index);
} else {
// pretend we did the post
hw.compositionComplete();
- unlockClients();
usleep(16667); // 60 fps period
}
return true;
@@ -872,30 +868,36 @@
for (size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer(layers[i]);
layer->setPerFrameData(&cur[i]);
- if (cur[i].hints & HWC_HINT_CLEAR_FB) {
- if (!(layer->needsBlending())) {
- transparent.orSelf(layer->visibleRegionScreen);
- }
- }
}
err = hwc.prepare();
LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err));
- }
- /*
- * clear the area of the FB that need to be transparent
- */
- transparent.andSelf(dirty);
- if (!transparent.isEmpty()) {
- glClearColor(0,0,0,0);
- Region::const_iterator it = transparent.begin();
- Region::const_iterator const end = transparent.end();
- const int32_t height = hw.getHeight();
- while (it != end) {
- const Rect& r(*it++);
- const GLint sy = height - (r.top + r.height());
- glScissor(r.left, sy, r.width(), r.height());
- glClear(GL_COLOR_BUFFER_BIT);
+ if (err == NO_ERROR) {
+ for (size_t i=0 ; i<count ; i++) {
+ if (cur[i].hints & HWC_HINT_CLEAR_FB) {
+ const sp<LayerBase>& layer(layers[i]);
+ if (!(layer->needsBlending())) {
+ transparent.orSelf(layer->visibleRegionScreen);
+ }
+ }
+ }
+
+ /*
+ * clear the area of the FB that need to be transparent
+ */
+ transparent.andSelf(dirty);
+ if (!transparent.isEmpty()) {
+ glClearColor(0,0,0,0);
+ Region::const_iterator it = transparent.begin();
+ Region::const_iterator const end = transparent.end();
+ const int32_t height = hw.getHeight();
+ while (it != end) {
+ const Rect& r(*it++);
+ const GLint sy = height - (r.top + r.height());
+ glScissor(r.left, sy, r.width(), r.height());
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+ }
}
}
@@ -920,17 +922,6 @@
}
}
-void SurfaceFlinger::unlockClients()
-{
- const LayerVector& drawingLayers(mDrawingState.layersSortedByZ);
- const size_t count = drawingLayers.size();
- sp<LayerBase> const* const layers = drawingLayers.array();
- for (size_t i=0 ; i<count ; ++i) {
- const sp<LayerBase>& layer = layers[i];
- layer->finishPageFlip();
- }
-}
-
void SurfaceFlinger::debugFlashRegions()
{
const DisplayHardware& hw(graphicPlane(0).displayHardware());
@@ -1491,8 +1482,13 @@
result.append(buffer);
}
+ /*
+ * Dump the visible layer list
+ */
const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
const size_t count = currentLayers.size();
+ snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count);
+ result.append(buffer);
for (size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer(currentLayers[i]);
layer->dump(result, buffer, SIZE);
@@ -1502,6 +1498,24 @@
layer->visibleRegionScreen.dump(result, "visibleRegionScreen");
}
+ /*
+ * Dump the layers in the purgatory
+ */
+
+ const size_t purgatorySize = mLayerPurgatory.size();
+ snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize);
+ result.append(buffer);
+ for (size_t i=0 ; i<purgatorySize ; i++) {
+ const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i));
+ layer->shortDump(result, buffer, SIZE);
+ }
+
+ /*
+ * Dump SurfaceFlinger global state
+ */
+
+ snprintf(buffer, SIZE, "SurfaceFlinger global state\n");
+ result.append(buffer);
mWormholeRegion.dump(result, "WormholeRegion");
const DisplayHardware& hw(graphicPlane(0).displayHardware());
snprintf(buffer, SIZE,
@@ -1527,6 +1541,9 @@
result.append(buffer);
}
+ /*
+ * Dump HWComposer state
+ */
HWComposer& hwc(hw.getHwComposer());
snprintf(buffer, SIZE, " h/w composer %s and %s\n",
hwc.initCheck()==NO_ERROR ? "present" : "not present",
@@ -1534,6 +1551,9 @@
result.append(buffer);
hwc.dump(result, buffer, SIZE);
+ /*
+ * Dump gralloc state
+ */
const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
alloc.dump(result);
hw.dump(result);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index eabdc64..6dd91ac 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -314,7 +314,6 @@
void handleRepaint();
void postFramebuffer();
void composeSurfaces(const Region& dirty);
- void unlockClients();
ssize_t addClientLayer(const sp<Client>& client,
diff --git a/vpn/java/android/net/vpn/IVpnService.aidl b/vpn/java/android/net/vpn/IVpnService.aidl
index fedccb0..6bf3edd 100644
--- a/vpn/java/android/net/vpn/IVpnService.aidl
+++ b/vpn/java/android/net/vpn/IVpnService.aidl
@@ -24,10 +24,11 @@
*/
interface IVpnService {
/**
- * Sets up the VPN connection.
+ * Sets up a VPN connection.
* @param profile the profile object
* @param username the username for authentication
* @param password the corresponding password for authentication
+ * @return true if VPN is successfully connected
*/
boolean connect(in VpnProfile profile, String username, String password);
@@ -37,7 +38,13 @@
void disconnect();
/**
- * Makes the service broadcast the connectivity state.
+ * Gets the the current connection state.
*/
- void checkStatus(in VpnProfile profile);
+ String getState(in VpnProfile profile);
+
+ /**
+ * Returns the idle state.
+ * @return true if the system is not connecting/connected to a VPN
+ */
+ boolean isIdle();
}
diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java
index ce40b5d..02486bb 100644
--- a/vpn/java/android/net/vpn/VpnManager.java
+++ b/vpn/java/android/net/vpn/VpnManager.java
@@ -16,17 +16,19 @@
package android.net.vpn;
-import java.io.File;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.Log;
+import com.android.server.vpn.VpnServiceBinder;
+
/**
* The class provides interface to manage all VPN-related tasks, including:
* <ul>
@@ -40,8 +42,6 @@
* {@hide}
*/
public class VpnManager {
- // Action for broadcasting a connectivity state.
- private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
/** Key to the profile name of a connectivity broadcast event. */
public static final String BROADCAST_PROFILE_NAME = "profile_name";
/** Key to the connectivity state of a connectivity broadcast event. */
@@ -74,8 +74,10 @@
private static final String PACKAGE_PREFIX =
VpnManager.class.getPackage().getName() + ".";
- // Action to start VPN service
- private static final String ACTION_VPN_SERVICE = PACKAGE_PREFIX + "SERVICE";
+ // Action for broadcasting a connectivity state.
+ private static final String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
+
+ private static final String VPN_SERVICE_NAME = "vpn";
// Action to start VPN settings
private static final String ACTION_VPN_SETTINGS =
@@ -96,13 +98,76 @@
return VpnType.values();
}
+ public static void startVpnService(Context c) {
+ ServiceManager.addService(VPN_SERVICE_NAME, new VpnServiceBinder(c));
+ }
+
private Context mContext;
+ private IVpnService mVpnService;
/**
* Creates a manager object with the specified context.
*/
public VpnManager(Context c) {
mContext = c;
+ createVpnServiceClient();
+ }
+
+ private void createVpnServiceClient() {
+ IBinder b = ServiceManager.getService(VPN_SERVICE_NAME);
+ mVpnService = IVpnService.Stub.asInterface(b);
+ }
+
+ /**
+ * Sets up a VPN connection.
+ * @param profile the profile object
+ * @param username the username for authentication
+ * @param password the corresponding password for authentication
+ * @return true if VPN is successfully connected
+ */
+ public boolean connect(VpnProfile p, String username, String password) {
+ try {
+ return mVpnService.connect(p, username, password);
+ } catch (RemoteException e) {
+ Log.e(TAG, "connect()", e);
+ return false;
+ }
+ }
+
+ /**
+ * Tears down the VPN connection.
+ */
+ public void disconnect() {
+ try {
+ mVpnService.disconnect();
+ } catch (RemoteException e) {
+ Log.e(TAG, "disconnect()", e);
+ }
+ }
+
+ /**
+ * Gets the the current connection state.
+ */
+ public VpnState getState(VpnProfile p) {
+ try {
+ return Enum.valueOf(VpnState.class, mVpnService.getState(p));
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState()", e);
+ return VpnState.IDLE;
+ }
+ }
+
+ /**
+ * Returns the idle state.
+ * @return true if the system is not connecting/connected to a VPN
+ */
+ public boolean isIdle() {
+ try {
+ return mVpnService.isIdle();
+ } catch (RemoteException e) {
+ Log.e(TAG, "isIdle()", e);
+ return true;
+ }
}
/**
@@ -134,33 +199,6 @@
}
}
- /**
- * Starts the VPN service to establish VPN connection.
- */
- public void startVpnService() {
- mContext.startService(new Intent(ACTION_VPN_SERVICE));
- }
-
- /**
- * Stops the VPN service.
- */
- public void stopVpnService() {
- mContext.stopService(new Intent(ACTION_VPN_SERVICE));
- }
-
- /**
- * Binds the specified ServiceConnection with the VPN service.
- */
- public boolean bindVpnService(ServiceConnection c) {
- if (!mContext.bindService(new Intent(ACTION_VPN_SERVICE), c, 0)) {
- Log.w(TAG, "failed to connect to VPN service");
- return false;
- } else {
- Log.d(TAG, "succeeded to connect to VPN service");
- return true;
- }
- }
-
/** Broadcasts the connectivity state of the specified profile. */
public void broadcastConnectivity(String profileName, VpnState s) {
broadcastConnectivity(profileName, s, VPN_ERROR_NO_ERROR);
diff --git a/vpn/java/com/android/server/vpn/DaemonProxy.java b/vpn/java/com/android/server/vpn/DaemonProxy.java
new file mode 100644
index 0000000..289ee45
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/DaemonProxy.java
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.vpn.VpnManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Proxy to start, stop and interact with a VPN daemon.
+ * The daemon is expected to accept connection through Unix domain socket.
+ * When the proxy successfully starts the daemon, it will establish a socket
+ * connection with the daemon, to both send commands to the daemon and receive
+ * response and connecting error code from the daemon.
+ */
+class DaemonProxy implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private static final boolean DBG = true;
+
+ private static final int WAITING_TIME = 15; // sec
+
+ private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
+ private static final String SVC_START_CMD = "ctl.start";
+ private static final String SVC_STOP_CMD = "ctl.stop";
+ private static final String SVC_STATE_RUNNING = "running";
+ private static final String SVC_STATE_STOPPED = "stopped";
+
+ private static final int END_OF_ARGUMENTS = 255;
+
+ private String mName;
+ private String mTag;
+ private transient LocalSocket mControlSocket;
+
+ /**
+ * Creates a proxy of the specified daemon.
+ * @param daemonName name of the daemon
+ */
+ DaemonProxy(String daemonName) {
+ mName = daemonName;
+ mTag = "SProxy_" + daemonName;
+ }
+
+ String getName() {
+ return mName;
+ }
+
+ void start() throws IOException {
+ String svc = mName;
+
+ Log.i(mTag, "Start VPN daemon: " + svc);
+ SystemProperties.set(SVC_START_CMD, svc);
+
+ if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
+ throw new IOException("cannot start service: " + svc);
+ } else {
+ mControlSocket = createServiceSocket();
+ }
+ }
+
+ void sendCommand(String ...args) throws IOException {
+ OutputStream out = getControlSocketOutput();
+ for (String arg : args) outputString(out, arg);
+ out.write(END_OF_ARGUMENTS);
+ out.flush();
+
+ int result = getResultFromSocket(true);
+ if (result != args.length) {
+ throw new IOException("socket error, result from service: "
+ + result);
+ }
+ }
+
+ // returns 0 if nothing is in the receive buffer
+ int getResultFromSocket() throws IOException {
+ return getResultFromSocket(false);
+ }
+
+ void closeControlSocket() {
+ if (mControlSocket == null) return;
+ try {
+ mControlSocket.close();
+ } catch (IOException e) {
+ Log.w(mTag, "close control socket", e);
+ } finally {
+ mControlSocket = null;
+ }
+ }
+
+ void stop() {
+ String svc = mName;
+ Log.i(mTag, "Stop VPN daemon: " + svc);
+ SystemProperties.set(SVC_STOP_CMD, svc);
+ boolean success = blockUntil(SVC_STATE_STOPPED, 5);
+ if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
+ }
+
+ boolean isStopped() {
+ String cmd = SVC_STATE_CMD_PREFIX + mName;
+ return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd));
+ }
+
+ private int getResultFromSocket(boolean blocking) throws IOException {
+ LocalSocket s = mControlSocket;
+ if (s == null) return 0;
+ InputStream in = s.getInputStream();
+ if (!blocking && in.available() == 0) return 0;
+
+ int data = in.read();
+ Log.i(mTag, "got data from control socket: " + data);
+
+ return data;
+ }
+
+ private LocalSocket createServiceSocket() throws IOException {
+ LocalSocket s = new LocalSocket();
+ LocalSocketAddress a = new LocalSocketAddress(mName,
+ LocalSocketAddress.Namespace.RESERVED);
+
+ // try a few times in case the service has not listen()ed
+ IOException excp = null;
+ for (int i = 0; i < 10; i++) {
+ try {
+ s.connect(a);
+ return s;
+ } catch (IOException e) {
+ if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
+ excp = e;
+ sleep(500);
+ }
+ }
+ throw excp;
+ }
+
+ private OutputStream getControlSocketOutput() throws IOException {
+ if (mControlSocket != null) {
+ return mControlSocket.getOutputStream();
+ } else {
+ throw new IOException("no control socket available");
+ }
+ }
+
+ /**
+ * Waits for the process to be in the expected state. The method returns
+ * false if after the specified duration (in seconds), the process is still
+ * not in the expected state.
+ */
+ private boolean blockUntil(String expectedState, int waitTime) {
+ String cmd = SVC_STATE_CMD_PREFIX + mName;
+ int sleepTime = 200; // ms
+ int n = waitTime * 1000 / sleepTime;
+ for (int i = 0; i < n; i++) {
+ if (expectedState.equals(SystemProperties.get(cmd))) {
+ if (DBG) {
+ Log.d(mTag, mName + " is " + expectedState + " after "
+ + (i * sleepTime) + " msec");
+ }
+ break;
+ }
+ sleep(sleepTime);
+ }
+ return expectedState.equals(SystemProperties.get(cmd));
+ }
+
+ private void outputString(OutputStream out, String s) throws IOException {
+ byte[] bytes = s.getBytes();
+ out.write(bytes.length);
+ out.write(bytes);
+ out.flush();
+ }
+
+ private void sleep(int msec) {
+ try {
+ Thread.currentThread().sleep(msec);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java b/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java
new file mode 100644
index 0000000..50e0de1
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/L2tpIpsecPskService.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.net.vpn.L2tpIpsecPskProfile;
+
+import java.io.IOException;
+
+/**
+ * The service that manages the preshared key based L2TP-over-IPSec VPN
+ * connection.
+ */
+class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
+ private static final String IPSEC = "racoon";
+
+ @Override
+ protected void connect(String serverIp, String username, String password)
+ throws IOException {
+ L2tpIpsecPskProfile p = getProfile();
+ VpnDaemons daemons = getDaemons();
+
+ // IPSEC
+ daemons.startIpsecForL2tp(serverIp, p.getPresharedKey())
+ .closeControlSocket();
+
+ sleep(2000); // 2 seconds
+
+ // L2TP
+ daemons.startL2tp(serverIp,
+ (p.isSecretEnabled() ? p.getSecretString() : null),
+ username, password);
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/L2tpIpsecService.java b/vpn/java/com/android/server/vpn/L2tpIpsecService.java
new file mode 100644
index 0000000..663b0e8
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/L2tpIpsecService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.net.vpn.L2tpIpsecProfile;
+import android.security.Credentials;
+
+import java.io.IOException;
+
+/**
+ * The service that manages the certificate based L2TP-over-IPSec VPN connection.
+ */
+class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
+ private static final String IPSEC = "racoon";
+
+ @Override
+ protected void connect(String serverIp, String username, String password)
+ throws IOException {
+ L2tpIpsecProfile p = getProfile();
+ VpnDaemons daemons = getDaemons();
+
+ // IPSEC
+ DaemonProxy ipsec = daemons.startIpsecForL2tp(serverIp,
+ Credentials.USER_PRIVATE_KEY + p.getUserCertificate(),
+ Credentials.USER_CERTIFICATE + p.getUserCertificate(),
+ Credentials.CA_CERTIFICATE + p.getCaCertificate());
+ ipsec.closeControlSocket();
+
+ sleep(2000); // 2 seconds
+
+ // L2TP
+ daemons.startL2tp(serverIp,
+ (p.isSecretEnabled() ? p.getSecretString() : null),
+ username, password);
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/L2tpService.java b/vpn/java/com/android/server/vpn/L2tpService.java
new file mode 100644
index 0000000..784a366
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/L2tpService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.net.vpn.L2tpProfile;
+
+import java.io.IOException;
+
+/**
+ * The service that manages the L2TP VPN connection.
+ */
+class L2tpService extends VpnService<L2tpProfile> {
+ @Override
+ protected void connect(String serverIp, String username, String password)
+ throws IOException {
+ L2tpProfile p = getProfile();
+ getDaemons().startL2tp(serverIp,
+ (p.isSecretEnabled() ? p.getSecretString() : null),
+ username, password);
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/PptpService.java b/vpn/java/com/android/server/vpn/PptpService.java
new file mode 100644
index 0000000..de12710
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/PptpService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.net.vpn.PptpProfile;
+
+import java.io.IOException;
+
+/**
+ * The service that manages the PPTP VPN connection.
+ */
+class PptpService extends VpnService<PptpProfile> {
+ @Override
+ protected void connect(String serverIp, String username, String password)
+ throws IOException {
+ PptpProfile p = getProfile();
+ getDaemons().startPptp(serverIp, username, password,
+ p.isEncryptionEnabled());
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/VpnConnectingError.java b/vpn/java/com/android/server/vpn/VpnConnectingError.java
new file mode 100644
index 0000000..3c4ec7d
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/VpnConnectingError.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when a connecting attempt fails.
+ */
+class VpnConnectingError extends IOException {
+ private int mErrorCode;
+
+ VpnConnectingError(int errorCode) {
+ super("Connecting error: " + errorCode);
+ mErrorCode = errorCode;
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/VpnDaemons.java b/vpn/java/com/android/server/vpn/VpnDaemons.java
new file mode 100644
index 0000000..499195f
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/VpnDaemons.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A helper class for managing native VPN daemons.
+ */
+class VpnDaemons implements Serializable {
+ static final long serialVersionUID = 1L;
+ private final String TAG = VpnDaemons.class.getSimpleName();
+
+ private static final String MTPD = "mtpd";
+ private static final String IPSEC = "racoon";
+
+ private static final String L2TP = "l2tp";
+ private static final String L2TP_PORT = "1701";
+
+ private static final String PPTP = "pptp";
+ private static final String PPTP_PORT = "1723";
+
+ private static final String VPN_LINKNAME = "vpn";
+ private static final String PPP_ARGS_SEPARATOR = "";
+
+ private List<DaemonProxy> mDaemonList = new ArrayList<DaemonProxy>();
+
+ public DaemonProxy startL2tp(String serverIp, String secret,
+ String username, String password) throws IOException {
+ return startMtpd(L2TP, serverIp, L2TP_PORT, secret, username, password,
+ false);
+ }
+
+ public DaemonProxy startPptp(String serverIp, String username,
+ String password, boolean encryption) throws IOException {
+ return startMtpd(PPTP, serverIp, PPTP_PORT, null, username, password,
+ encryption);
+ }
+
+ public DaemonProxy startIpsecForL2tp(String serverIp, String pskKey)
+ throws IOException {
+ DaemonProxy ipsec = startDaemon(IPSEC);
+ ipsec.sendCommand(serverIp, L2TP_PORT, pskKey);
+ return ipsec;
+ }
+
+ public DaemonProxy startIpsecForL2tp(String serverIp, String userKeyKey,
+ String userCertKey, String caCertKey) throws IOException {
+ DaemonProxy ipsec = startDaemon(IPSEC);
+ ipsec.sendCommand(serverIp, L2TP_PORT, userKeyKey, userCertKey,
+ caCertKey);
+ return ipsec;
+ }
+
+ public synchronized void stopAll() {
+ new DaemonProxy(MTPD).stop();
+ new DaemonProxy(IPSEC).stop();
+ }
+
+ public synchronized void closeSockets() {
+ for (DaemonProxy s : mDaemonList) s.closeControlSocket();
+ }
+
+ public synchronized boolean anyDaemonStopped() {
+ for (DaemonProxy s : mDaemonList) {
+ if (s.isStopped()) {
+ Log.w(TAG, " VPN daemon gone: " + s.getName());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized int getSocketError() {
+ for (DaemonProxy s : mDaemonList) {
+ int errCode = getResultFromSocket(s);
+ if (errCode != 0) return errCode;
+ }
+ return 0;
+ }
+
+ private synchronized DaemonProxy startDaemon(String daemonName)
+ throws IOException {
+ DaemonProxy daemon = new DaemonProxy(daemonName);
+ mDaemonList.add(daemon);
+ daemon.start();
+ return daemon;
+ }
+
+ private int getResultFromSocket(DaemonProxy s) {
+ try {
+ return s.getResultFromSocket();
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ private DaemonProxy startMtpd(String protocol,
+ String serverIp, String port, String secret, String username,
+ String password, boolean encryption) throws IOException {
+ ArrayList<String> args = new ArrayList<String>();
+ args.addAll(Arrays.asList(protocol, serverIp, port));
+ if (secret != null) args.add(secret);
+ args.add(PPP_ARGS_SEPARATOR);
+ addPppArguments(args, serverIp, username, password, encryption);
+
+ DaemonProxy mtpd = startDaemon(MTPD);
+ mtpd.sendCommand(args.toArray(new String[args.size()]));
+ return mtpd;
+ }
+
+ private static void addPppArguments(ArrayList<String> args, String serverIp,
+ String username, String password, boolean encryption)
+ throws IOException {
+ args.addAll(Arrays.asList(
+ "linkname", VPN_LINKNAME,
+ "name", username,
+ "password", password,
+ "refuse-eap", "nodefaultroute", "usepeerdns",
+ "idle", "1800",
+ "mtu", "1400",
+ "mru", "1400"));
+ if (encryption) {
+ args.add("+mppe");
+ }
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/VpnService.java b/vpn/java/com/android/server/vpn/VpnService.java
new file mode 100644
index 0000000..4966c06
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/VpnService.java
@@ -0,0 +1,477 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.net.vpn.VpnManager;
+import android.net.vpn.VpnProfile;
+import android.net.vpn.VpnState;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+
+/**
+ * The service base class for managing a type of VPN connection.
+ */
+abstract class VpnService<E extends VpnProfile> {
+ private static final boolean DBG = true;
+ private static final int NOTIFICATION_ID = 1;
+
+ private static final String DNS1 = "net.dns1";
+ private static final String DNS2 = "net.dns2";
+ private static final String VPN_DNS1 = "vpn.dns1";
+ private static final String VPN_DNS2 = "vpn.dns2";
+ private static final String VPN_STATUS = "vpn.status";
+ private static final String VPN_IS_UP = "ok";
+ private static final String VPN_IS_DOWN = "down";
+
+ private static final String REMOTE_IP = "net.ipremote";
+ private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
+
+ private final String TAG = VpnService.class.getSimpleName();
+
+ E mProfile;
+ transient Context mContext;
+
+ private VpnState mState = VpnState.IDLE;
+ private Throwable mError;
+
+ // connection settings
+ private String mOriginalDns1;
+ private String mOriginalDns2;
+ private String mOriginalDomainSuffices;
+ private String mLocalIp;
+ private String mLocalIf;
+
+ private long mStartTime; // VPN connection start time
+
+ // for helping managing daemons
+ private VpnDaemons mDaemons = new VpnDaemons();
+
+ // for helping showing, updating notification
+ private transient NotificationHelper mNotification;
+
+ /**
+ * Establishes a VPN connection with the specified username and password.
+ */
+ protected abstract void connect(String serverIp, String username,
+ String password) throws IOException;
+
+ /**
+ * Returns the daemons management class for this service object.
+ */
+ protected VpnDaemons getDaemons() {
+ return mDaemons;
+ }
+
+ /**
+ * Returns the VPN profile associated with the connection.
+ */
+ protected E getProfile() {
+ return mProfile;
+ }
+
+ /**
+ * Returns the IP address of the specified host name.
+ */
+ protected String getIp(String hostName) throws IOException {
+ return InetAddress.getByName(hostName).getHostAddress();
+ }
+
+ void setContext(Context context, E profile) {
+ mProfile = profile;
+ mContext = context;
+ mNotification = new NotificationHelper();
+
+ if (VpnState.CONNECTED.equals(mState)) {
+ Log.i("VpnService", " recovered: " + mProfile.getName());
+ startConnectivityMonitor();
+ }
+ }
+
+ VpnState getState() {
+ return mState;
+ }
+
+ boolean isIdle() {
+ return (mState == VpnState.IDLE);
+ }
+
+ synchronized boolean onConnect(String username, String password) {
+ try {
+ setState(VpnState.CONNECTING);
+
+ mDaemons.stopAll();
+ String serverIp = getIp(getProfile().getServerName());
+ saveLocalIpAndInterface(serverIp);
+ onBeforeConnect();
+ connect(serverIp, username, password);
+ waitUntilConnectedOrTimedout();
+ return true;
+ } catch (Throwable e) {
+ onError(e);
+ return false;
+ }
+ }
+
+ synchronized void onDisconnect() {
+ try {
+ Log.i(TAG, "disconnecting VPN...");
+ setState(VpnState.DISCONNECTING);
+ mNotification.showDisconnect();
+
+ mDaemons.stopAll();
+ } catch (Throwable e) {
+ Log.e(TAG, "onDisconnect()", e);
+ } finally {
+ onFinalCleanUp();
+ }
+ }
+
+ private void onError(Throwable error) {
+ // error may occur during or after connection setup
+ // and it may be due to one or all services gone
+ if (mError != null) {
+ Log.w(TAG, " multiple errors occur, record the last one: "
+ + error);
+ }
+ Log.e(TAG, "onError()", error);
+ mError = error;
+ onDisconnect();
+ }
+
+ private void onError(int errorCode) {
+ onError(new VpnConnectingError(errorCode));
+ }
+
+
+ private void onBeforeConnect() throws IOException {
+ mNotification.disableNotification();
+
+ SystemProperties.set(VPN_DNS1, "");
+ SystemProperties.set(VPN_DNS2, "");
+ SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
+ if (DBG) {
+ Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS));
+ }
+ }
+
+ private void waitUntilConnectedOrTimedout() throws IOException {
+ sleep(2000); // 2 seconds
+ for (int i = 0; i < 80; i++) {
+ if (mState != VpnState.CONNECTING) {
+ break;
+ } else if (VPN_IS_UP.equals(
+ SystemProperties.get(VPN_STATUS))) {
+ onConnected();
+ return;
+ } else {
+ int err = mDaemons.getSocketError();
+ if (err != 0) {
+ onError(err);
+ return;
+ }
+ }
+ sleep(500); // 0.5 second
+ }
+
+ if (mState == VpnState.CONNECTING) {
+ onError(new IOException("Connecting timed out"));
+ }
+ }
+
+ private synchronized void onConnected() throws IOException {
+ if (DBG) Log.d(TAG, "onConnected()");
+
+ mDaemons.closeSockets();
+ saveOriginalDns();
+ saveAndSetDomainSuffices();
+
+ mStartTime = System.currentTimeMillis();
+
+ setState(VpnState.CONNECTED);
+ setVpnDns();
+
+ startConnectivityMonitor();
+ }
+
+ private synchronized void onFinalCleanUp() {
+ if (DBG) Log.d(TAG, "onFinalCleanUp()");
+
+ if (mState == VpnState.IDLE) return;
+
+ // keep the notification when error occurs
+ if (!anyError()) mNotification.disableNotification();
+
+ restoreOriginalDns();
+ restoreOriginalDomainSuffices();
+ setState(VpnState.IDLE);
+
+ SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
+ }
+
+ private boolean anyError() {
+ return (mError != null);
+ }
+
+ private void restoreOriginalDns() {
+ // restore only if they are not overridden
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ if (vpnDns1.equals(SystemProperties.get(DNS1))) {
+ Log.i(TAG, String.format("restore original dns prop: %s --> %s",
+ SystemProperties.get(DNS1), mOriginalDns1));
+ Log.i(TAG, String.format("restore original dns prop: %s --> %s",
+ SystemProperties.get(DNS2), mOriginalDns2));
+ SystemProperties.set(DNS1, mOriginalDns1);
+ SystemProperties.set(DNS2, mOriginalDns2);
+ }
+ }
+
+ private void saveOriginalDns() {
+ mOriginalDns1 = SystemProperties.get(DNS1);
+ mOriginalDns2 = SystemProperties.get(DNS2);
+ Log.i(TAG, String.format("save original dns prop: %s, %s",
+ mOriginalDns1, mOriginalDns2));
+ }
+
+ private void setVpnDns() {
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ String vpnDns2 = SystemProperties.get(VPN_DNS2);
+ SystemProperties.set(DNS1, vpnDns1);
+ SystemProperties.set(DNS2, vpnDns2);
+ Log.i(TAG, String.format("set vpn dns prop: %s, %s",
+ vpnDns1, vpnDns2));
+ }
+
+ private void saveAndSetDomainSuffices() {
+ mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
+ Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices);
+ String list = mProfile.getDomainSuffices();
+ if (!TextUtils.isEmpty(list)) {
+ SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
+ }
+ }
+
+ private void restoreOriginalDomainSuffices() {
+ Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
+ SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
+ }
+
+ private void setState(VpnState newState) {
+ mState = newState;
+ broadcastConnectivity(newState);
+ }
+
+ private void broadcastConnectivity(VpnState s) {
+ VpnManager m = new VpnManager(mContext);
+ Throwable err = mError;
+ if ((s == VpnState.IDLE) && (err != null)) {
+ if (err instanceof UnknownHostException) {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ VpnManager.VPN_ERROR_UNKNOWN_SERVER);
+ } else if (err instanceof VpnConnectingError) {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ ((VpnConnectingError) err).getErrorCode());
+ } else if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ VpnManager.VPN_ERROR_CONNECTION_LOST);
+ } else {
+ m.broadcastConnectivity(mProfile.getName(), s,
+ VpnManager.VPN_ERROR_CONNECTION_FAILED);
+ }
+ } else {
+ m.broadcastConnectivity(mProfile.getName(), s);
+ }
+ }
+
+ private void startConnectivityMonitor() {
+ new Thread(new Runnable() {
+ public void run() {
+ Log.i(TAG, "VPN connectivity monitor running");
+ try {
+ mNotification.update(mStartTime); // to pop up notification
+ for (int i = 10; ; i--) {
+ long now = System.currentTimeMillis();
+
+ boolean heavyCheck = i == 0;
+ synchronized (VpnService.this) {
+ if (mState != VpnState.CONNECTED) break;
+ mNotification.update(now);
+
+ if (heavyCheck) {
+ i = 10;
+ if (checkConnectivity()) checkDns();
+ }
+ long t = 1000L - System.currentTimeMillis() + now;
+ if (t > 100L) VpnService.this.wait(t);
+ }
+ }
+ } catch (InterruptedException e) {
+ onError(e);
+ }
+ Log.i(TAG, "VPN connectivity monitor stopped");
+ }
+ }).start();
+ }
+
+ private void saveLocalIpAndInterface(String serverIp) throws IOException {
+ DatagramSocket s = new DatagramSocket();
+ int port = 80; // arbitrary
+ s.connect(InetAddress.getByName(serverIp), port);
+ InetAddress localIp = s.getLocalAddress();
+ mLocalIp = localIp.getHostAddress();
+ NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp);
+ mLocalIf = (localIf == null) ? null : localIf.getName();
+ if (TextUtils.isEmpty(mLocalIf)) {
+ throw new IOException("Local interface is empty!");
+ }
+ if (DBG) {
+ Log.d(TAG, " Local IP: " + mLocalIp + ", if: " + mLocalIf);
+ }
+ }
+
+ // returns false if vpn connectivity is broken
+ private boolean checkConnectivity() {
+ if (mDaemons.anyDaemonStopped() || isLocalIpChanged()) {
+ onError(new IOException("Connectivity lost"));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private void checkDns() {
+ String dns1 = SystemProperties.get(DNS1);
+ String vpnDns1 = SystemProperties.get(VPN_DNS1);
+ if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) {
+ // dhcp expires?
+ setVpnDns();
+ }
+ }
+
+ private boolean isLocalIpChanged() {
+ try {
+ InetAddress localIp = InetAddress.getByName(mLocalIp);
+ NetworkInterface localIf =
+ NetworkInterface.getByInetAddress(localIp);
+ if (localIf == null || !mLocalIf.equals(localIf.getName())) {
+ Log.w(TAG, " local If changed from " + mLocalIf
+ + " to " + localIf);
+ return true;
+ } else {
+ return false;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "isLocalIpChanged()", e);
+ return true;
+ }
+ }
+
+ protected void sleep(int ms) {
+ try {
+ Thread.currentThread().sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Helper class for showing, updating notification.
+ private class NotificationHelper {
+ private NotificationManager mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ private Notification mNotification =
+ new Notification(R.drawable.vpn_connected, null, 0L);
+ private PendingIntent mPendingIntent = PendingIntent.getActivity(
+ mContext, 0,
+ new VpnManager(mContext).createSettingsActivityIntent(), 0);
+ private String mConnectedTitle;
+
+ void update(long now) {
+ Notification n = mNotification;
+ if (now == mStartTime) {
+ // to pop up the notification for the first time
+ n.when = mStartTime;
+ n.tickerText = mConnectedTitle = getNotificationTitle(true);
+ } else {
+ n.tickerText = null;
+ }
+ n.setLatestEventInfo(mContext, mConnectedTitle,
+ getConnectedNotificationMessage(now),
+ mPendingIntent);
+ n.flags |= Notification.FLAG_NO_CLEAR;
+ n.flags |= Notification.FLAG_ONGOING_EVENT;
+ enableNotification(n);
+ }
+
+ void showDisconnect() {
+ String title = getNotificationTitle(false);
+ Notification n = new Notification(R.drawable.vpn_disconnected,
+ title, System.currentTimeMillis());
+ n.setLatestEventInfo(mContext, title,
+ getDisconnectedNotificationMessage(),
+ mPendingIntent);
+ n.flags |= Notification.FLAG_AUTO_CANCEL;
+ disableNotification();
+ enableNotification(n);
+ }
+
+ void disableNotification() {
+ mNotificationManager.cancel(NOTIFICATION_ID);
+ }
+
+ private void enableNotification(Notification n) {
+ mNotificationManager.notify(NOTIFICATION_ID, n);
+ }
+
+ private String getNotificationTitle(boolean connected) {
+ String formatString = connected
+ ? mContext.getString(
+ R.string.vpn_notification_title_connected)
+ : mContext.getString(
+ R.string.vpn_notification_title_disconnected);
+ return String.format(formatString, mProfile.getName());
+ }
+
+ private String getFormattedTime(int duration) {
+ int hours = duration / 3600;
+ StringBuilder sb = new StringBuilder();
+ if (hours > 0) sb.append(hours).append(':');
+ sb.append(String.format("%02d:%02d", (duration % 3600 / 60),
+ (duration % 60)));
+ return sb.toString();
+ }
+
+ private String getConnectedNotificationMessage(long now) {
+ return getFormattedTime((int) (now - mStartTime) / 1000);
+ }
+
+ private String getDisconnectedNotificationMessage() {
+ return mContext.getString(
+ R.string.vpn_notification_hint_disconnected);
+ }
+ }
+}
diff --git a/vpn/java/com/android/server/vpn/VpnServiceBinder.java b/vpn/java/com/android/server/vpn/VpnServiceBinder.java
new file mode 100644
index 0000000..c474ff9
--- /dev/null
+++ b/vpn/java/com/android/server/vpn/VpnServiceBinder.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vpn;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.vpn.IVpnService;
+import android.net.vpn.L2tpIpsecProfile;
+import android.net.vpn.L2tpIpsecPskProfile;
+import android.net.vpn.L2tpProfile;
+import android.net.vpn.PptpProfile;
+import android.net.vpn.VpnManager;
+import android.net.vpn.VpnProfile;
+import android.net.vpn.VpnState;
+import android.util.Log;
+
+/**
+ * The service class for managing a VPN connection. It implements the
+ * {@link IVpnService} binder interface.
+ */
+public class VpnServiceBinder extends IVpnService.Stub {
+ private static final String TAG = VpnServiceBinder.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ // The actual implementation is delegated to the VpnService class.
+ private VpnService<? extends VpnProfile> mService;
+
+ private Context mContext;
+
+ public VpnServiceBinder(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public synchronized boolean connect(VpnProfile p, final String username,
+ final String password) {
+ if ((mService != null) && !mService.isIdle()) return false;
+ final VpnService s = mService = createService(p);
+
+ new Thread(new Runnable() {
+ public void run() {
+ s.onConnect(username, password);
+ }
+ }).start();
+ return true;
+ }
+
+ @Override
+ public synchronized void disconnect() {
+ if (mService == null) return;
+ final VpnService s = mService;
+ mService = null;
+
+ new Thread(new Runnable() {
+ public void run() {
+ s.onDisconnect();
+ }
+ }).start();
+ }
+
+ @Override
+ public synchronized String getState(VpnProfile p) {
+ if ((mService == null)
+ || (!p.getName().equals(mService.mProfile.getName()))) {
+ return VpnState.IDLE.toString();
+ } else {
+ return mService.getState().toString();
+ }
+ }
+
+ @Override
+ public synchronized boolean isIdle() {
+ return (mService == null || mService.isIdle());
+ }
+
+ private VpnService<? extends VpnProfile> createService(VpnProfile p) {
+ switch (p.getType()) {
+ case L2TP:
+ L2tpService l2tp = new L2tpService();
+ l2tp.setContext(mContext, (L2tpProfile) p);
+ return l2tp;
+
+ case PPTP:
+ PptpService pptp = new PptpService();
+ pptp.setContext(mContext, (PptpProfile) p);
+ return pptp;
+
+ case L2TP_IPSEC_PSK:
+ L2tpIpsecPskService psk = new L2tpIpsecPskService();
+ psk.setContext(mContext, (L2tpIpsecPskProfile) p);
+ return psk;
+
+ case L2TP_IPSEC:
+ L2tpIpsecService l2tpIpsec = new L2tpIpsecService();
+ l2tpIpsec.setContext(mContext, (L2tpIpsecProfile) p);
+ return l2tpIpsec;
+
+ default:
+ return null;
+ }
+ }
+}