Fix stopping all vpn daemons before connect and more.

* move DaemonHelper out from VpnService to VpnDaemons for better
  managing native daemons.
* check connectivity and dns less frequently to save battery.
diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
index 7910f4a..50e0de1 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java
@@ -31,24 +31,17 @@
     protected void connect(String serverIp, String username, String password)
             throws IOException {
         L2tpIpsecPskProfile p = getProfile();
+        VpnDaemons daemons = getDaemons();
 
         // IPSEC
-        DaemonProxy ipsec = startDaemon(IPSEC);
-        ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT, p.getPresharedKey());
-        ipsec.closeControlSocket();
+        daemons.startIpsecForL2tp(serverIp, p.getPresharedKey())
+                .closeControlSocket();
 
         sleep(2000); // 2 seconds
 
         // L2TP
-        MtpdHelper.sendCommand(this, L2tpService.L2TP_DAEMON, serverIp,
-                L2tpService.L2TP_PORT,
+        daemons.startL2tp(serverIp,
                 (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 9909905..663b0e8 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java
@@ -31,9 +31,10 @@
     protected void connect(String serverIp, String username, String password)
             throws IOException {
         L2tpIpsecProfile p = getProfile();
+        VpnDaemons daemons = getDaemons();
+
         // IPSEC
-        DaemonProxy ipsec = startDaemon(IPSEC);
-        ipsec.sendCommand(serverIp, L2tpService.L2TP_PORT,
+        DaemonProxy ipsec = daemons.startIpsecForL2tp(serverIp,
                 Credentials.USER_PRIVATE_KEY + p.getUserCertificate(),
                 Credentials.USER_CERTIFICATE + p.getUserCertificate(),
                 Credentials.CA_CERTIFICATE + p.getCaCertificate());
@@ -42,15 +43,8 @@
         sleep(2000); // 2 seconds
 
         // L2TP
-        MtpdHelper.sendCommand(this, L2tpService.L2TP_DAEMON, serverIp,
-                L2tpService.L2TP_PORT,
+        daemons.startL2tp(serverIp,
                 (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/L2tpService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
index d658a36..784a366 100644
--- a/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java
@@ -24,20 +24,12 @@
  * The service that manages the L2TP VPN connection.
  */
 class L2tpService extends VpnService<L2tpProfile> {
-    static final String L2TP_DAEMON = "l2tp";
-    static final String L2TP_PORT = "1701";
-
     @Override
     protected void connect(String serverIp, String username, String password)
             throws IOException {
         L2tpProfile p = getProfile();
-        MtpdHelper.sendCommand(this, L2TP_DAEMON, serverIp, L2TP_PORT,
+        getDaemons().startL2tp(serverIp,
                 (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
deleted file mode 100644
index 9078d9b..0000000
--- a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * A helper class for sending commands to the MTP daemon (mtpd).
- */
-class MtpdHelper {
-    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(args, serverIp, username, password, encryption);
-
-        DaemonProxy mtpd = vpnService.startDaemon(MTPD);
-        mtpd.sendCommand(args.toArray(new String[args.size()]));
-    }
-
-    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");
-        }
-    }
-
-    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 d903d1b..de12710 100644
--- a/packages/VpnServices/src/com/android/server/vpn/PptpService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/PptpService.java
@@ -24,19 +24,11 @@
  * The service that manages the PPTP VPN connection.
  */
 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, p.isEncryptionEnabled());
-    }
-
-    @Override
-    protected void stopPreviouslyRunDaemons() {
-        stopDaemon(MtpdHelper.MTPD);
+        getDaemons().startPptp(serverIp, username, password,
+                p.isEncryptionEnabled());
     }
 }
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnDaemons.java b/packages/VpnServices/src/com/android/server/vpn/VpnDaemons.java
new file mode 100644
index 0000000..499195f
--- /dev/null
+++ b/packages/VpnServices/src/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/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 53167f6..63b87b1 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -30,18 +30,15 @@
 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;
 
 /**
  * The service base class for managing a type of VPN connection.
  */
 abstract class VpnService<E extends VpnProfile> implements Serializable {
-    protected static final long serialVersionUID = 1L;
+    static final long serialVersionUID = 1L;
     private static final boolean DBG = true;
     private static final int NOTIFICATION_ID = 1;
 
@@ -75,8 +72,8 @@
 
     private long mStartTime; // VPN connection start time
 
-    // for helping managing multiple daemons
-    private DaemonHelper mDaemonHelper = new DaemonHelper();
+    // for helping managing daemons
+    private VpnDaemons mDaemons = new VpnDaemons();
 
     // for helping showing, updating notification
     private transient NotificationHelper mNotification;
@@ -87,21 +84,11 @@
     protected abstract void connect(String serverIp, String username,
             String password) throws IOException;
 
-    protected abstract void stopPreviouslyRunDaemons();
-
     /**
-     * Starts a VPN daemon.
+     * Returns the daemons management class for this service object.
      */
-    protected DaemonProxy startDaemon(String daemonName)
-            throws IOException {
-        return mDaemonHelper.startDaemon(daemonName);
-    }
-
-    /**
-     * Stops a VPN daemon.
-     */
-    protected void stopDaemon(String daemonName) {
-        new DaemonProxy(daemonName).stop();
+    protected VpnDaemons getDaemons() {
+        return mDaemons;
     }
 
     /**
@@ -141,7 +128,7 @@
         try {
             setState(VpnState.CONNECTING);
 
-            stopPreviouslyRunDaemons();
+            mDaemons.stopAll();
             String serverIp = getIp(getProfile().getServerName());
             saveLocalIpAndInterface(serverIp);
             onBeforeConnect();
@@ -160,7 +147,7 @@
             setState(VpnState.DISCONNECTING);
             mNotification.showDisconnect();
 
-            mDaemonHelper.stopAll();
+            mDaemons.stopAll();
         } catch (Throwable e) {
             Log.e(TAG, "onDisconnect()", e);
         } finally {
@@ -206,7 +193,7 @@
                 onConnected();
                 return;
             } else {
-                int err = mDaemonHelper.getSocketError();
+                int err = mDaemons.getSocketError();
                 if (err != 0) {
                     onError(err);
                     return;
@@ -223,7 +210,7 @@
     private synchronized void onConnected() throws IOException {
         if (DBG) Log.d(TAG, "onConnected()");
 
-        mDaemonHelper.closeSockets();
+        mDaemons.closeSockets();
         saveOriginalDns();
         saveAndSetDomainSuffices();
 
@@ -341,15 +328,20 @@
             public void run() {
                 Log.i(TAG, "VPN connectivity monitor running");
                 try {
-                    for (;;) {
+                    for (int i = 10; ; i--) {
+                        long now = System.currentTimeMillis();
+
+                        boolean heavyCheck = i == 0;
                         synchronized (VpnService.this) {
-                            if ((mState != VpnState.CONNECTED)
-                                || !checkConnectivity()) {
-                                break;
+                            if (mState != VpnState.CONNECTED) break;
+                            mNotification.update(now);
+
+                            if (heavyCheck) {
+                                i = 10;
+                                if (checkConnectivity()) checkDns();
                             }
-                            mNotification.update();
-                            checkDns();
-                            VpnService.this.wait(1000); // 1 second
+                            long t = 1000L - System.currentTimeMillis() + now;
+                            if (t > 100L) VpnService.this.wait(t);
                         }
                     }
                 } catch (InterruptedException e) {
@@ -378,7 +370,7 @@
 
     // returns false if vpn connectivity is broken
     private boolean checkConnectivity() {
-        if (mDaemonHelper.anyDaemonStopped() || isLocalIpChanged()) {
+        if (mDaemons.anyDaemonStopped() || isLocalIpChanged()) {
             onError(new IOException("Connectivity lost"));
             return false;
         } else {
@@ -421,60 +413,17 @@
     }
 
     private class DaemonHelper implements Serializable {
-        private List<DaemonProxy> mDaemonList =
-                new ArrayList<DaemonProxy>();
-
-        synchronized DaemonProxy startDaemon(String daemonName)
-                throws IOException {
-            DaemonProxy daemon = new DaemonProxy(daemonName);
-            mDaemonList.add(daemon);
-            daemon.start();
-            return daemon;
-        }
-
-        synchronized void stopAll() {
-            for (DaemonProxy s : mDaemonList) s.stop();
-        }
-
-        synchronized void closeSockets() {
-            for (DaemonProxy s : mDaemonList) s.closeControlSocket();
-        }
-
-        synchronized boolean anyDaemonStopped() {
-            for (DaemonProxy s : mDaemonList) {
-                if (s.isStopped()) {
-                    Log.w(TAG, "    VPN daemon gone: " + s.getName());
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private int getResultFromSocket(DaemonProxy s) {
-            try {
-                return s.getResultFromSocket();
-            } catch (IOException e) {
-                return -1;
-            }
-        }
-
-        synchronized int getSocketError() {
-            for (DaemonProxy s : mDaemonList) {
-                int errCode = getResultFromSocket(s);
-                if (errCode != 0) return errCode;
-            }
-            return 0;
-        }
     }
 
     // Helper class for showing, updating notification.
     private class NotificationHelper {
-        void update() {
+        void update(long now) {
             String title = getNotificationTitle(true);
             Notification n = new Notification(R.drawable.vpn_connected, title,
                     mStartTime);
             n.setLatestEventInfo(mContext, title,
-                    getNotificationMessage(true), prepareNotificationIntent());
+                    getConnectedNotificationMessage(now),
+                    prepareNotificationIntent());
             n.flags |= Notification.FLAG_NO_CLEAR;
             n.flags |= Notification.FLAG_ONGOING_EVENT;
             enableNotification(n);
@@ -485,7 +434,8 @@
             Notification n = new Notification(R.drawable.vpn_disconnected,
                     title, System.currentTimeMillis());
             n.setLatestEventInfo(mContext, title,
-                    getNotificationMessage(false), prepareNotificationIntent());
+                    getDisconnectedNotificationMessage(),
+                    prepareNotificationIntent());
             n.flags |= Notification.FLAG_AUTO_CANCEL;
             disableNotification();
             enableNotification(n);
@@ -515,8 +465,8 @@
             return String.format(formatString, mProfile.getName());
         }
 
-        private String getFormattedTime(long duration) {
-            long hours = duration / 3600;
+        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),
@@ -524,14 +474,13 @@
             return sb.toString();
         }
 
-        private String getNotificationMessage(boolean connected) {
-            if (connected) {
-                long time = (System.currentTimeMillis() - mStartTime) / 1000;
-                return getFormattedTime(time);
-            } else {
-                return mContext.getString(
-                        R.string.vpn_notification_hint_disconnected);
-            }
+        private String getConnectedNotificationMessage(long now) {
+            return getFormattedTime((int) (now - mStartTime) / 1000);
+        }
+
+        private String getDisconnectedNotificationMessage() {
+            return mContext.getString(
+                    R.string.vpn_notification_hint_disconnected);
         }
     }
 }