Remove system_server classes from the boot image.

We set the system_server classpath in the environment
(like we do with BOOTCLASSPATH). After the zygote forks
the system_server, we dexopt the classpath (if needed)
and then launch the system server with the correct
PathClassLoader. This needed several small / medium
refactorings :

- The logic for connecting to installd is now in a separate
  class and belongs in the system_server.
- SystemService / SystemServiceManager have now moved to
  classes.jar. They are only used from there, and since they
  use Class.forName, we want them to be loaded by the
  system_server classloader, and not the bootclassloader.
- BootReceiver now moves to frameworks.jar, because it is
  used by ActivityThread and friends.

bug: 16555230

Change-Id: Ic84f0b2baf611eeedff6d123cb7191bb0259e600
diff --git a/services/appwidget/Android.mk b/services/appwidget/Android.mk
index ca38f2f..e9bab4a 100644
--- a/services/appwidget/Android.mk
+++ b/services/appwidget/Android.mk
@@ -7,4 +7,6 @@
 LOCAL_SRC_FILES += \
       $(call all-java-files-under,java)
 
+LOCAL_JAVA_LIBRARIES := services.core
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
deleted file mode 100644
index 7249985..0000000
--- a/services/core/java/com/android/server/BootReceiver.java
+++ /dev/null
@@ -1,260 +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;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.IPackageManager;
-import android.os.Build;
-import android.os.DropBoxManager;
-import android.os.FileObserver;
-import android.os.FileUtils;
-import android.os.RecoverySystem;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.provider.Downloads;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
- */
-public class BootReceiver extends BroadcastReceiver {
-    private static final String TAG = "BootReceiver";
-
-    // Maximum size of a logged event (files get truncated if they're longer).
-    // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg.
-    private static final int LOG_SIZE =
-        SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
-
-    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
-
-    // The pre-froyo package and class of the system updater, which
-    // ran in the system process.  We need to remove its packages here
-    // in order to clean up after a pre-froyo-to-froyo update.
-    private static final String OLD_UPDATER_PACKAGE =
-        "com.google.android.systemupdater";
-    private static final String OLD_UPDATER_CLASS =
-        "com.google.android.systemupdater.SystemUpdateReceiver";
-
-    // Keep a reference to the observer so the finalizer doesn't disable it.
-    private static FileObserver sTombstoneObserver = null;
-
-    @Override
-    public void onReceive(final Context context, Intent intent) {
-        // Log boot events in the background to avoid blocking the main thread with I/O
-        new Thread() {
-            @Override
-            public void run() {
-                try {
-                    logBootEvents(context);
-                } catch (Exception e) {
-                    Slog.e(TAG, "Can't log boot events", e);
-                }
-                try {
-                    boolean onlyCore = false;
-                    try {
-                        onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService(
-                                "package")).isOnlyCoreApps();
-                    } catch (RemoteException e) {
-                    }
-                    if (!onlyCore) {
-                        removeOldUpdatePackages(context);
-                    }
-                } catch (Exception e) {
-                    Slog.e(TAG, "Can't remove old update packages", e);
-                }
-
-            }
-        }.start();
-    }
-
-    private void removeOldUpdatePackages(Context context) {
-        Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
-    }
-
-    private void logBootEvents(Context ctx) throws IOException {
-        final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
-        final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
-        final String headers = new StringBuilder(512)
-            .append("Build: ").append(Build.FINGERPRINT).append("\n")
-            .append("Hardware: ").append(Build.BOARD).append("\n")
-            .append("Revision: ")
-            .append(SystemProperties.get("ro.revision", "")).append("\n")
-            .append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
-            .append("Radio: ").append(Build.RADIO).append("\n")
-            .append("Kernel: ")
-            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
-            .append("\n").toString();
-        final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
-
-        String recovery = RecoverySystem.handleAftermath();
-        if (recovery != null && db != null) {
-            db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
-        }
-
-        String lastKmsgFooter = "";
-        if (bootReason != null) {
-            lastKmsgFooter = new StringBuilder(512)
-                .append("\n")
-                .append("Boot info:\n")
-                .append("Last boot reason: ").append(bootReason).append("\n")
-                .toString();
-        }
-
-        if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
-            String now = Long.toString(System.currentTimeMillis());
-            SystemProperties.set("ro.runtime.firstboot", now);
-            if (db != null) db.addText("SYSTEM_BOOT", headers);
-
-            // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
-            addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter,
-                    "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");
-            addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter,
-                    "/sys/fs/pstore/console-ramoops", -LOG_SIZE,
-                    "SYSTEM_LAST_KMSG");
-            addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
-                    -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
-            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
-                    -LOG_SIZE, "APANIC_CONSOLE");
-            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
-                    -LOG_SIZE, "APANIC_THREADS");
-            addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT");
-            addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK");
-        } else {
-            if (db != null) db.addText("SYSTEM_RESTART", headers);
-        }
-
-        // Scan existing tombstones (in case any new ones appeared)
-        File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
-        for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
-            addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(),
-                    LOG_SIZE, "SYSTEM_TOMBSTONE");
-        }
-
-        // Start watching for new tombstone files; will record them as they occur.
-        // This gets registered with the singleton file observer thread.
-        sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
-            @Override
-            public void onEvent(int event, String path) {
-                try {
-                    String filename = new File(TOMBSTONE_DIR, path).getPath();
-                    addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
-                } catch (IOException e) {
-                    Slog.e(TAG, "Can't log tombstone", e);
-                }
-            }
-        };
-
-        sTombstoneObserver.startWatching();
-    }
-
-    private static void addFileToDropBox(
-            DropBoxManager db, SharedPreferences prefs,
-            String headers, String filename, int maxSize, String tag) throws IOException {
-        addFileWithFootersToDropBox(db, prefs, headers, "", filename, maxSize,
-                tag);
-    }
-
-    private static void addFileWithFootersToDropBox(
-            DropBoxManager db, SharedPreferences prefs,
-            String headers, String footers, String filename, int maxSize,
-            String tag) throws IOException {
-        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
-
-        File file = new File(filename);
-        long fileTime = file.lastModified();
-        if (fileTime <= 0) return;  // File does not exist
-
-        if (prefs != null) {
-            long lastTime = prefs.getLong(filename, 0);
-            if (lastTime == fileTime) return;  // Already logged this particular file
-            // TODO: move all these SharedPreferences Editor commits
-            // outside this function to the end of logBootEvents
-            prefs.edit().putLong(filename, fileTime).apply();
-        }
-
-        Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
-        db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + footers);
-    }
-
-    private static void addAuditErrorsToDropBox(DropBoxManager db,  SharedPreferences prefs,
-            String headers, int maxSize, String tag) throws IOException {
-        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
-        Slog.i(TAG, "Copying audit failures to DropBox");
-
-        File file = new File("/proc/last_kmsg");
-        long fileTime = file.lastModified();
-        if (fileTime <= 0) {
-            file = new File("/sys/fs/pstore/console-ramoops");
-            fileTime = file.lastModified();
-        }
-
-        if (fileTime <= 0) return;  // File does not exist
-
-        if (prefs != null) {
-            long lastTime = prefs.getLong(tag, 0);
-            if (lastTime == fileTime) return;  // Already logged this particular file
-            // TODO: move all these SharedPreferences Editor commits
-            // outside this function to the end of logBootEvents
-            prefs.edit().putLong(tag, fileTime).apply();
-        }
-
-        String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
-        StringBuilder sb = new StringBuilder();
-        for (String line : log.split("\n")) {
-            if (line.contains("audit")) {
-                sb.append(line + "\n");
-            }
-        }
-        Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox");
-        db.addText(tag, headers + sb.toString());
-    }
-
-    private static void addFsckErrorsToDropBox(DropBoxManager db,  SharedPreferences prefs,
-            String headers, int maxSize, String tag) throws IOException {
-        boolean upload_needed = false;
-        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
-        Slog.i(TAG, "Checking for fsck errors");
-
-        File file = new File("/dev/fscklogs/log");
-        long fileTime = file.lastModified();
-        if (fileTime <= 0) return;  // File does not exist
-
-        String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
-        StringBuilder sb = new StringBuilder();
-        for (String line : log.split("\n")) {
-            if (line.contains("FILE SYSTEM WAS MODIFIED")) {
-                upload_needed = true;
-                break;
-            }
-        }
-
-        if (upload_needed) {
-            addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag);
-        }
-
-        // Remove the file so we don't re-upload if the runtime restarts.
-        file.delete();
-    }
-}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
new file mode 100644
index 0000000..6e67970
--- /dev/null
+++ b/services/core/java/com/android/server/SystemService.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * The base class for services running in the system process. Override and implement
+ * the lifecycle event callback methods as needed.
+ * <p>
+ * The lifecycle of a SystemService:
+ * </p><ul>
+ * <li>The constructor is called and provided with the system {@link Context}
+ * to initialize the system service.
+ * <li>{@link #onStart()} is called to get the service running.  The service should
+ * publish its binder interface at this point using
+ * {@link #publishBinderService(String, IBinder)}.  It may also publish additional
+ * local interfaces that other services within the system server may use to access
+ * privileged internal functions.
+ * <li>Then {@link #onBootPhase(int)} is called as many times as there are boot phases
+ * until {@link #PHASE_BOOT_COMPLETE} is sent, which is the last boot phase. Each phase
+ * is an opportunity to do special work, like acquiring optional service dependencies,
+ * waiting to see if SafeMode is enabled, or registering with a service that gets
+ * started after this one.
+ * </ul><p>
+ * NOTE: All lifecycle methods are called from the system server's main looper thread.
+ * </p>
+ *
+ * {@hide}
+ */
+public abstract class SystemService {
+    /*
+     * Boot Phases
+     */
+    public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+
+    /**
+     * After receiving this boot phase, services can obtain lock settings data.
+     */
+    public static final int PHASE_LOCK_SETTINGS_READY = 480;
+
+    /**
+     * After receiving this boot phase, services can safely call into core system services
+     * such as the PowerManager or PackageManager.
+     */
+    public static final int PHASE_SYSTEM_SERVICES_READY = 500;
+
+    /**
+     * After receiving this boot phase, services can broadcast Intents.
+     */
+    public static final int PHASE_ACTIVITY_MANAGER_READY = 550;
+
+    /**
+     * After receiving this boot phase, services can start/bind to third party apps.
+     * Apps will be able to make Binder calls into services at this point.
+     */
+    public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600;
+
+    /**
+     * After receiving this boot phase, services can allow user interaction with the device.
+     * This phase occurs when boot has completed and the home application has started.
+     * System services may prefer to listen to this phase rather than registering a
+     * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+     */
+    public static final int PHASE_BOOT_COMPLETED = 1000;
+
+    private final Context mContext;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     */
+    public SystemService(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Gets the system context.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns true if the system is running in safe mode.
+     * TODO: we should define in which phase this becomes valid
+     */
+    public final boolean isSafeMode() {
+        return getManager().isSafeMode();
+    }
+
+    /**
+     * Called when the dependencies listed in the @Service class-annotation are available
+     * and after the chosen start phase.
+     * When this method returns, the service should be published.
+     */
+    public abstract void onStart();
+
+    /**
+     * Called on each phase of the boot process. Phases before the service's start phase
+     * (as defined in the @Service annotation) are never received.
+     *
+     * @param phase The current boot phase.
+     */
+    public void onBootPhase(int phase) {}
+
+    /**
+     * Called when a new user is starting, for system services to initialize any per-user
+     * state they maintain for running users.
+     * @param userHandle The identifier of the user.
+     */
+    public void onStartUser(int userHandle) {}
+
+    /**
+     * Called when switching to a different foreground user, for system services that have
+     * special behavior for whichever user is currently in the foreground.  This is called
+     * before any application processes are aware of the new user.
+     * @param userHandle The identifier of the user.
+     */
+    public void onSwitchUser(int userHandle) {}
+
+    /**
+     * Called when an existing user is stopping, for system services to finalize any per-user
+     * state they maintain for running users.  This is called prior to sending the SHUTDOWN
+     * broadcast to the user; it is a good place to stop making use of any resources of that
+     * user (such as binding to a service running in the user).
+     * @param userHandle The identifier of the user.
+     */
+    public void onStopUser(int userHandle) {}
+
+    /**
+     * Called when an existing user is stopping, for system services to finalize any per-user
+     * state they maintain for running users.  This is called after all application process
+     * teardown of the user is complete.
+     * @param userHandle The identifier of the user.
+     */
+    public void onCleanupUser(int userHandle) {}
+
+    /**
+     * Publish the service so it is accessible to other services and apps.
+     */
+    protected final void publishBinderService(String name, IBinder service) {
+        publishBinderService(name, service, false);
+    }
+
+    /**
+     * Publish the service so it is accessible to other services and apps.
+     */
+    protected final void publishBinderService(String name, IBinder service,
+            boolean allowIsolated) {
+        ServiceManager.addService(name, service, allowIsolated);
+    }
+
+    /**
+     * Get a binder service by its name.
+     */
+    protected final IBinder getBinderService(String name) {
+        return ServiceManager.getService(name);
+    }
+
+    /**
+     * Publish the service so it is only accessible to the system process.
+     */
+    protected final <T> void publishLocalService(Class<T> type, T service) {
+        LocalServices.addService(type, service);
+    }
+
+    /**
+     * Get a local service by interface.
+     */
+    protected final <T> T getLocalService(Class<T> type) {
+        return LocalServices.getService(type);
+    }
+
+    private SystemServiceManager getManager() {
+        return LocalServices.getService(SystemServiceManager.class);
+    }
+}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
new file mode 100644
index 0000000..fda6479
--- /dev/null
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.content.Context;
+import android.util.Slog;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+
+/**
+ * Manages creating, starting, and other lifecycle events of
+ * {@link com.android.server.SystemService system services}.
+ *
+ * {@hide}
+ */
+public class SystemServiceManager {
+    private static final String TAG = "SystemServiceManager";
+
+    private final Context mContext;
+    private boolean mSafeMode;
+
+    // Services that should receive lifecycle events.
+    private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+
+    private int mCurrentPhase = -1;
+
+    public SystemServiceManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Starts a service by class name.
+     *
+     * @return The service instance.
+     */
+    @SuppressWarnings("unchecked")
+    public SystemService startService(String className) {
+        final Class<SystemService> serviceClass;
+        try {
+            serviceClass = (Class<SystemService>)Class.forName(className);
+        } catch (ClassNotFoundException ex) {
+            Slog.i(TAG, "Starting " + className);
+            throw new RuntimeException("Failed to create service " + className
+                    + ": service class not found, usually indicates that the caller should "
+                    + "have called PackageManager.hasSystemFeature() to check whether the "
+                    + "feature is available on this device before trying to start the "
+                    + "services that implement it", ex);
+        }
+        return startService(serviceClass);
+    }
+
+    /**
+     * Creates and starts a system service. The class must be a subclass of
+     * {@link com.android.server.SystemService}.
+     *
+     * @param serviceClass A Java class that implements the SystemService interface.
+     * @return The service instance, never null.
+     * @throws RuntimeException if the service fails to start.
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends SystemService> T startService(Class<T> serviceClass) {
+        final String name = serviceClass.getName();
+        Slog.i(TAG, "Starting " + name);
+
+        // Create the service.
+        if (!SystemService.class.isAssignableFrom(serviceClass)) {
+            throw new RuntimeException("Failed to create " + name
+                    + ": service must extend " + SystemService.class.getName());
+        }
+        final T service;
+        try {
+            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
+            service = constructor.newInstance(mContext);
+        } catch (InstantiationException ex) {
+            throw new RuntimeException("Failed to create service " + name
+                    + ": service could not be instantiated", ex);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException("Failed to create service " + name
+                    + ": service must have a public constructor with a Context argument", ex);
+        } catch (NoSuchMethodException ex) {
+            throw new RuntimeException("Failed to create service " + name
+                    + ": service must have a public constructor with a Context argument", ex);
+        } catch (InvocationTargetException ex) {
+            throw new RuntimeException("Failed to create service " + name
+                    + ": service constructor threw an exception", ex);
+        }
+
+        // Register it.
+        mServices.add(service);
+
+        // Start it.
+        try {
+            service.onStart();
+        } catch (RuntimeException ex) {
+            throw new RuntimeException("Failed to start service " + name
+                    + ": onStart threw an exception", ex);
+        }
+        return service;
+    }
+
+    /**
+     * Starts the specified boot phase for all system services that have been started up to
+     * this point.
+     *
+     * @param phase The boot phase to start.
+     */
+    public void startBootPhase(final int phase) {
+        if (phase <= mCurrentPhase) {
+            throw new IllegalArgumentException("Next phase must be larger than previous");
+        }
+        mCurrentPhase = phase;
+
+        Slog.i(TAG, "Starting phase " + mCurrentPhase);
+
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onBootPhase(mCurrentPhase);
+            } catch (Exception ex) {
+                throw new RuntimeException("Failed to boot service "
+                        + service.getClass().getName()
+                        + ": onBootPhase threw an exception during phase "
+                        + mCurrentPhase, ex);
+            }
+        }
+    }
+
+    public void startUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onStartUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void switchUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onSwitchUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void stopUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onStopUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    public void cleanupUser(final int userHandle) {
+        final int serviceLen = mServices.size();
+        for (int i = 0; i < serviceLen; i++) {
+            final SystemService service = mServices.get(i);
+            try {
+                service.onCleanupUser(userHandle);
+            } catch (Exception ex) {
+                Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+                        + " to service " + service.getClass().getName(), ex);
+            }
+        }
+    }
+
+    /** Sets the safe mode flag for services to query. */
+    public void setSafeMode(boolean safeMode) {
+        mSafeMode = safeMode;
+    }
+
+    /**
+     * Returns whether we are booting into safe mode.
+     * @return safe mode flag
+     */
+    public boolean isSafeMode() {
+        return mSafeMode;
+    }
+
+    /**
+     * Outputs the state of this manager to the System log.
+     */
+    public void dump() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Current phase: ").append(mCurrentPhase).append("\n");
+        builder.append("Services:\n");
+        final int startedLen = mServices.size();
+        for (int i = 0; i < startedLen; i++) {
+            final SystemService service = mServices.get(i);
+            builder.append("\t")
+                    .append(service.getClass().getSimpleName())
+                    .append("\n");
+        }
+
+        Slog.e(TAG, builder.toString());
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ad2704a..ecd8f11 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2031,7 +2031,7 @@
 
             ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
                     "android", STOCK_PM_FLAGS);
-            mSystemThread.installSystemApplicationInfo(info);
+            mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());
 
             synchronized (this) {
                 ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 3e40d3f..d1e03ec 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,35 +16,23 @@
 
 package com.android.server.pm;
 
-import android.os.Build;
-import com.android.server.SystemService;
-
 import android.content.Context;
 import android.content.pm.PackageStats;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
+import android.os.Build;
 import android.util.Slog;
 import dalvik.system.VMRuntime;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
+import com.android.internal.os.InstallerConnection;
+import com.android.server.SystemService;
 
 public final class Installer extends SystemService {
     private static final String TAG = "Installer";
 
-    private static final boolean LOCAL_DEBUG = false;
-
-    InputStream mIn;
-    OutputStream mOut;
-    LocalSocket mSocket;
-
-    byte buf[] = new byte[1024];
-    int buflen = 0;
+    private final InstallerConnection mInstaller;
 
     public Installer(Context context) {
         super(context);
+        mInstaller = new InstallerConnection();
     }
 
     @Override
@@ -53,154 +41,6 @@
         ping();
     }
 
-    private boolean connect() {
-        if (mSocket != null) {
-            return true;
-        }
-        Slog.i(TAG, "connecting...");
-        try {
-            mSocket = new LocalSocket();
-
-            LocalSocketAddress address = new LocalSocketAddress("installd",
-                    LocalSocketAddress.Namespace.RESERVED);
-
-            mSocket.connect(address);
-
-            mIn = mSocket.getInputStream();
-            mOut = mSocket.getOutputStream();
-        } catch (IOException ex) {
-            disconnect();
-            return false;
-        }
-        return true;
-    }
-
-    private void disconnect() {
-        Slog.i(TAG, "disconnecting...");
-        try {
-            if (mSocket != null)
-                mSocket.close();
-        } catch (IOException ex) {
-        }
-        try {
-            if (mIn != null)
-                mIn.close();
-        } catch (IOException ex) {
-        }
-        try {
-            if (mOut != null)
-                mOut.close();
-        } catch (IOException ex) {
-        }
-        mSocket = null;
-        mIn = null;
-        mOut = null;
-    }
-
-    private boolean readBytes(byte buffer[], int len) {
-        int off = 0, count;
-        if (len < 0)
-            return false;
-        while (off != len) {
-            try {
-                count = mIn.read(buffer, off, len - off);
-                if (count <= 0) {
-                    Slog.e(TAG, "read error " + count);
-                    break;
-                }
-                off += count;
-            } catch (IOException ex) {
-                Slog.e(TAG, "read exception");
-                break;
-            }
-        }
-        if (LOCAL_DEBUG) {
-            Slog.i(TAG, "read " + len + " bytes");
-        }
-        if (off == len)
-            return true;
-        disconnect();
-        return false;
-    }
-
-    private boolean readReply() {
-        int len;
-        buflen = 0;
-        if (!readBytes(buf, 2))
-            return false;
-        len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
-        if ((len < 1) || (len > 1024)) {
-            Slog.e(TAG, "invalid reply length (" + len + ")");
-            disconnect();
-            return false;
-        }
-        if (!readBytes(buf, len))
-            return false;
-        buflen = len;
-        return true;
-    }
-
-    private boolean writeCommand(String _cmd) {
-        byte[] cmd = _cmd.getBytes();
-        int len = cmd.length;
-        if ((len < 1) || (len > 1024))
-            return false;
-        buf[0] = (byte) (len & 0xff);
-        buf[1] = (byte) ((len >> 8) & 0xff);
-        try {
-            mOut.write(buf, 0, 2);
-            mOut.write(cmd, 0, len);
-        } catch (IOException ex) {
-            Slog.e(TAG, "write error");
-            disconnect();
-            return false;
-        }
-        return true;
-    }
-
-    private synchronized String transaction(String cmd) {
-        if (!connect()) {
-            Slog.e(TAG, "connection failed");
-            return "-1";
-        }
-
-        if (!writeCommand(cmd)) {
-            /*
-             * If installd died and restarted in the background (unlikely but
-             * possible) we'll fail on the next write (this one). Try to
-             * reconnect and write the command one more time before giving up.
-             */
-            Slog.e(TAG, "write command failed? reconnect!");
-            if (!connect() || !writeCommand(cmd)) {
-                return "-1";
-            }
-        }
-        if (LOCAL_DEBUG) {
-            Slog.i(TAG, "send: '" + cmd + "'");
-        }
-        if (readReply()) {
-            String s = new String(buf, 0, buflen);
-            if (LOCAL_DEBUG) {
-                Slog.i(TAG, "recv: '" + s + "'");
-            }
-            return s;
-        } else {
-            if (LOCAL_DEBUG) {
-                Slog.i(TAG, "fail");
-            }
-            return "-1";
-        }
-    }
-
-    private int execute(String cmd) {
-        String res = transaction(cmd);
-        try {
-            return Integer.parseInt(res);
-        } catch (NumberFormatException ex) {
-            return -1;
-        }
-    }
-
     public int install(String name, int uid, int gid, String seinfo) {
         StringBuilder builder = new StringBuilder("install");
         builder.append(' ');
@@ -211,7 +51,7 @@
         builder.append(gid);
         builder.append(' ');
         builder.append(seinfo != null ? seinfo : "!");
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
@@ -231,7 +71,7 @@
         builder.append(pkgName);
         builder.append(' ');
         builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
@@ -240,16 +80,7 @@
             return -1;
         }
 
-        StringBuilder builder = new StringBuilder("patchoat");
-        builder.append(' ');
-        builder.append(apkPath);
-        builder.append(' ');
-        builder.append(uid);
-        builder.append(isPublic ? " 1" : " 0");
-        builder.append(" *");         // No pkgName arg present
-        builder.append(' ');
-        builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
     }
 
     public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
@@ -258,16 +89,7 @@
             return -1;
         }
 
-        StringBuilder builder = new StringBuilder("dexopt");
-        builder.append(' ');
-        builder.append(apkPath);
-        builder.append(' ');
-        builder.append(uid);
-        builder.append(isPublic ? " 1" : " 0");
-        builder.append(" *");         // No pkgName arg present
-        builder.append(' ');
-        builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet);
     }
 
     public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
@@ -287,7 +109,7 @@
         builder.append(pkgName);
         builder.append(' ');
         builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int idmap(String targetApkPath, String overlayApkPath, int uid) {
@@ -298,7 +120,7 @@
         builder.append(overlayApkPath);
         builder.append(' ');
         builder.append(uid);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int movedex(String srcPath, String dstPath, String instructionSet) {
@@ -314,7 +136,7 @@
         builder.append(dstPath);
         builder.append(' ');
         builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int rmdex(String codePath, String instructionSet) {
@@ -328,7 +150,7 @@
         builder.append(codePath);
         builder.append(' ');
         builder.append(instructionSet);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int remove(String name, int userId) {
@@ -337,7 +159,7 @@
         builder.append(name);
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int rename(String oldname, String newname) {
@@ -346,7 +168,7 @@
         builder.append(oldname);
         builder.append(' ');
         builder.append(newname);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int fixUid(String name, int uid, int gid) {
@@ -357,7 +179,7 @@
         builder.append(uid);
         builder.append(' ');
         builder.append(gid);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int deleteCacheFiles(String name, int userId) {
@@ -366,7 +188,7 @@
         builder.append(name);
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int deleteCodeCacheFiles(String name, int userId) {
@@ -375,7 +197,7 @@
         builder.append(name);
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int createUserData(String name, int uid, int userId, String seinfo) {
@@ -388,21 +210,21 @@
         builder.append(userId);
         builder.append(' ');
         builder.append(seinfo != null ? seinfo : "!");
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int createUserConfig(int userId) {
         StringBuilder builder = new StringBuilder("mkuserconfig");
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int removeUserDataDirs(int userId) {
         StringBuilder builder = new StringBuilder("rmuser");
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int clearUserData(String name, int userId) {
@@ -411,11 +233,11 @@
         builder.append(name);
         builder.append(' ');
         builder.append(userId);
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public boolean ping() {
-        if (execute("ping") < 0) {
+        if (mInstaller.execute("ping") < 0) {
             return false;
         } else {
             return true;
@@ -423,14 +245,14 @@
     }
 
     public int pruneDexCache(String cacheSubDir) {
-        return execute("prunedexcache " + cacheSubDir);
+        return mInstaller.execute("prunedexcache " + cacheSubDir);
     }
 
     public int freeCache(long freeStorageSize) {
         StringBuilder builder = new StringBuilder("freecache");
         builder.append(' ');
         builder.append(String.valueOf(freeStorageSize));
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
@@ -462,7 +284,7 @@
         // just the primary.
         builder.append(instructionSets[0]);
 
-        String s = transaction(builder.toString());
+        String s = mInstaller.transact(builder.toString());
         String res[] = s.split(" ");
 
         if ((res == null) || (res.length != 5)) {
@@ -480,7 +302,7 @@
     }
 
     public int moveFiles() {
-        return execute("movefiles");
+        return mInstaller.execute("movefiles");
     }
 
     /**
@@ -506,7 +328,7 @@
         builder.append(' ');
         builder.append(userId);
 
-        return execute(builder.toString());
+        return mInstaller.execute(builder.toString());
     }
 
     public boolean restoreconData(String pkgName, String seinfo, int uid) {
@@ -517,7 +339,7 @@
         builder.append(seinfo != null ? seinfo : "!");
         builder.append(' ');
         builder.append(uid);
-        return (execute(builder.toString()) == 0);
+        return (mInstaller.execute(builder.toString()) == 0);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 89bd1d4..304441c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1394,16 +1394,27 @@
              * list of process files because dexopt will have been run
              * if necessary during zygote startup.
              */
-            String bootClassPath = System.getProperty("java.boot.class.path");
+            final String bootClassPath = System.getenv("BOOTCLASSPATH");
+            final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
+
             if (bootClassPath != null) {
-                String[] paths = splitString(bootClassPath, ':');
-                for (int i=0; i<paths.length; i++) {
-                    alreadyDexOpted.add(paths[i]);
+                String[] bootClassPathElements = splitString(bootClassPath, ':');
+                for (String element : bootClassPathElements) {
+                    alreadyDexOpted.add(element);
                 }
             } else {
                 Slog.w(TAG, "No BOOTCLASSPATH found!");
             }
 
+            if (systemServerClassPath != null) {
+                String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
+                for (String element : systemServerClassPathElements) {
+                    alreadyDexOpted.add(element);
+                }
+            } else {
+                Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
+            }
+
             boolean didDexOptLibraryOrTool = false;
 
             final List<String> allInstructionSets = getAllInstructionSets();
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index ce2ca9b..39b70a8 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,6 +41,8 @@
 int register_android_server_hdmi_HdmiMhlController(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
 int register_android_server_PersistentDataBlockService(JNIEnv* env);
+int register_android_server_fingerprint_FingerprintService(JNIEnv* env);
+int register_android_server_Watchdog(JNIEnv* env);
 };
 
 using namespace android;
@@ -77,6 +79,8 @@
     register_android_server_hdmi_HdmiMhlController(env);
     register_android_server_tv_TvInputHal(env);
     register_android_server_PersistentDataBlockService(env);
+    register_android_server_fingerprint_FingerprintService(env);
+    register_android_server_Watchdog(env);
 
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/Android.mk b/services/devicepolicy/Android.mk
index a55d138..7020f17 100644
--- a/services/devicepolicy/Android.mk
+++ b/services/devicepolicy/Android.mk
@@ -7,6 +7,6 @@
 LOCAL_SRC_FILES += \
       $(call all-java-files-under,java)
 
-LOCAL_JAVA_LIBRARIES := conscrypt
+LOCAL_JAVA_LIBRARIES := conscrypt services.core
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/print/Android.mk b/services/print/Android.mk
index 33604b7..00eb2e4 100644
--- a/services/print/Android.mk
+++ b/services/print/Android.mk
@@ -7,4 +7,6 @@
 LOCAL_SRC_FILES += \
       $(call all-java-files-under,java)
 
+LOCAL_JAVA_LIBRARIES := services.core
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk
index fcf8626..57d1c46 100644
--- a/services/restrictions/Android.mk
+++ b/services/restrictions/Android.mk
@@ -7,4 +7,6 @@
 LOCAL_SRC_FILES += \
       $(call all-java-files-under,java)
 
+LOCAL_JAVA_LIBRARIES := services.core
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usage/Android.mk b/services/usage/Android.mk
index d4b7fa8..f1cbe98 100644
--- a/services/usage/Android.mk
+++ b/services/usage/Android.mk
@@ -7,4 +7,6 @@
 LOCAL_SRC_FILES += \
       $(call all-java-files-under,java)
 
+LOCAL_JAVA_LIBRARIES := services.core
+
 include $(BUILD_STATIC_JAVA_LIBRARY)