Start combining threads in system process.

This introduces four generic thread that services can
use in the system process:

- Background: part of the framework for all processes, for
work that is purely background (no timing constraint).
- UI: for time-critical display of UI.
- Foreground: normal foreground work.
- IO: performing IO operations.

I went through and moved services into these threads in the
places I felt relatively comfortable about understanding what
they are doing.  There are still a bunch more we need to look
at -- lots of networking stuff left, 3 or so different native
daemon connectors which I didn't know how much would block,
audio stuff, etc.

Also updated Watchdog to be aware of and check these new
threads, with a new API for other threads to also participate
in this checking.

Change-Id: Ie2f11061cebde5f018d7383b3a910fbbd11d5e11
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index d5715a5..5b76f39 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -27,7 +27,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -37,6 +36,7 @@
 
 import com.android.internal.appwidget.IAppWidgetHost;
 import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -63,9 +63,7 @@
     AppWidgetService(Context context) {
         mContext = context;
 
-        HandlerThread handlerThread = new HandlerThread("AppWidgetService -- Save state");
-        handlerThread.start();
-        mSaveStateHandler = new Handler(handlerThread.getLooper());
+        mSaveStateHandler = BackgroundThread.getHandler();
 
         mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
         AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index ea7b696..8684e5b 100644
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -33,7 +33,6 @@
 import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -115,7 +114,6 @@
     // used inside handler thread
     private boolean mEnable;
     private int mState;
-    private HandlerThread mThread;
     private final BluetoothHandler mHandler;
 
     private void registerForAirplaneMode(IntentFilter filter) {
@@ -188,9 +186,7 @@
     };
 
     BluetoothManagerService(Context context) {
-        mThread = new HandlerThread("BluetoothManager");
-        mThread.start();
-        mHandler = new BluetoothHandler(mThread.getLooper());
+        mHandler = new BluetoothHandler(IoThread.get().getLooper());
 
         mContext = context;
         mBluetooth = null;
diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java
index fc76277..8407fa4 100644
--- a/services/java/com/android/server/CountryDetectorService.java
+++ b/services/java/com/android/server/CountryDetectorService.java
@@ -20,6 +20,7 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.server.location.ComprehensiveCountryDetector;
 
 import android.content.Context;
@@ -29,8 +30,6 @@
 import android.location.ICountryListener;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Process;
 import android.os.RemoteException;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -169,8 +168,7 @@
 
     void systemReady() {
         // Shall we wait for the initialization finish.
-        Thread thread = new Thread(this, "CountryDetectorService");
-        thread.start();
+        BackgroundThread.getHandler().post(this);
     }
 
     private void initialize() {
@@ -187,12 +185,9 @@
     }
 
     public void run() {
-        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-        Looper.prepare();
         mHandler = new Handler();
         initialize();
         mSystemReady = true;
-        Looper.loop();
     }
 
     protected void setCountryListener(final CountryListener listener) {
diff --git a/services/java/com/android/server/FgThread.java b/services/java/com/android/server/FgThread.java
new file mode 100644
index 0000000..3b655f2
--- /dev/null
+++ b/services/java/com/android/server/FgThread.java
@@ -0,0 +1,65 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Shared singleton foreground thread for the system.  This is a thread for regular
+ * foreground service operations, which shouldn't be blocked by anything running in
+ * the background.  In particular, the shared background thread could be doing
+ * relatively long-running operations like saving state to disk (in addition to
+ * simply being a background priority), which can cause operations scheduled on it
+ * to be delayed for a user-noticeable amount of time.
+ */
+public final class FgThread extends HandlerThread {
+    private static FgThread sInstance;
+    private static Handler sHandler;
+
+    private FgThread() {
+        super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new FgThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    android.os.Process.setCanSelfBackground(false);
+                }
+            });
+        }
+    }
+
+    public static FgThread get() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/java/com/android/server/IoThread.java b/services/java/com/android/server/IoThread.java
new file mode 100644
index 0000000..b443578
--- /dev/null
+++ b/services/java/com/android/server/IoThread.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Shared singleton I/O thread for the system.  This is a thread for non-background
+ * service operations that can potential block briefly on network IO operations
+ * (not waiting for data itself, but communicating with network daemons).
+ */
+public final class IoThread extends HandlerThread {
+    private static IoThread sInstance;
+    private static Handler sHandler;
+
+    private IoThread() {
+        super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new IoThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    android.os.Process.setCanSelfBackground(false);
+                }
+            });
+        }
+    }
+
+    public static IoThread get() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 2675309..b162c6b 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -63,6 +63,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
+import com.android.internal.os.BackgroundThread;
 import com.android.server.location.GeocoderProxy;
 import com.android.server.location.GeofenceProxy;
 import com.android.server.location.GeofenceManager;
@@ -93,7 +94,6 @@
     public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String WAKELOCK_KEY = TAG;
-    private static final String THREAD_NAME = TAG;
 
     // Location resolution level: no location data whatsoever
     private static final int RESOLUTION_LEVEL_NONE = 0;
@@ -143,7 +143,6 @@
     private LocationWorkerHandler mLocationHandler;
     private PassiveProvider mPassiveProvider;  // track passive provider for special cases
     private LocationBlacklist mBlacklist;
-    private HandlerThread mHandlerThread;
 
     // --- fields below are protected by mWakeLock ---
     private int mPendingBroadcasts;
@@ -216,9 +215,7 @@
             mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
 
             // prepare worker thread
-            mHandlerThread = new HandlerThread(THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND);
-            mHandlerThread.start();
-            mLocationHandler = new LocationWorkerHandler(mHandlerThread.getLooper());
+            mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
 
             // prepare mLocationHandler's dependents
             mLocationFudger = new LocationFudger(mContext, mLocationHandler);
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index e8d7882..9f40000 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -37,7 +37,6 @@
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -485,7 +484,6 @@
         }
     };
 
-    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
 
     void waitForAsecScan() {
@@ -820,7 +818,7 @@
             }
 
             if (code == VoldResponseCode.VolumeDiskInserted) {
-                new Thread() {
+                new Thread("MountService#VolumeDiskInserted") {
                     @Override
                     public void run() {
                         try {
@@ -1105,7 +1103,7 @@
             /*
              * USB mass storage disconnected while enabled
              */
-            new Thread() {
+            new Thread("MountService#AvailabilityChange") {
                 @Override
                 public void run() {
                     try {
@@ -1304,9 +1302,7 @@
         // XXX: This will go away soon in favor of IMountServiceObserver
         mPms = (PackageManagerService) ServiceManager.getService("package");
 
-        mHandlerThread = new HandlerThread("MountService");
-        mHandlerThread.start();
-        mHandler = new MountServiceHandler(mHandlerThread.getLooper());
+        mHandler = new MountServiceHandler(IoThread.get().getLooper());
 
         // Watch for user changes
         final IntentFilter userFilter = new IntentFilter();
@@ -1328,7 +1324,7 @@
                 idleMaintenanceFilter, null, mHandler);
 
         // Add OBB Action Handler to MountService thread.
-        mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
+        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
 
         /*
          * Create the connection to vold with a maximum queue of twice the
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index c3f2afa..abcd8ee 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -19,7 +19,6 @@
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.LocalLog;
@@ -81,9 +80,7 @@
 
     @Override
     public void run() {
-        HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
-        thread.start();
-        mCallbackHandler = new Handler(thread.getLooper(), this);
+        mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
 
         while (true) {
             try {
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
index 3bfd190..02b42b8 100644
--- a/services/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -27,7 +27,6 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
@@ -36,6 +35,7 @@
 import android.util.NtpTrustedTime;
 import android.util.TrustedTime;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.TelephonyIntents;
 
 /**
@@ -71,7 +71,6 @@
 
     // NTP lookup is done on this thread and handler
     private Handler mHandler;
-    private HandlerThread mThread;
     private AlarmManager mAlarmManager;
     private PendingIntent mPendingPollIntent;
     private SettingsObserver mSettingsObserver;
@@ -114,9 +113,7 @@
         registerForAlarms();
         registerForConnectivityIntents();
 
-        mThread = new HandlerThread(TAG);
-        mThread.start();
-        mHandler = new MyHandler(mThread.getLooper());
+        mHandler = new MyHandler(BackgroundThread.get().getLooper());
         // Check the network time on the new thread
         mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b263680..3f9d666 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,30 +154,6 @@
         InputManagerService inputManager = null;
         TelephonyRegistry telephonyRegistry = null;
 
-        // Create a shared handler thread for UI within the system server.
-        // This thread is used by at least the following components:
-        // - WindowManagerPolicy
-        // - KeyguardViewManager
-        // - DisplayManagerService
-        HandlerThread uiHandlerThread = new HandlerThread("UI");
-        uiHandlerThread.start();
-        Handler uiHandler = new Handler(uiHandlerThread.getLooper());
-        uiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                //Looper.myLooper().setMessageLogging(new LogPrinter(
-                //        Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM));
-                android.os.Process.setThreadPriority(
-                        android.os.Process.THREAD_PRIORITY_FOREGROUND);
-                android.os.Process.setCanSelfBackground(false);
-
-                // For debug builds, log event loop stalls to dropbox for analysis.
-                if (StrictMode.conditionallyEnableDebugLogging()) {
-                    Slog.i(TAG, "Enabled StrictMode logging for UI Looper");
-                }
-            }
-        });
-
         // Create a handler thread just for the window manager to enjoy.
         HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
         wmHandlerThread.start();
@@ -231,7 +207,7 @@
 
         try {
             Slog.i(TAG, "Display Manager");
-            display = new DisplayManagerService(context, wmHandler, uiHandler);
+            display = new DisplayManagerService(context, wmHandler);
             ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
 
             Slog.i(TAG, "Telephony Registry");
@@ -319,14 +295,14 @@
             Slog.i(TAG, "Init Watchdog");
             Watchdog.getInstance().init(context, battery, power, alarm,
                     ActivityManagerService.self());
+            Watchdog.getInstance().addThread(wmHandler, "WindowManager thread");
 
             Slog.i(TAG, "Input Manager");
             inputManager = new InputManagerService(context, wmHandler);
 
             Slog.i(TAG, "Window Manager");
             wm = WindowManagerService.main(context, power, display, inputManager,
-                    uiHandler, wmHandler,
-                    factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
+                    wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
                     !firstBoot, onlyCore);
             ServiceManager.addService(Context.WINDOW_SERVICE, wm);
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
diff --git a/services/java/com/android/server/UiThread.java b/services/java/com/android/server/UiThread.java
new file mode 100644
index 0000000..60d73aa
--- /dev/null
+++ b/services/java/com/android/server/UiThread.java
@@ -0,0 +1,71 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+import android.os.StrictMode;
+import android.util.Slog;
+
+/**
+ * Shared singleton thread for showing UI.  This is a foreground thread, and in
+ * additional should not have operations that can take more than a few ms scheduled
+ * on it to avoid UI jank.
+ */
+public final class UiThread extends HandlerThread {
+    private static UiThread sInstance;
+    private static Handler sHandler;
+
+    private UiThread() {
+        super("android.ui", android.os.Process.THREAD_PRIORITY_FOREGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new UiThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    //Looper.myLooper().setMessageLogging(new LogPrinter(
+                    //        Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM));
+                    android.os.Process.setCanSelfBackground(false);
+
+                    // For debug builds, log event loop stalls to dropbox for analysis.
+                    if (StrictMode.conditionallyEnableDebugLogging()) {
+                        Slog.i("UiThread", "Enabled StrictMode logging for UI thread");
+                    }
+                }
+            });
+        }
+    }
+
+    public static UiThread get() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index d4d3850..fc95790 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -80,15 +80,13 @@
     static Watchdog sWatchdog;
 
     /* This handler will be used to post message back onto the main thread */
-    final Handler mHandler;
-    final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
+    final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>();
+    final HandlerChecker mMonitorChecker;
     ContentResolver mResolver;
     BatteryService mBattery;
     PowerManagerService mPower;
     AlarmManagerService mAlarm;
     ActivityManagerService mActivity;
-    boolean mCompleted;
-    Monitor mCurrentMonitor;
 
     int mPhonePid;
 
@@ -111,40 +109,65 @@
     int mReqRecheckInterval= -1;  // >= 0 if a specific recheck interval has been requested
 
     /**
-     * Used for scheduling monitor callbacks and checking memory usage.
+     * Used for checking status of handle threads and scheduling monitor callbacks.
      */
-    final class HeartbeatHandler extends Handler {
-        HeartbeatHandler(Looper looper) {
-            super(looper);
+    public final class HandlerChecker implements Runnable {
+        private final Handler mHandler;
+        private final String mName;
+        private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
+        private final boolean mCheckReboot;
+        private boolean mCompleted;
+        private Monitor mCurrentMonitor;
+
+        HandlerChecker(Handler handler, String name, boolean checkReboot) {
+            mHandler = handler;
+            mName = name;
+            mCheckReboot = checkReboot;
+        }
+
+        public void addMonitor(Monitor monitor) {
+            mMonitors.add(monitor);
+        }
+
+        public void scheduleCheckLocked() {
+            mCompleted = false;
+            mCurrentMonitor = null;
+            mHandler.postAtFrontOfQueue(this);
+        }
+
+        public boolean isCompletedLocked() {
+            return mCompleted;
+        }
+
+        public String describeBlockedStateLocked() {
+            return mCurrentMonitor == null ? mName : mCurrentMonitor.getClass().getName();
         }
 
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MONITOR: {
-                    // See if we should force a reboot.
-                    int rebootInterval = mReqRebootInterval >= 0
-                            ? mReqRebootInterval : REBOOT_DEFAULT_INTERVAL;
-                    if (mRebootInterval != rebootInterval) {
-                        mRebootInterval = rebootInterval;
-                        // We have been running long enough that a reboot can
-                        // be considered...
-                        checkReboot(false);
-                    }
+        public void run() {
+            // See if we should force a reboot.
+            if (mCheckReboot) {
+                int rebootInterval = mReqRebootInterval >= 0
+                        ? mReqRebootInterval : REBOOT_DEFAULT_INTERVAL;
+                if (mRebootInterval != rebootInterval) {
+                    mRebootInterval = rebootInterval;
+                    // We have been running long enough that a reboot can
+                    // be considered...
+                    checkReboot(false);
+                }
+            }
 
-                    final int size = mMonitors.size();
-                    for (int i = 0 ; i < size ; i++) {
-                        synchronized (Watchdog.this) {
-                            mCurrentMonitor = mMonitors.get(i);
-                        }
-                        mCurrentMonitor.monitor();
-                    }
+            final int size = mMonitors.size();
+            for (int i = 0 ; i < size ; i++) {
+                synchronized (Watchdog.this) {
+                    mCurrentMonitor = mMonitors.get(i);
+                }
+                mCurrentMonitor.monitor();
+            }
 
-                    synchronized (Watchdog.this) {
-                        mCompleted = true;
-                        mCurrentMonitor = null;
-                    }
-                } break;
+            synchronized (Watchdog.this) {
+                mCompleted = true;
+                mCurrentMonitor = null;
             }
         }
     }
@@ -189,9 +212,23 @@
 
     private Watchdog() {
         super("watchdog");
-        // Explicitly bind the HeartbeatHandler to run on the ServerThread, so
-        // that it can't get accidentally bound to another thread.
-        mHandler = new HeartbeatHandler(Looper.getMainLooper());
+        // Initialize handler checkers for each common thread we want to check.  Note
+        // that we are not currently checking the background thread, since it can
+        // potentially hold longer running operations with no guarantees about the timeliness
+        // of operations there.
+
+        // The shared foreground thread is the main checker.  It is where we
+        // will also dispatch monitor checks and do other work.
+        mMonitorChecker = new HandlerChecker(FgThread.getHandler(), "foreground thread", true);
+        mHandlerCheckers.add(mMonitorChecker);
+        // Add checker for main thread.  We only do a quick check since there
+        // can be UI running on the thread.
+        mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
+                "main thread", false));
+        // Add checker for shared UI thread.
+        mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(), "ui thread", false));
+        // And also check IO thread.
+        mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(), "i/o thread", false));
     }
 
     public void init(Context context, BatteryService battery,
@@ -226,9 +263,18 @@
     public void addMonitor(Monitor monitor) {
         synchronized (this) {
             if (isAlive()) {
-                throw new RuntimeException("Monitors can't be added while the Watchdog is running");
+                throw new RuntimeException("Monitors can't be added once the Watchdog is running");
             }
-            mMonitors.add(monitor);
+            mMonitorChecker.addMonitor(monitor);
+        }
+    }
+
+    public void addThread(Handler thread, String name) {
+        synchronized (this) {
+            if (isAlive()) {
+                throw new RuntimeException("Threads can't be added once the Watchdog is running");
+            }
+            mHandlerCheckers.add(new HandlerChecker(thread, name, false));
         }
     }
 
@@ -382,6 +428,30 @@
         return newTime;
     }
 
+    private boolean haveAllCheckersCompletedLocked() {
+        for (int i=0; i<mHandlerCheckers.size(); i++) {
+            HandlerChecker hc = mHandlerCheckers.get(i);
+            if (!hc.isCompletedLocked()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private String describeBlockedCheckersLocked() {
+        StringBuilder builder = new StringBuilder(128);
+        for (int i=0; i<mHandlerCheckers.size(); i++) {
+            HandlerChecker hc = mHandlerCheckers.get(i);
+            if (!hc.isCompletedLocked()) {
+                if (builder.length() > 0) {
+                    builder.append(", ");
+                }
+                builder.append(hc.describeBlockedStateLocked());
+            }
+        }
+        return builder.toString();
+    }
+
     @Override
     public void run() {
         boolean waitedHalf = false;
@@ -389,8 +459,14 @@
             final String name;
             synchronized (this) {
                 long timeout = TIME_TO_WAIT;
-                mCompleted = false;
-                mHandler.sendEmptyMessage(MONITOR);
+                if (!waitedHalf) {
+                    // If we are not at the half-point of waiting, perform a
+                    // new set of checks.  Otherwise we are still waiting for a previous set.
+                    for (int i=0; i<mHandlerCheckers.size(); i++) {
+                        HandlerChecker hc = mHandlerCheckers.get(i);
+                        hc.scheduleCheckLocked();
+                    }
+                }
 
                 // NOTE: We use uptimeMillis() here because we do not want to increment the time we
                 // wait while asleep. If the device is asleep then the thing that we are waiting
@@ -406,7 +482,7 @@
                     timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start);
                 }
 
-                if (mCompleted) {
+                if (haveAllCheckersCompletedLocked()) {
                     // The monitors have returned.
                     waitedHalf = false;
                     continue;
@@ -423,8 +499,7 @@
                     continue;
                 }
 
-                name = (mCurrentMonitor != null) ?
-                    mCurrentMonitor.getClass().getName() : "main thread blocked";
+                name = describeBlockedCheckersLocked();
             }
 
             // If we got here, that means that the system is most likely hung.
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 2e8d6df..d1236c1 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -56,7 +56,6 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -74,6 +73,7 @@
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
@@ -113,7 +113,6 @@
     private final PackageManager mPackageManager;
     private UserManager mUserManager;
 
-    private HandlerThread mMessageThread;
     private final MessageHandler mMessageHandler;
 
     // Messages that can be sent on mHandler
@@ -234,9 +233,7 @@
         mContext = context;
         mPackageManager = packageManager;
 
-        mMessageThread = new HandlerThread("AccountManagerService");
-        mMessageThread.start();
-        mMessageHandler = new MessageHandler(mMessageThread.getLooper());
+        mMessageHandler = new MessageHandler(FgThread.get().getLooper());
 
         mAuthenticatorCache = authenticatorCache;
         mAuthenticatorCache.setListener(this, null /* Handler */);
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 32f39b7..87263b30 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -37,13 +37,10 @@
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.os.Binder;
-import android.os.HandlerThread;
-import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -53,6 +50,7 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.IoThread;
 import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
@@ -100,7 +98,6 @@
     private final INetworkStatsService mStatsService;
     private final IConnectivityManager mConnService;
     private Looper mLooper;
-    private HandlerThread mThread;
 
     private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
 
@@ -147,9 +144,7 @@
         mIfaces = new HashMap<String, TetherInterfaceSM>();
 
         // make our own thread so we don't anr the system
-        mThread = new HandlerThread("Tethering");
-        mThread.start();
-        mLooper = mThread.getLooper();
+        mLooper = IoThread.get().getLooper();
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
         mTetherMasterSM.start();
 
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 1c883ec..cf593ce 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -53,12 +53,10 @@
 import android.net.NetworkInfo;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -74,6 +72,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
@@ -82,7 +81,6 @@
 import com.google.android.collect.Sets;
 
 import java.io.FileDescriptor;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -381,10 +379,7 @@
         mSyncAdapters = new SyncAdaptersCache(mContext);
         mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters);
 
-        HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        syncThread.start();
-        mSyncHandler = new SyncHandler(syncThread.getLooper());
+        mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper());
 
         mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
             @Override
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 17b0662..ca85e42 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -37,6 +37,7 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
+import com.android.server.UiThread;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -190,12 +191,12 @@
     private final DisplayViewport mTempDefaultViewport = new DisplayViewport();
     private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport();
 
-    public DisplayManagerService(Context context, Handler mainHandler, Handler uiHandler) {
+    public DisplayManagerService(Context context, Handler mainHandler) {
         mContext = context;
         mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
 
         mHandler = new DisplayManagerHandler(mainHandler.getLooper());
-        mUiHandler = uiHandler;
+        mUiHandler = UiThread.getHandler();
         mDisplayAdapterListener = new DisplayAdapterListener();
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
 
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index a82f421..5ca7242 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -106,7 +106,6 @@
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
 import android.os.Message;
@@ -134,6 +133,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Objects;
+import com.android.server.IoThread;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
@@ -274,7 +274,6 @@
     private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
             INetworkPolicyListener>();
 
-    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
 
     private final AtomicFile mPolicyFile;
@@ -306,9 +305,7 @@
         mNetworkManager = checkNotNull(networkManagement, "missing networkManagement");
         mTime = checkNotNull(time, "missing TrustedTime");
 
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
+        mHandler = new Handler(IoThread.get().getLooper(), mHandlerCallback);
 
         mSuppressDefaultPolicy = suppressDefaultPolicy;
 
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 74be472..5074409 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -96,7 +96,6 @@
 import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.PowerManager;
@@ -120,6 +119,7 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
+import com.android.server.IoThread;
 import com.android.server.connectivity.Tethering;
 import com.google.android.collect.Maps;
 
@@ -240,7 +240,6 @@
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
 
-    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
 
     private boolean mSystemReady;
@@ -271,9 +270,7 @@
                 Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
+        mHandler = new Handler(IoThread.get().getLooper(), mHandlerCallback);
 
         mSystemDir = checkNotNull(systemDir);
         mBaseDir = new File(systemDir, "netstats");
diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java
index 93d3114..ba3f1d1 100644
--- a/services/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/java/com/android/server/usb/UsbDebuggingManager.java
@@ -22,15 +22,14 @@
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.util.Base64;
+import com.android.server.FgThread;
 
 import java.lang.Thread;
 import java.io.File;
@@ -54,7 +53,6 @@
 
     private final Context mContext;
     private final Handler mHandler;
-    private final HandlerThread mHandlerThread;
     private Thread mThread;
     private boolean mAdbEnabled = false;
     private String mFingerprints;
@@ -62,9 +60,7 @@
     private OutputStream mOutputStream = null;
 
     public UsbDebuggingManager(Context context) {
-        mHandlerThread = new HandlerThread("UsbDebuggingHandler");
-        mHandlerThread.start();
-        mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper());
+        mHandler = new UsbDebuggingHandler(FgThread.get().getLooper());
         mContext = context;
     }
 
@@ -165,7 +161,7 @@
 
                     mAdbEnabled = true;
 
-                    mThread = new Thread(UsbDebuggingManager.this);
+                    mThread = new Thread(UsbDebuggingManager.this, "UsbDebuggingManager");
                     mThread.start();
 
                     break;
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 87aa8cce..3a5357a 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -32,11 +32,9 @@
 import android.hardware.usb.UsbManager;
 import android.os.FileUtils;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UEventObserver;
@@ -48,6 +46,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.FgThread;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -158,11 +157,7 @@
 
         readOemUsbOverrideConfig();
 
-        // create a thread for our Handler
-        HandlerThread thread = new HandlerThread("UsbDeviceManager",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        mHandler = new UsbHandler(thread.getLooper());
+        mHandler = new UsbHandler(FgThread.get().getLooper());
 
         if (nativeIsStartRequested()) {
             if (DEBUG) Slog.d(TAG, "accessory attached at boot");
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 6a2d3e2..24f8cf1 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -34,6 +34,7 @@
 import com.android.internal.view.WindowManagerPolicyThread;
 import com.android.server.AttributeCache;
 import com.android.server.EventLogTags;
+import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.DisplayManagerService;
@@ -701,8 +702,7 @@
 
     public static WindowManagerService main(final Context context,
             final PowerManagerService pm, final DisplayManagerService dm,
-            final InputManagerService im,
-            final Handler uiHandler, final Handler wmHandler,
+            final InputManagerService im, final Handler wmHandler,
             final boolean haveInputMethods, final boolean showBootMsgs,
             final boolean onlyCore) {
         final WindowManagerService[] holder = new WindowManagerService[1];
@@ -710,7 +710,7 @@
             @Override
             public void run() {
                 holder[0] = new WindowManagerService(context, pm, dm, im,
-                        uiHandler, haveInputMethods, showBootMsgs, onlyCore);
+                        haveInputMethods, showBootMsgs, onlyCore);
             }
         }, 0);
         return holder[0];
@@ -732,7 +732,6 @@
 
     private WindowManagerService(Context context, PowerManagerService pm,
             DisplayManagerService displayManager, InputManagerService inputManager,
-            Handler uiHandler,
             boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
         mContext = context;
         mHaveInputMethods = haveInputMethods;
@@ -795,7 +794,7 @@
         mFxSession = new SurfaceSession();
         mAnimator = new WindowAnimator(this);
 
-        initPolicy(uiHandler);
+        initPolicy(UiThread.getHandler());
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);