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;
+        }
+    }
+}