Refactor ZygoteInit to support a WebView-specific zygote.

This is a non-functional change that separates out functionality
that should be shared between the system zygote and the WebView
zygote from that which is system zygote specific.

* Move MethodAndArgsCaller to Zygote.
* Split out server socket functions into ZygoteServer.
* Add a new (stub, for now) WebViewZygoteInit class.

Bug: 22084679
Bug: 21643067
(cherry picked from commit ba816e0c9efd8cd2aeef618a819a2ad46b742f87)

Merged-In: I4c508a42af7ab7b53d10570ad53b846df7782cc4
Change-Id: I54f04c03443d10dabe6426697d1ff8a0cc66b985
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index de671b1..c851e4e 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -230,7 +230,7 @@
      * @param classLoader the classLoader to load {@className} with
      */
     private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
-            throws ZygoteInit.MethodAndArgsCaller {
+            throws Zygote.MethodAndArgsCaller {
         Class<?> cl;
 
         try {
@@ -264,7 +264,7 @@
          * clears up all the stack frames that were required in setting
          * up the process.
          */
-        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
+        throw new Zygote.MethodAndArgsCaller(m, argv);
     }
 
     public static final void main(String[] argv) {
@@ -301,7 +301,7 @@
      * @param argv arg strings
      */
     public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
-            throws ZygoteInit.MethodAndArgsCaller {
+            throws Zygote.MethodAndArgsCaller {
         if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
@@ -324,14 +324,14 @@
      * @param argv arg strings
      */
     public static void wrapperInit(int targetSdkVersion, String[] argv)
-            throws ZygoteInit.MethodAndArgsCaller {
+            throws Zygote.MethodAndArgsCaller {
         if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper");
 
         applicationInit(targetSdkVersion, argv, null);
     }
 
     private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
-            throws ZygoteInit.MethodAndArgsCaller {
+            throws Zygote.MethodAndArgsCaller {
         // If the application calls System.exit(), terminate the process
         // immediately without running any shutdown hooks.  It is not possible to
         // shutdown an Android application gracefully.  Among other things, the
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
new file mode 100644
index 0000000..2ed7aa2
--- /dev/null
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+/**
+ * Startup class for the WebView zygote process.
+ *
+ * See {@link ZygoteInit} for generic zygote startup documentation.
+ *
+ * @hide
+ */
+class WebViewZygoteInit {
+    public static final String TAG = "WebViewZygoteInit";
+
+    public static void main(String argv[]) {
+        throw new RuntimeException("Not implemented yet");
+    }
+}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index c558cf8..594b6ab 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -74,14 +74,14 @@
                 }
             }
 
-            // Mimic Zygote preloading.
+            // Mimic system Zygote preloading.
             ZygoteInit.preload();
 
             // Launch the application.
             String[] runtimeArgs = new String[args.length - 2];
             System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
             RuntimeInit.wrapperInit(targetSdkVersion, runtimeArgs);
-        } catch (ZygoteInit.MethodAndArgsCaller caller) {
+        } catch (Zygote.MethodAndArgsCaller caller) {
             caller.run();
         }
     }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 66cc975..fc0ccb7 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -22,6 +22,9 @@
 import android.system.ErrnoException;
 import android.system.Os;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 /** @hide */
 public final class Zygote {
     /*
@@ -191,4 +194,39 @@
             command.append(" '").append(arg.replace("'", "'\\''")).append("'");
         }
     }
+
+    /**
+     * Helper exception class which holds a method and arguments and
+     * can call them. This is used as part of a trampoline to get rid of
+     * the initial process setup stack frames.
+     */
+    public static class MethodAndArgsCaller extends Exception
+            implements Runnable {
+        /** method to call */
+        private final Method mMethod;
+
+        /** argument array */
+        private final String[] mArgs;
+
+        public MethodAndArgsCaller(Method method, String[] args) {
+            mMethod = method;
+            mArgs = args;
+        }
+
+        public void run() {
+            try {
+                mMethod.invoke(null, new Object[] { mArgs });
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException(ex);
+            } catch (InvocationTargetException ex) {
+                Throwable cause = ex.getCause();
+                if (cause instanceof RuntimeException) {
+                    throw (RuntimeException) cause;
+                } else if (cause instanceof Error) {
+                    throw (Error) cause;
+                }
+                throw new RuntimeException(ex);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 85d84bb..132b022 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -117,7 +117,7 @@
 
     /**
      * Reads one start command from the command socket. If successful,
-     * a child is forked and a {@link ZygoteInit.MethodAndArgsCaller}
+     * a child is forked and a {@link Zygote.MethodAndArgsCaller}
      * exception is thrown in that child while in the parent process,
      * the method returns normally. On failure, the child is not
      * spawned and messages are printed to the log and stderr. Returns
@@ -126,10 +126,10 @@
      *
      * @return false if command socket should continue to be read from, or
      * true if an end-of-file has been encountered.
-     * @throws ZygoteInit.MethodAndArgsCaller trampoline to invoke main()
+     * @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
      * method in child process
      */
-    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
+    boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
 
         String args[];
         Arguments parsedArgs = null;
@@ -214,7 +214,7 @@
                 fdsToClose[0] = fd.getInt$();
             }
 
-            fd = ZygoteInit.getServerSocketFileDescriptor();
+            fd = zygoteServer.getServerSocketFileDescriptor();
 
             if (fd != null) {
                 fdsToClose[1] = fd.getInt$();
@@ -238,12 +238,13 @@
         try {
             if (pid == 0) {
                 // in child
+                zygoteServer.closeServerSocket();
                 IoUtils.closeQuietly(serverPipeFd);
                 serverPipeFd = null;
                 handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
 
                 // should never get here, the child is expected to either
-                // throw ZygoteInit.MethodAndArgsCaller or exec().
+                // throw Zygote.MethodAndArgsCaller or exec().
                 return true;
             } else {
                 // in parent...pid of < 0 means failure
@@ -712,12 +713,12 @@
      * @param newStderr null-ok; stream to use for stderr until stdio
      * is reopened.
      *
-     * @throws ZygoteInit.MethodAndArgsCaller on success to
+     * @throws Zygote.MethodAndArgsCaller on success to
      * trampoline to code that invokes static main.
      */
     private void handleChildProc(Arguments parsedArgs,
             FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
-            throws ZygoteInit.MethodAndArgsCaller {
+            throws Zygote.MethodAndArgsCaller {
         /**
          * By the time we get here, the native code has closed the two actual Zygote
          * socket connections, and substituted /dev/null in their place.  The LocalSocket
@@ -725,8 +726,6 @@
          */
 
         closeSocket();
-        ZygoteInit.closeServerSocket();
-
         if (descriptors != null) {
             try {
                 Os.dup2(descriptors[0], STDIN_FILENO);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b57aea6..2e5b550 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.os;
 
-import static android.system.OsConstants.POLLIN;
 import static android.system.OsConstants.S_IRWXG;
 import static android.system.OsConstants.S_IRWXO;
 
@@ -35,7 +34,6 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.system.StructPollfd;
 import android.text.Hyphenator;
 import android.util.EventLog;
 import android.util.Log;
@@ -52,17 +50,13 @@
 import libcore.io.IoUtils;
 
 import java.io.BufferedReader;
-import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.security.Security;
 import java.security.Provider;
-import java.util.ArrayList;
 
 /**
  * Startup class for the zygote process.
@@ -82,8 +76,6 @@
     private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
     private static final String PROPERTY_RUNNING_IN_CONTAINER = "ro.boot.container";
 
-    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
-
     private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
     private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
 
@@ -94,11 +86,8 @@
 
     private static final String SOCKET_NAME_ARG = "--socket-name=";
 
-    private static LocalServerSocket sServerSocket;
-
     /**
-     * Used to pre-load resources.  We hold a global reference on it so it
-     * never gets destroyed.
+     * Used to pre-load resources.
      */
     private static Resources mResources;
 
@@ -110,78 +99,6 @@
     /** Controls whether we should preload resources during zygote init. */
     public static final boolean PRELOAD_RESOURCES = true;
 
-    /**
-     * Registers a server socket for zygote command connections
-     *
-     * @throws RuntimeException when open fails
-     */
-    private static void registerZygoteSocket(String socketName) {
-        if (sServerSocket == null) {
-            int fileDesc;
-            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
-            try {
-                String env = System.getenv(fullSocketName);
-                fileDesc = Integer.parseInt(env);
-            } catch (RuntimeException ex) {
-                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
-            }
-
-            try {
-                FileDescriptor fd = new FileDescriptor();
-                fd.setInt$(fileDesc);
-                sServerSocket = new LocalServerSocket(fd);
-            } catch (IOException ex) {
-                throw new RuntimeException(
-                        "Error binding to local socket '" + fileDesc + "'", ex);
-            }
-        }
-    }
-
-    /**
-     * Waits for and accepts a single command connection. Throws
-     * RuntimeException on failure.
-     */
-    private static ZygoteConnection acceptCommandPeer(String abiList) {
-        try {
-            return new ZygoteConnection(sServerSocket.accept(), abiList);
-        } catch (IOException ex) {
-            throw new RuntimeException(
-                    "IOException during accept()", ex);
-        }
-    }
-
-    /**
-     * Close and clean up zygote sockets. Called on shutdown and on the
-     * child's exit path.
-     */
-    static void closeServerSocket() {
-        try {
-            if (sServerSocket != null) {
-                FileDescriptor fd = sServerSocket.getFileDescriptor();
-                sServerSocket.close();
-                if (fd != null) {
-                    Os.close(fd);
-                }
-            }
-        } catch (IOException ex) {
-            Log.e(TAG, "Zygote:  error closing sockets", ex);
-        } catch (ErrnoException ex) {
-            Log.e(TAG, "Zygote:  error closing descriptor", ex);
-        }
-
-        sServerSocket = null;
-    }
-
-    /**
-     * Return the server socket's underlying file descriptor, so that
-     * ZygoteConnection can pass it to the native code for proper
-     * closure after a child process is forked off.
-     */
-
-    static FileDescriptor getServerSocketFileDescriptor() {
-        return sServerSocket.getFileDescriptor();
-    }
-
     private static final int UNPRIVILEGED_UID = 9999;
     private static final int UNPRIVILEGED_GID = 9999;
 
@@ -504,9 +421,7 @@
      */
     private static void handleSystemServerProcess(
             ZygoteConnection.Arguments parsedArgs)
-            throws ZygoteInit.MethodAndArgsCaller {
-
-        closeServerSocket();
+            throws Zygote.MethodAndArgsCaller {
 
         // set umask to 0077 so new files and directories will default to owner-only permissions.
         Os.umask(S_IRWXG | S_IRWXO);
@@ -609,8 +524,8 @@
     /**
      * Prepare the arguments and fork for the system server process.
      */
-    private static boolean startSystemServer(String abiList, String socketName)
-            throws MethodAndArgsCaller, RuntimeException {
+    private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
+            throws Zygote.MethodAndArgsCaller, RuntimeException {
         long capabilities = posixCapabilitiesAsBits(
             OsConstants.CAP_IPC_LOCK,
             OsConstants.CAP_KILL,
@@ -666,6 +581,7 @@
                 waitForSecondaryZygote(socketName);
             }
 
+            zygoteServer.closeServerSocket();
             handleSystemServerProcess(parsedArgs);
         }
 
@@ -687,6 +603,8 @@
     }
 
     public static void main(String argv[]) {
+        ZygoteServer zygoteServer = new ZygoteServer();
+
         // Mark zygote start. This ensures that thread creation will throw
         // an error.
         ZygoteHooks.startZygoteNoThreadCreation();
@@ -716,7 +634,7 @@
                 throw new RuntimeException("No ABI list supplied.");
             }
 
-            registerZygoteSocket(socketName);
+            zygoteServer.registerServerSocket(socketName);
             Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygotePreload");
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                 SystemClock.uptimeMillis());
@@ -733,8 +651,6 @@
             gcAndFinalize();
             Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
 
-            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-
             // Disable tracing so that forked processes do not inherit stale tracing tags from
             // Zygote.
             Trace.setTracingEnabled(false);
@@ -745,18 +661,18 @@
             ZygoteHooks.stopZygoteNoThreadCreation();
 
             if (startSystemServer) {
-                startSystemServer(abiList, socketName);
+                startSystemServer(abiList, socketName, zygoteServer);
             }
 
             Log.i(TAG, "Accepting command socket connections");
-            runSelectLoop(abiList);
+            zygoteServer.runSelectLoop(abiList);
 
-            closeServerSocket();
-        } catch (MethodAndArgsCaller caller) {
+            zygoteServer.closeServerSocket();
+        } catch (Zygote.MethodAndArgsCaller caller) {
             caller.run();
         } catch (RuntimeException ex) {
-            Log.e(TAG, "Zygote died with exception", ex);
-            closeServerSocket();
+            Log.e(TAG, "System zygote died with exception", ex);
+            zygoteServer.closeServerSocket();
             throw ex;
         }
     }
@@ -792,89 +708,8 @@
     }
 
     /**
-     * Runs the zygote process's select loop. Accepts new connections as
-     * they happen, and reads commands from connections one spawn-request's
-     * worth at a time.
-     *
-     * @throws MethodAndArgsCaller in a child process when a main() should
-     * be executed.
-     */
-    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
-        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
-        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
-
-        fds.add(sServerSocket.getFileDescriptor());
-        peers.add(null);
-
-        while (true) {
-            StructPollfd[] pollFds = new StructPollfd[fds.size()];
-            for (int i = 0; i < pollFds.length; ++i) {
-                pollFds[i] = new StructPollfd();
-                pollFds[i].fd = fds.get(i);
-                pollFds[i].events = (short) POLLIN;
-            }
-            try {
-                Os.poll(pollFds, -1);
-            } catch (ErrnoException ex) {
-                throw new RuntimeException("poll failed", ex);
-            }
-            for (int i = pollFds.length - 1; i >= 0; --i) {
-                if ((pollFds[i].revents & POLLIN) == 0) {
-                    continue;
-                }
-                if (i == 0) {
-                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
-                    peers.add(newPeer);
-                    fds.add(newPeer.getFileDesciptor());
-                } else {
-                    boolean done = peers.get(i).runOnce();
-                    if (done) {
-                        peers.remove(i);
-                        fds.remove(i);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
      * Class not instantiable.
      */
     private ZygoteInit() {
     }
-
-    /**
-     * Helper exception class which holds a method and arguments and
-     * can call them. This is used as part of a trampoline to get rid of
-     * the initial process setup stack frames.
-     */
-    public static class MethodAndArgsCaller extends Exception
-            implements Runnable {
-        /** method to call */
-        private final Method mMethod;
-
-        /** argument array */
-        private final String[] mArgs;
-
-        public MethodAndArgsCaller(Method method, String[] args) {
-            mMethod = method;
-            mArgs = args;
-        }
-
-        public void run() {
-            try {
-                mMethod.invoke(null, new Object[] { mArgs });
-            } catch (IllegalAccessException ex) {
-                throw new RuntimeException(ex);
-            } catch (InvocationTargetException ex) {
-                Throwable cause = ex.getCause();
-                if (cause instanceof RuntimeException) {
-                    throw (RuntimeException) cause;
-                } else if (cause instanceof Error) {
-                    throw (Error) cause;
-                }
-                throw new RuntimeException(ex);
-            }
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
new file mode 100644
index 0000000..ab876410
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import static android.system.OsConstants.POLLIN;
+
+import android.net.LocalServerSocket;
+import android.system.Os;
+import android.system.ErrnoException;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+/**
+ * Server socket class for zygote processes.
+ *
+ * Provides functions to wait for commands on a UNIX domain socket, and fork
+ * off child processes that inherit the initial state of the VM.%
+ *
+ * Please see {@link ZygoteConnection.Arguments} for documentation on the
+ * client protocol.
+ */
+class ZygoteServer {
+    public static final String TAG = "ZygoteServer";
+
+    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+    private LocalServerSocket mServerSocket;
+
+    ZygoteServer() {
+    }
+
+    /**
+     * Registers a server socket for zygote command connections
+     *
+     * @throws RuntimeException when open fails
+     */
+    void registerServerSocket(String socketName) {
+        if (mServerSocket == null) {
+            int fileDesc;
+            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
+            try {
+                String env = System.getenv(fullSocketName);
+                fileDesc = Integer.parseInt(env);
+            } catch (RuntimeException ex) {
+                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
+            }
+
+            try {
+                FileDescriptor fd = new FileDescriptor();
+                fd.setInt$(fileDesc);
+                mServerSocket = new LocalServerSocket(fd);
+            } catch (IOException ex) {
+                throw new RuntimeException(
+                        "Error binding to local socket '" + fileDesc + "'", ex);
+            }
+        }
+    }
+
+    /**
+     * Waits for and accepts a single command connection. Throws
+     * RuntimeException on failure.
+     */
+    private ZygoteConnection acceptCommandPeer(String abiList) {
+        try {
+            return new ZygoteConnection(mServerSocket.accept(), abiList);
+        } catch (IOException ex) {
+            throw new RuntimeException(
+                    "IOException during accept()", ex);
+        }
+    }
+
+    /**
+     * Close and clean up zygote sockets. Called on shutdown and on the
+     * child's exit path.
+     */
+    void closeServerSocket() {
+        try {
+            if (mServerSocket != null) {
+                FileDescriptor fd = mServerSocket.getFileDescriptor();
+                mServerSocket.close();
+                if (fd != null) {
+                    Os.close(fd);
+                }
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "Zygote:  error closing sockets", ex);
+        } catch (ErrnoException ex) {
+            Log.e(TAG, "Zygote:  error closing descriptor", ex);
+        }
+
+        mServerSocket = null;
+    }
+
+    /**
+     * Return the server socket's underlying file descriptor, so that
+     * ZygoteConnection can pass it to the native code for proper
+     * closure after a child process is forked off.
+     */
+
+    FileDescriptor getServerSocketFileDescriptor() {
+        return mServerSocket.getFileDescriptor();
+    }
+
+    /**
+     * Runs the zygote process's select loop. Accepts new connections as
+     * they happen, and reads commands from connections one spawn-request's
+     * worth at a time.
+     *
+     * @throws Zygote.MethodAndArgsCaller in a child process when a main()
+     * should be executed.
+     */
+    void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
+        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
+
+        fds.add(mServerSocket.getFileDescriptor());
+        peers.add(null);
+
+        while (true) {
+            StructPollfd[] pollFds = new StructPollfd[fds.size()];
+            for (int i = 0; i < pollFds.length; ++i) {
+                pollFds[i] = new StructPollfd();
+                pollFds[i].fd = fds.get(i);
+                pollFds[i].events = (short) POLLIN;
+            }
+            try {
+                Os.poll(pollFds, -1);
+            } catch (ErrnoException ex) {
+                throw new RuntimeException("poll failed", ex);
+            }
+            for (int i = pollFds.length - 1; i >= 0; --i) {
+                if ((pollFds[i].revents & POLLIN) == 0) {
+                    continue;
+                }
+                if (i == 0) {
+                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
+                    peers.add(newPeer);
+                    fds.add(newPeer.getFileDesciptor());
+                } else {
+                    boolean done = peers.get(i).runOnce(this);
+                    if (done) {
+                        peers.remove(i);
+                        fds.remove(i);
+                    }
+                }
+            }
+        }
+    }
+}