Add state saving mechanism to support proc restart

Also...
+ stop daemons before getting server IP;
+ remove setForeground();
+ add the DBG flag for Log.d calls.

PatchSet 3:
+ add CHALLENGE_ERROR and REMOTE_HUNG_UP to VpnManager
+ broadcast new error codes in VpnService
+ check local IP change instead of dns change
+ move removeStates() to VpnService.onFinalCleanUp()

PatchSet 7:
+ add encryption flag to PptpProfile
+ PptpService and MtpdHelper are revised accordingly
diff --git a/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java b/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
index b749821..289ee45 100644
--- a/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
+++ b/packages/VpnServices/src/com/android/server/vpn/DaemonProxy.java
@@ -25,6 +25,7 @@
 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.
@@ -33,7 +34,10 @@
  * connection with the daemon, to both send commands to the daemon and receive
  * response and connecting error code from the daemon.
  */
-class DaemonProxy {
+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.";
@@ -45,8 +49,8 @@
     private static final int END_OF_ARGUMENTS = 255;
 
     private String mName;
-    private LocalSocket mControlSocket;
     private String mTag;
+    private transient LocalSocket mControlSocket;
 
     /**
      * Creates a proxy of the specified daemon.
@@ -63,14 +67,8 @@
 
     void start() throws IOException {
         String svc = mName;
-        Log.d(mTag, "-----  Stop the daemon just in case: " + mName);
-        SystemProperties.set(SVC_STOP_CMD, mName);
-        if (!blockUntil(SVC_STATE_STOPPED, 5)) {
-            throw new IOException("cannot start service anew: " + svc
-                    + ", it is still running");
-        }
 
-        Log.d(mTag, "+++++  Start: " + svc);
+        Log.i(mTag, "Start VPN daemon: " + svc);
         SystemProperties.set(SVC_START_CMD, svc);
 
         if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
@@ -103,7 +101,7 @@
         try {
             mControlSocket.close();
         } catch (IOException e) {
-            Log.e(mTag, "close control socket", e);
+            Log.w(mTag, "close control socket", e);
         } finally {
             mControlSocket = null;
         }
@@ -111,10 +109,10 @@
 
     void stop() {
         String svc = mName;
-        Log.d(mTag, "-----  Stop: " + svc);
+        Log.i(mTag, "Stop VPN daemon: " + svc);
         SystemProperties.set(SVC_STOP_CMD, svc);
         boolean success = blockUntil(SVC_STATE_STOPPED, 5);
-        Log.d(mTag, "stopping " + svc + ", success? " + success);
+        if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
     }
 
     boolean isStopped() {
@@ -129,7 +127,7 @@
         if (!blocking && in.available() == 0) return 0;
 
         int data = in.read();
-        Log.d(mTag, "got data from control socket: " + data);
+        Log.i(mTag, "got data from control socket: " + data);
 
         return data;
     }
@@ -146,7 +144,7 @@
                 s.connect(a);
                 return s;
             } catch (IOException e) {
-                Log.d(mTag, "service not yet listen()ing; try again");
+                if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
                 excp = e;
                 sleep(500);
             }
@@ -173,8 +171,10 @@
         int n = waitTime * 1000 / sleepTime;
         for (int i = 0; i < n; i++) {
             if (expectedState.equals(SystemProperties.get(cmd))) {
-                Log.d(mTag, mName + " is " + expectedState + " after "
-                        + (i * sleepTime) + " msec");
+                if (DBG) {
+                    Log.d(mTag, mName + " is " + expectedState + " after "
+                            + (i * sleepTime) + " msec");
+                }
                 break;
             }
             sleep(sleepTime);
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
index 8efd7c4..7910f4a 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
@@ -45,4 +45,10 @@
                 (p.isSecretEnabled() ? p.getSecretString() : null),
                 username, password);
     }
+
+    @Override
+    protected void stopPreviouslyRunDaemons() {
+        stopDaemon(IPSEC);
+        stopDaemon(MtpdHelper.MTPD);
+    }
 }
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
index 56694b6..13b4952 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
@@ -46,6 +46,12 @@
                 username, password);
     }
 
+    @Override
+    protected void stopPreviouslyRunDaemons() {
+        stopDaemon(IPSEC);
+        stopDaemon(MtpdHelper.MTPD);
+    }
+
     private String getCaCertPath() {
         return CertTool.getInstance().getCaCertificate(
                 getProfile().getCaCertificate());
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
index 9273f35..d658a36 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
@@ -35,4 +35,9 @@
                 (p.isSecretEnabled() ? p.getSecretString() : null),
                 username, password);
     }
+
+    @Override
+    protected void stopPreviouslyRunDaemons() {
+        stopDaemon(MtpdHelper.MTPD);
+    }
 }
diff --git a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
index 805a5b5..9078d9b 100644
--- a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
+++ b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
@@ -24,26 +24,33 @@
  * A helper class for sending commands to the MTP daemon (mtpd).
  */
 class MtpdHelper {
-    private static final String MTPD = "mtpd";
+    static final String MTPD = "mtpd";
     private static final String VPN_LINKNAME = "vpn";
     private static final String PPP_ARGS_SEPARATOR = "";
 
     static void sendCommand(VpnService<?> vpnService, String protocol,
             String serverIp, String port, String secret, String username,
             String password) throws IOException {
+        sendCommand(vpnService, protocol, serverIp, port, secret, username,
+                password, false);
+    }
+
+    static void sendCommand(VpnService<?> vpnService, 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(vpnService, args, serverIp, username, password);
+        addPppArguments(args, serverIp, username, password, encryption);
 
         DaemonProxy mtpd = vpnService.startDaemon(MTPD);
         mtpd.sendCommand(args.toArray(new String[args.size()]));
     }
 
-    private static void addPppArguments(VpnService<?> vpnService,
-            ArrayList<String> args, String serverIp, String username,
-            String password) throws IOException {
+    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,
@@ -52,6 +59,9 @@
                 "idle", "1800",
                 "mtu", "1400",
                 "mru", "1400"));
+        if (encryption) {
+            args.add("+mppe");
+        }
     }
 
     private MtpdHelper() {
diff --git a/packages/VpnServices/src/com/android/server/vpn/PptpService.java b/packages/VpnServices/src/com/android/server/vpn/PptpService.java
index 01362a5..d903d1b 100644
--- a/packages/VpnServices/src/com/android/server/vpn/PptpService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/PptpService.java
@@ -26,11 +26,17 @@
 class PptpService extends VpnService<PptpProfile> {
     static final String PPTP_DAEMON = "pptp";
     static final String PPTP_PORT = "1723";
+
     @Override
     protected void connect(String serverIp, String username, String password)
             throws IOException {
+        PptpProfile p = getProfile();
         MtpdHelper.sendCommand(this, PPTP_DAEMON, serverIp, PPTP_PORT, null,
-                username, password);
+                username, password, p.isEncryptionEnabled());
     }
 
+    @Override
+    protected void stopPreviouslyRunDaemons() {
+        stopDaemon(MtpdHelper.MTPD);
+    }
 }
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 60a07d5..b107c7d 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -28,7 +28,11 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.io.Serializable;
+import java.net.DatagramSocket;
+import java.net.Socket;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
@@ -36,7 +40,9 @@
 /**
  * The service base class for managing a type of VPN connection.
  */
-abstract class VpnService<E extends VpnProfile> {
+abstract class VpnService<E extends VpnProfile> implements Serializable {
+    protected static final long serialVersionUID = 1L;
+    private static final boolean DBG = true;
     private static final int NOTIFICATION_ID = 1;
 
     private static final String DNS1 = "net.dns1";
@@ -50,12 +56,16 @@
     private static final String REMOTE_IP = "net.ipremote";
     private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
 
+    private static final int CHALLENGE_ERROR_CODE = 5;
+    private static final int REMOTE_HUNG_UP_ERROR_CODE = 7;
     private static final int AUTH_ERROR_CODE = 51;
 
     private final String TAG = VpnService.class.getSimpleName();
 
+    // FIXME: profile is only needed in connecting phase, so we can just save
+    // the profile name and service class name for recovery
     E mProfile;
-    VpnServiceBinder mContext;
+    transient VpnServiceBinder mContext;
 
     private VpnState mState = VpnState.IDLE;
     private Throwable mError;
@@ -63,9 +73,9 @@
     // connection settings
     private String mOriginalDns1;
     private String mOriginalDns2;
-    private String mVpnDns1 = "";
-    private String mVpnDns2 = "";
     private String mOriginalDomainSuffices;
+    private String mLocalIp;
+    private String mLocalIf;
 
     private long mStartTime; // VPN connection start time
 
@@ -73,7 +83,7 @@
     private DaemonHelper mDaemonHelper = new DaemonHelper();
 
     // for helping showing, updating notification
-    private NotificationHelper mNotification = new NotificationHelper();
+    private transient NotificationHelper mNotification;
 
     /**
      * Establishes a VPN connection with the specified username and password.
@@ -81,6 +91,8 @@
     protected abstract void connect(String serverIp, String username,
             String password) throws IOException;
 
+    protected abstract void stopPreviouslyRunDaemons();
+
     /**
      * Starts a VPN daemon.
      */
@@ -90,6 +102,13 @@
     }
 
     /**
+     * Stops a VPN daemon.
+     */
+    protected void stopDaemon(String daemonName) {
+        new DaemonProxy(daemonName).stop();
+    }
+
+    /**
      * Returns the VPN profile associated with the connection.
      */
     protected E getProfile() {
@@ -104,8 +123,22 @@
     }
 
     void setContext(VpnServiceBinder context, E profile) {
-        mContext = context;
         mProfile = profile;
+        recover(context);
+    }
+
+    void recover(VpnServiceBinder context) {
+        mContext = context;
+        mNotification = new NotificationHelper();
+
+        if (VpnState.CONNECTED.equals(mState)) {
+            Log.i("VpnService", "     recovered: " + mProfile.getName());
+            new Thread(new Runnable() {
+                public void run() {
+                    enterConnectivityLoop();
+                }
+            }).start();
+        }
     }
 
     VpnState getState() {
@@ -117,14 +150,14 @@
             mState = VpnState.CONNECTING;
             broadcastConnectivity(VpnState.CONNECTING);
 
+            stopPreviouslyRunDaemons();
             String serverIp = getIp(getProfile().getServerName());
-
+            saveLocalIpAndInterface(serverIp);
             onBeforeConnect();
             connect(serverIp, username, password);
             waitUntilConnectedOrTimedout();
             return true;
         } catch (Throwable e) {
-            Log.e(TAG, "onConnect()", e);
             onError(e);
             return false;
         }
@@ -132,7 +165,7 @@
 
     synchronized void onDisconnect() {
         try {
-            Log.d(TAG, "       disconnecting VPN...");
+            Log.i(TAG, "disconnecting VPN...");
             mState = VpnState.DISCONNECTING;
             broadcastConnectivity(VpnState.DISCONNECTING);
             mNotification.showDisconnect();
@@ -152,6 +185,7 @@
             Log.w(TAG, "   multiple errors occur, record the last one: "
                     + error);
         }
+        Log.e(TAG, "onError()", error);
         mError = error;
         onDisconnect();
     }
@@ -161,16 +195,18 @@
     }
 
 
-    private void onBeforeConnect() {
+    private void onBeforeConnect() throws IOException {
         mNotification.disableNotification();
 
-        SystemProperties.set(VPN_DNS1, "-");
-        SystemProperties.set(VPN_DNS2, "-");
+        SystemProperties.set(VPN_DNS1, "");
+        SystemProperties.set(VPN_DNS2, "");
         SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
-        Log.d(TAG, "       VPN UP: " + SystemProperties.get(VPN_STATUS));
+        if (DBG) {
+            Log.d(TAG, "       VPN UP: " + SystemProperties.get(VPN_STATUS));
+        }
     }
 
-    private void waitUntilConnectedOrTimedout() {
+    private void waitUntilConnectedOrTimedout() throws IOException {
         sleep(2000); // 2 seconds
         for (int i = 0; i < 60; i++) {
             if (mState != VpnState.CONNECTING) {
@@ -187,39 +223,49 @@
 
         synchronized (VpnService.this) {
             if (mState == VpnState.CONNECTING) {
-                Log.d(TAG, "       connecting timed out !!");
                 onError(new IOException("Connecting timed out"));
             }
         }
     }
 
-    private synchronized void onConnected() {
-        Log.d(TAG, "onConnected()");
+    private synchronized void onConnected() throws IOException {
+        if (DBG) Log.d(TAG, "onConnected()");
 
         mDaemonHelper.closeSockets();
-        saveVpnDnsProperties();
+        saveOriginalDns();
         saveAndSetDomainSuffices();
 
         mState = VpnState.CONNECTED;
+        mStartTime = System.currentTimeMillis();
+
+        // set DNS after saving the states in case the process gets killed
+        // before states are saved
+        saveSelf();
+        setVpnDns();
         broadcastConnectivity(VpnState.CONNECTED);
 
         enterConnectivityLoop();
     }
 
+    private void saveSelf() throws IOException {
+        mContext.saveStates();
+    }
+
     private synchronized void onFinalCleanUp() {
-        Log.d(TAG, "onFinalCleanUp()");
+        if (DBG) Log.d(TAG, "onFinalCleanUp()");
 
         if (mState == VpnState.IDLE) return;
 
         // keep the notification when error occurs
         if (!anyError()) mNotification.disableNotification();
 
-        restoreOriginalDnsProperties();
+        restoreOriginalDns();
         restoreOriginalDomainSuffices();
         mState = VpnState.IDLE;
         broadcastConnectivity(VpnState.IDLE);
 
         // stop the service itself
+        mContext.removeStates();
         mContext.stopSelf();
     }
 
@@ -227,46 +273,38 @@
         return (mError != null);
     }
 
-    private void restoreOriginalDnsProperties() {
+    private void restoreOriginalDns() {
         // restore only if they are not overridden
-        if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
-            Log.d(TAG, String.format("restore original dns prop: %s --> %s",
+        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.d(TAG, String.format("restore original dns prop: %s --> %s",
+            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 saveVpnDnsProperties() {
-        mOriginalDns1 = mOriginalDns2 = "";
-        for (int i = 0; i < 5; i++) {
-            mVpnDns1 = SystemProperties.get(VPN_DNS1);
-            mVpnDns2 = SystemProperties.get(VPN_DNS2);
-            if (mOriginalDns1.equals(mVpnDns1)) {
-                Log.d(TAG, "wait for vpn dns to settle in..." + i);
-                sleep(200);
-            } else {
-                mOriginalDns1 = SystemProperties.get(DNS1);
-                mOriginalDns2 = SystemProperties.get(DNS2);
-                SystemProperties.set(DNS1, mVpnDns1);
-                SystemProperties.set(DNS2, mVpnDns2);
-                Log.d(TAG, String.format("save original dns prop: %s, %s",
-                        mOriginalDns1, mOriginalDns2));
-                Log.d(TAG, String.format("set vpn dns prop: %s, %s",
-                        mVpnDns1, mVpnDns2));
-                return;
-            }
-        }
-        Log.d(TAG, "saveVpnDnsProperties(): DNS not updated??");
-        mOriginalDns1 = mVpnDns1 = SystemProperties.get(DNS1);
-        mOriginalDns2 = mVpnDns2 = SystemProperties.get(DNS2);
+    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.d(TAG, "save original dns search: " + mOriginalDomainSuffices);
+        Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices);
         String list = mProfile.getDomainSuffices();
         if (!TextUtils.isEmpty(list)) {
             SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
@@ -274,7 +312,7 @@
     }
 
     private void restoreOriginalDomainSuffices() {
-        Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices);
+        Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
         SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
     }
 
@@ -298,46 +336,73 @@
     }
 
     private void enterConnectivityLoop() {
-        mStartTime = System.currentTimeMillis();
-
-        Log.d(TAG, "   +++++   connectivity monitor running");
+        Log.i(TAG, "VPN connectivity monitor running");
         try {
             for (;;) {
                 synchronized (VpnService.this) {
-                    if (mState != VpnState.CONNECTED) break;
+                    if (mState != VpnState.CONNECTED || !checkConnectivity()) {
+                        break;
+                    }
                     mNotification.update();
-                    checkConnectivity();
+                    checkDns();
                     VpnService.this.wait(1000); // 1 second
                 }
             }
         } catch (InterruptedException e) {
-            Log.e(TAG, "connectivity monitor", e);
+            onError(e);
         }
-        Log.d(TAG, "   -----   connectivity monitor stopped");
+        Log.i(TAG, "VPN connectivity monitor stopped");
     }
 
-    private void checkConnectivity() {
+    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 (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
             onDisconnect();
+            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() {
-        // TODO
-        if (!isDnsIntact()) {
-            Log.w(TAG, "       local IP changed");
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private boolean isDnsIntact() {
-        String dns1 = SystemProperties.get(DNS1);
-        if (!mVpnDns1.equals(dns1)) {
-            Log.w(TAG, "   dns being overridden by: " + dns1);
-            return false;
-        } else {
+        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;
         }
     }
@@ -349,7 +414,7 @@
         }
     }
 
-    private class DaemonHelper {
+    private class DaemonHelper implements Serializable {
         private List<DaemonProxy> mDaemonList =
                 new ArrayList<DaemonProxy>();
 
@@ -376,7 +441,7 @@
         synchronized boolean anyDaemonStopped() {
             for (DaemonProxy s : mDaemonList) {
                 if (s.isStopped()) {
-                    Log.w(TAG, "       daemon gone: " + s.getName());
+                    Log.w(TAG, "    VPN daemon gone: " + s.getName());
                     return true;
                 }
             }
@@ -401,6 +466,14 @@
                         onError(VpnManager.VPN_ERROR_AUTH);
                         return true;
 
+                    case CHALLENGE_ERROR_CODE:
+                        onError(VpnManager.VPN_ERROR_CHALLENGE);
+                        return true;
+
+                    case REMOTE_HUNG_UP_ERROR_CODE:
+                        onError(VpnManager.VPN_ERROR_REMOTE_HUNG_UP);
+                        return true;
+
                     default:
                         onError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
                         return true;
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
index 513a2c9..4892a7b 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
@@ -27,23 +27,31 @@
 import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
 import android.os.IBinder;
+import android.util.Log;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 
 /**
  * The service class for managing a VPN connection. It implements the
  * {@link IVpnService} binder interface.
  */
 public class VpnServiceBinder extends Service {
-    private final String TAG = VpnServiceBinder.class.getSimpleName();
+    private static final String TAG = VpnServiceBinder.class.getSimpleName();
+    private static final boolean DBG = true;
+
+    private static final String STATES_FILE_PATH = "/data/misc/vpn/.states";
 
     // The actual implementation is delegated to the VpnService class.
     private VpnService<? extends VpnProfile> mService;
 
     private final IBinder mBinder = new IVpnService.Stub() {
         public boolean connect(VpnProfile p, String username, String password) {
-            android.util.Log.d("VpnServiceBinder", "becoming foreground");
-            setForeground(true);
             return VpnServiceBinder.this.connect(p, username, password);
         }
 
@@ -57,6 +65,13 @@
     };
 
     @Override
+    public void onCreate() {
+        super.onCreate();
+        checkSavedStates();
+    }
+
+
+    @Override
     public void onStart(Intent intent, int startId) {
         super.onStart(intent, startId);
     }
@@ -66,14 +81,30 @@
         return mBinder;
     }
 
+    void saveStates() throws IOException {
+        if (DBG) Log.d("VpnServiceBinder", "     saving states");
+        ObjectOutputStream oos =
+                new ObjectOutputStream(new FileOutputStream(STATES_FILE_PATH));
+        oos.writeObject(mService);
+        oos.close();
+    }
+
+    void removeStates() {
+        try {
+            new File(STATES_FILE_PATH).delete();
+        } catch (Throwable e) {
+            if (DBG) Log.d("VpnServiceBinder", "     remove states: " + e);
+        }
+    }
+
     private synchronized boolean connect(final VpnProfile p,
             final String username, final String password) {
         if (mService != null) return false;
+        final VpnService s = mService = createService(p);
 
         new Thread(new Runnable() {
             public void run() {
-                mService = createService(p);
-                mService.onConnect(username, password);
+                s.onConnect(username, password);
             }
         }).start();
         return true;
@@ -81,12 +112,11 @@
 
     private synchronized void disconnect() {
         if (mService == null) return;
+        final VpnService s = mService;
 
         new Thread(new Runnable() {
             public void run() {
-                mService.onDisconnect();
-                android.util.Log.d("VpnServiceBinder", "becoming background");
-                setForeground(false);
+                s.onDisconnect();
             }
         }).start();
     }
@@ -100,6 +130,21 @@
         }
     }
 
+    private void checkSavedStates() {
+        try {
+            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
+                    STATES_FILE_PATH));
+            mService = (VpnService<? extends VpnProfile>) ois.readObject();
+            mService.recover(this);
+            ois.close();
+        } catch (FileNotFoundException e) {
+            // do nothing
+        } catch (Throwable e) {
+            Log.i("VpnServiceBinder", "recovery error, remove states: " + e);
+            removeStates();
+        }
+    }
+
     private VpnService<? extends VpnProfile> createService(VpnProfile p) {
         switch (p.getType()) {
             case L2TP:
diff --git a/vpn/java/android/net/vpn/PptpProfile.java b/vpn/java/android/net/vpn/PptpProfile.java
index c68bb71..cdc90f0 100644
--- a/vpn/java/android/net/vpn/PptpProfile.java
+++ b/vpn/java/android/net/vpn/PptpProfile.java
@@ -22,9 +22,21 @@
  */
 public class PptpProfile extends VpnProfile {
     private static final long serialVersionUID = 1L;
+    private boolean mEncryption = true;
 
     @Override
     public VpnType getType() {
         return VpnType.PPTP;
     }
+
+    /**
+     * Enables/disables the encryption for PPTP tunnel.
+     */
+    public void setEncryptionEnabled(boolean enabled) {
+        mEncryption = enabled;
+    }
+
+    public boolean isEncryptionEnabled() {
+        return mEncryption;
+    }
 }
diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java
index 0bf2346..e448e5a 100644
--- a/vpn/java/android/net/vpn/VpnManager.java
+++ b/vpn/java/android/net/vpn/VpnManager.java
@@ -50,6 +50,10 @@
     public static final int VPN_ERROR_CONNECTION_FAILED = 2;
     /** Error code to indicate the server is not known. */
     public static final int VPN_ERROR_UNKNOWN_SERVER = 3;
+    /** Error code to indicate an error from challenge response. */
+    public static final int VPN_ERROR_CHALLENGE = 4;
+    /** Error code to indicate an error of remote server hanging up. */
+    public static final int VPN_ERROR_REMOTE_HUNG_UP = 5;
     private static final int VPN_ERROR_NO_ERROR = 0;
 
     public static final String PROFILES_PATH = "/data/misc/vpn/profiles";