Add initial multi-display support.

Split the DisplayManager into two parts.  One part is bound
to a Context and takes care of Display compatibility and
caching Display objects on behalf of the Context.  The other
part is global and takes care of communicating with the
DisplayManagerService, handling callbacks, and caching
DisplayInfo objects on behalf of the process.

Implemented support for enumerating Displays and getting
callbacks when displays are added, removed or changed.

Elaborated the roles of DisplayManagerService, DisplayAdapter,
and DisplayDevice.  We now support having multiple display
adapters registered, each of which can register multiple display
devices and configure them dynamically.

Added an OverlayDisplayAdapter which is used to simulate
secondary displays by means of overlay windows.  Different
configurations of overlays can be selected using a new
setting in the Developer Settings panel.  The overlays can
be repositioned and resized by the user for convenience.

At the moment, all displays are mirrors of display 0 and
no display transformations are applied.  This will be improved
in future patches.

Refactored the way that the window manager creates its threads.
The OverlayDisplayAdapter needs to be able to use hardware
acceleration so it must share the same UI thread as the Keyguard
and window manager policy.  We now handle this explicitly as
part of starting up the system server.  This puts us in a
better position to consider how we might want to share (or not
share) Loopers among components.

Overlay displays are disabled when in safe mode or in only-core
mode to reduce the number of dependencies started in these modes.

Change-Id: Ic2a661d5448dde01b095ab150697cb6791d69bb5
diff --git a/Android.mk b/Android.mk
index 0665e60..a637eb7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -115,6 +115,7 @@
 	core/java/android/database/IContentObserver.aidl \
 	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/display/IDisplayManager.aidl \
+	core/java/android/hardware/display/IDisplayManagerCallback.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
 	core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 14928a0..f5e3966 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9997,7 +9997,8 @@
 package android.hardware.display {
 
   public final class DisplayManager {
-    method public android.view.Display getDisplay(int, android.content.Context);
+    method public android.view.Display getDisplay(int);
+    method public android.view.Display[] getDisplays();
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
   }
@@ -23453,6 +23454,7 @@
     method public int getRotation();
     method public void getSize(android.graphics.Point);
     method public deprecated int getWidth();
+    method public boolean isValid();
     field public static final int DEFAULT_DISPLAY = 0; // 0x0
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e644db4..f8a9d75 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,6 +31,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
@@ -376,7 +377,8 @@
             return true;
         }
 
-        Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(
+                Display.DEFAULT_DISPLAY);
         Point p = new Point();
         display.getRealSize(p);
         int pixels = p.x * p.y;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b8e16c5..4a1bf75 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.net.IConnectivityManager;
 import android.net.Proxy;
 import android.net.ProxyProperties;
@@ -1557,7 +1558,7 @@
             return dm;
         }
 
-        DisplayManager displayManager = DisplayManager.getInstance();
+        DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
         if (displayManager == null) {
             // may be null early in system startup
             dm = new DisplayMetrics();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 32086d7..efe4b7b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -349,10 +349,11 @@
                     return InputManager.getInstance();
                 }});
 
-        registerService(DISPLAY_SERVICE, new StaticServiceFetcher() {
-            public Object createStaticService() {
-                return DisplayManager.getInstance();
-            }});
+        registerService(DISPLAY_SERVICE, new ServiceFetcher() {
+                @Override
+                public Object createService(ContextImpl ctx) {
+                    return new DisplayManager(ctx.getOuterContext());
+                }});
 
         registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 98d2f69..74996da 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -18,20 +18,12 @@
 
 import android.content.Context;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
+import android.util.SparseArray;
 import android.view.CompatibilityInfoHolder;
 import android.view.Display;
-import android.view.DisplayInfo;
-
-import java.util.ArrayList;
 
 /**
- * Manages the properties, media routing and power state of attached displays.
+ * Manages the properties of attached displays.
  * <p>
  * Get an instance of this class by calling
  * {@link android.content.Context#getSystemService(java.lang.String)
@@ -43,110 +35,79 @@
     private static final String TAG = "DisplayManager";
     private static final boolean DEBUG = false;
 
-    private static final int MSG_DISPLAY_ADDED = 1;
-    private static final int MSG_DISPLAY_REMOVED = 2;
-    private static final int MSG_DISPLAY_CHANGED = 3;
+    private final Context mContext;
+    private final DisplayManagerGlobal mGlobal;
 
-    private static DisplayManager sInstance;
+    private final Object mLock = new Object();
+    private final SparseArray<Display> mDisplays = new SparseArray<Display>();
 
-    private final IDisplayManager mDm;
-
-    // Guarded by mDisplayLock
-    private final Object mDisplayLock = new Object();
-    private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
-            new ArrayList<DisplayListenerDelegate>();
-
-
-    private DisplayManager(IDisplayManager dm) {
-        mDm = dm;
+    /** @hide */
+    public DisplayManager(Context context) {
+        mContext = context;
+        mGlobal = DisplayManagerGlobal.getInstance();
     }
 
     /**
-     * Gets an instance of the display manager.
+     * Gets information about a logical display.
      *
-     * @return The display manager instance, may be null early in system startup
-     * before the display manager has been fully initialized.
+     * The display metrics may be adjusted to provide compatibility
+     * for legacy applications.
      *
-     * @hide
+     * @param displayId The logical display id.
+     * @return The display object, or null if there is no valid display with the given id.
      */
-    public static DisplayManager getInstance() {
-        synchronized (DisplayManager.class) {
-            if (sInstance == null) {
-                IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
-                if (b != null) {
-                    sInstance = new DisplayManager(IDisplayManager.Stub.asInterface(b));
+    public Display getDisplay(int displayId) {
+        synchronized (mLock) {
+            return getOrCreateDisplayLocked(displayId, false /*assumeValid*/);
+        }
+    }
+
+    /**
+     * Gets all currently valid logical displays.
+     *
+     * @return An array containing all displays.
+     */
+    public Display[] getDisplays() {
+        int[] displayIds = mGlobal.getDisplayIds();
+        int expectedCount = displayIds.length;
+        Display[] displays = new Display[expectedCount];
+        synchronized (mLock) {
+            int actualCount = 0;
+            for (int i = 0; i < expectedCount; i++) {
+                Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+                if (display != null) {
+                    displays[actualCount++] = display;
                 }
             }
-            return sInstance;
+            if (actualCount != expectedCount) {
+                Display[] oldDisplays = displays;
+                displays = new Display[actualCount];
+                System.arraycopy(oldDisplays, 0, displays, 0, actualCount);
+            }
         }
+        return displays;
     }
 
-    /**
-     * Get information about a particular logical display.
-     *
-     * @param displayId The logical display id.
-     * @param outInfo A structure to populate with the display info.
-     * @return True if the logical display exists, false otherwise.
-     * @hide
-     */
-    public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) {
-        try {
-            return mDm.getDisplayInfo(displayId, outInfo);
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Could not get display information from display manager.", ex);
-            return false;
+    private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
+        Display display = mDisplays.get(displayId);
+        if (display == null) {
+            display = mGlobal.getCompatibleDisplay(displayId,
+                    getCompatibilityInfoForDisplayLocked(displayId));
+            if (display != null) {
+                mDisplays.put(displayId, display);
+            }
+        } else if (!assumeValid && !display.isValid()) {
+            display = null;
         }
+        return display;
     }
 
-    /**
-     * Gets information about a logical display.
-     *
-     * The display metrics may be adjusted to provide compatibility
-     * for legacy applications.
-     *
-     * @param displayId The logical display id.
-     * @param applicationContext The application context from which to obtain
-     * compatible metrics.
-     * @return The display object.
-     */
-    public Display getDisplay(int displayId, Context applicationContext) {
-        if (applicationContext == null) {
-            throw new IllegalArgumentException("applicationContext must not be null");
-        }
-
+    private CompatibilityInfoHolder getCompatibilityInfoForDisplayLocked(int displayId) {
         CompatibilityInfoHolder cih = null;
         if (displayId == Display.DEFAULT_DISPLAY) {
-            cih = applicationContext.getCompatibilityInfo();
+            cih = mContext.getCompatibilityInfo();
         }
-        return getCompatibleDisplay(displayId, cih);
-    }
-
-    /**
-     * Gets information about a logical display.
-     *
-     * The display metrics may be adjusted to provide compatibility
-     * for legacy applications.
-     *
-     * @param displayId The logical display id.
-     * @param cih The compatibility info, or null if none is required.
-     * @return The display object.
-     *
-     * @hide
-     */
-    public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
-        return new Display(displayId, cih);
-    }
-
-    /**
-     * Gets information about a logical display without applying any compatibility metrics.
-     *
-     * @param displayId The logical display id.
-     * @return The display object.
-     *
-     * @hide
-     */
-    public Display getRealDisplay(int displayId) {
-        return getCompatibleDisplay(displayId, null);
+        return cih;
     }
 
     /**
@@ -160,16 +121,7 @@
      * @see #unregisterDisplayListener
      */
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mDisplayLock) {
-            int index = findDisplayListenerLocked(listener);
-            if (index < 0) {
-                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
-            }
-        }
+        mGlobal.registerDisplayListener(listener, handler);
     }
 
     /**
@@ -180,28 +132,7 @@
      * @see #registerDisplayListener
      */
     public void unregisterDisplayListener(DisplayListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mDisplayLock) {
-            int index = findDisplayListenerLocked(listener);
-            if (index >= 0) {
-                DisplayListenerDelegate d = mDisplayListeners.get(index);
-                d.removeCallbacksAndMessages(null);
-                mDisplayListeners.remove(index);
-            }
-        }
-    }
-
-    private int findDisplayListenerLocked(DisplayListener listener) {
-        final int numListeners = mDisplayListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            if (mDisplayListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterDisplayListener(listener);
     }
 
     /**
@@ -210,7 +141,8 @@
     public interface DisplayListener {
         /**
          * Called whenever a logical display has been added to the system.
-         * Use {@link DisplayManager#getDisplay} to get more information about the display.
+         * Use {@link DisplayManager#getDisplay} to get more information about
+         * the display.
          *
          * @param displayId The id of the logical display that was added.
          */
@@ -230,28 +162,4 @@
          */
         void onDisplayChanged(int displayId);
     }
-
-    private static final class DisplayListenerDelegate extends Handler {
-        public final DisplayListener mListener;
-
-        public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DISPLAY_ADDED:
-                    mListener.onDisplayAdded(msg.arg1);
-                    break;
-                case MSG_DISPLAY_REMOVED:
-                    mListener.onDisplayRemoved(msg.arg1);
-                    break;
-                case MSG_DISPLAY_CHANGED:
-                    mListener.onDisplayChanged(msg.arg1);
-                    break;
-            }
-        }
-    }
 }
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
new file mode 100644
index 0000000..69c0319
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.CompatibilityInfoHolder;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Manager communication with the display manager service on behalf of
+ * an application process.  You're probably looking for {@link DisplayManager}.
+ *
+ * @hide
+ */
+public final class DisplayManagerGlobal {
+    private static final String TAG = "DisplayManager";
+    private static final boolean DEBUG = false;
+
+    public static final int EVENT_DISPLAY_ADDED = 1;
+    public static final int EVENT_DISPLAY_CHANGED = 2;
+    public static final int EVENT_DISPLAY_REMOVED = 3;
+
+    private static DisplayManagerGlobal sInstance;
+
+    private final Object mLock = new Object();
+
+    private final IDisplayManager mDm;
+
+    private DisplayManagerCallback mCallback;
+    private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
+            new ArrayList<DisplayListenerDelegate>();
+
+    private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+    private int[] mDisplayIdCache;
+
+    private DisplayManagerGlobal(IDisplayManager dm) {
+        mDm = dm;
+    }
+
+    /**
+     * Gets an instance of the display manager global singleton.
+     *
+     * @return The display manager instance, may be null early in system startup
+     * before the display manager has been fully initialized.
+     */
+    public static DisplayManagerGlobal getInstance() {
+        synchronized (DisplayManagerGlobal.class) {
+            if (sInstance == null) {
+                IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
+                if (b != null) {
+                    sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
+                }
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Get information about a particular logical display.
+     *
+     * @param displayId The logical display id.
+     * @return Information about the specified display, or null if it does not exist.
+     * This object belongs to an internal cache and should be treated as if it were immutable.
+     */
+    public DisplayInfo getDisplayInfo(int displayId) {
+        try {
+            synchronized (mLock) {
+                DisplayInfo info = mDisplayInfoCache.get(displayId);
+                if (info != null) {
+                    return info;
+                }
+
+                info = mDm.getDisplayInfo(displayId);
+                if (info == null) {
+                    return null;
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
+                }
+
+                mDisplayInfoCache.put(displayId, info);
+                registerCallbackIfNeededLocked();
+                return info;
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Could not get display information from display manager.", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Gets all currently valid logical display ids.
+     *
+     * @return An array containing all display ids.
+     */
+    public int[] getDisplayIds() {
+        try {
+            synchronized (mLock) {
+                if (mDisplayIdCache == null) {
+                    mDisplayIdCache = mDm.getDisplayIds();
+                    registerCallbackIfNeededLocked();
+                }
+                return mDisplayIdCache;
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Could not get display ids from display manager.", ex);
+            return new int[] { Display.DEFAULT_DISPLAY };
+        }
+    }
+
+    /**
+     * Gets information about a logical display.
+     *
+     * The display metrics may be adjusted to provide compatibility
+     * for legacy applications.
+     *
+     * @param displayId The logical display id.
+     * @param cih The compatibility info, or null if none is required.
+     * @return The display object, or null if there is no display with the given id.
+     */
+    public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
+        DisplayInfo displayInfo = getDisplayInfo(displayId);
+        if (displayInfo == null) {
+            return null;
+        }
+        return new Display(this, displayId, displayInfo, cih);
+    }
+
+    /**
+     * Gets information about a logical display without applying any compatibility metrics.
+     *
+     * @param displayId The logical display id.
+     * @return The display object, or null if there is no display with the given id.
+     */
+    public Display getRealDisplay(int displayId) {
+        return getCompatibleDisplay(displayId, null);
+    }
+
+    public void registerDisplayListener(DisplayListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mLock) {
+            int index = findDisplayListenerLocked(listener);
+            if (index < 0) {
+                mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
+                registerCallbackIfNeededLocked();
+            }
+        }
+    }
+
+    public void unregisterDisplayListener(DisplayListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mLock) {
+            int index = findDisplayListenerLocked(listener);
+            if (index >= 0) {
+                DisplayListenerDelegate d = mDisplayListeners.get(index);
+                d.clearEvents();
+                mDisplayListeners.remove(index);
+            }
+        }
+    }
+
+    private int findDisplayListenerLocked(DisplayListener listener) {
+        final int numListeners = mDisplayListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mDisplayListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private void registerCallbackIfNeededLocked() {
+        if (mCallback == null) {
+            mCallback = new DisplayManagerCallback();
+            try {
+                mDm.registerCallback(mCallback);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to register callback with display manager service.", ex);
+                mCallback = null;
+            }
+        }
+    }
+
+    private void handleDisplayEvent(int displayId, int event) {
+        synchronized (mLock) {
+            mDisplayInfoCache.remove(displayId);
+
+            if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
+                mDisplayIdCache = null;
+            }
+
+            final int numListeners = mDisplayListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
+            }
+        }
+    }
+
+    private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
+        @Override
+        public void onDisplayEvent(int displayId, int event) {
+            if (DEBUG) {
+                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
+            }
+            handleDisplayEvent(displayId, event);
+        }
+    }
+
+    private static final class DisplayListenerDelegate extends Handler {
+        public final DisplayListener mListener;
+
+        public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
+            mListener = listener;
+        }
+
+        public void sendDisplayEvent(int displayId, int event) {
+            Message msg = obtainMessage(event, displayId, 0);
+            sendMessage(msg);
+        }
+
+        public void clearEvents() {
+            removeCallbacksAndMessages(null);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_DISPLAY_ADDED:
+                    mListener.onDisplayAdded(msg.arg1);
+                    break;
+                case EVENT_DISPLAY_CHANGED:
+                    mListener.onDisplayChanged(msg.arg1);
+                    break;
+                case EVENT_DISPLAY_REMOVED:
+                    mListener.onDisplayRemoved(msg.arg1);
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index fd8c35f..d802aa1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -16,9 +16,13 @@
 
 package android.hardware.display;
 
+import android.hardware.display.IDisplayManagerCallback;
 import android.view.DisplayInfo;
 
 /** @hide */
 interface IDisplayManager {
-    boolean getDisplayInfo(int displayId, out DisplayInfo outInfo);
+    DisplayInfo getDisplayInfo(int displayId);
+    int[] getDisplayIds();
+
+    void registerCallback(in IDisplayManagerCallback callback);
 }
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
new file mode 100644
index 0000000..c50e3fb
--- /dev/null
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 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 android.hardware.display;
+
+/** @hide */
+interface IDisplayManagerCallback {
+    oneway void onDisplayEvent(int displayId, int event);
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6f43891..b4841b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4351,6 +4351,25 @@
         public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_";
 
         /**
+         * Overlay display devices setting.
+         * The associated value is a specially formatted string that describes the
+         * size and density of simulated secondary display devices.
+         * <p>
+         * Format: {width}x{height}/{dpi};...
+         * </p><p>
+         * Example:
+         * <ul>
+         * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li>
+         * <li><code>1920x1080/320;1280x720/213</code>: make two overlays, the first
+         * at 1080p and the second at 720p.</li>
+         * <li>If the value is empty, then no overlay display devices are created.</li>
+         * </ul></p>
+         *
+         * @hide
+         */
+        public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 392d1f2..6848606 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -166,8 +167,7 @@
         mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
         mLastFrameTimeNanos = Long.MIN_VALUE;
 
-        Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
-        mFrameIntervalNanos = (long)(1000000000 / d.getRefreshRate());
+        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
 
         mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
         for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -175,6 +175,12 @@
         }
     }
 
+    private static float getRefreshRate() {
+        DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+                Display.DEFAULT_DISPLAY);
+        return di.refreshRate;
+    }
+
     /**
      * Gets the choreographer for the calling thread.  Must be called from
      * a thread that already has a {@link android.os.Looper} associated with it.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 6f8ca13..ec635a2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,7 +19,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -49,10 +49,14 @@
  */
 public final class Display {
     private static final String TAG = "Display";
+    private static final boolean DEBUG = false;
 
+    private final DisplayManagerGlobal mGlobal;
     private final int mDisplayId;
     private final CompatibilityInfoHolder mCompatibilityInfo;
-    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+
+    private DisplayInfo mDisplayInfo; // never null
+    private boolean mIsValid;
 
     // Temporary display metrics structure used for compatibility mode.
     private final DisplayMetrics mTempMetrics = new DisplayMetrics();
@@ -80,9 +84,14 @@
      *
      * @hide
      */
-    public Display(int displayId, CompatibilityInfoHolder compatibilityInfo) {
+    public Display(DisplayManagerGlobal global,
+            int displayId, DisplayInfo displayInfo /*not null*/,
+            CompatibilityInfoHolder compatibilityInfo) {
+        mGlobal = global;
         mDisplayId = displayId;
+        mDisplayInfo = displayInfo;
         mCompatibilityInfo = compatibilityInfo;
+        mIsValid = true;
     }
 
     /**
@@ -97,15 +106,37 @@
     }
 
     /**
+     * Returns true if this display is still valid, false if the display has been removed.
+     *
+     * If the display is invalid, then the methods of this class will
+     * continue to report the most recently observed display information.
+     * However, it is unwise (and rather fruitless) to continue using a
+     * {@link Display} object after the display's demise.
+     *
+     * It's possible for a display that was previously invalid to become
+     * valid again if a display with the same id is reconnected.
+     *
+     * @return True if the display is still valid.
+     */
+    public boolean isValid() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mIsValid;
+        }
+    }
+
+    /**
      * Gets a full copy of the display information.
      *
      * @param outDisplayInfo The object to receive the copy of the display information.
+     * @return True if the display is still valid.
      * @hide
      */
-    public void getDisplayInfo(DisplayInfo outDisplayInfo) {
+    public boolean getDisplayInfo(DisplayInfo outDisplayInfo) {
         synchronized (this) {
             updateDisplayInfoLocked();
             outDisplayInfo.copyFrom(mDisplayInfo);
+            return mIsValid;
         }
     }
 
@@ -366,9 +397,25 @@
     }
 
     private void updateDisplayInfoLocked() {
-        // TODO: only refresh the display information when needed
-        if (!DisplayManager.getInstance().getDisplayInfo(mDisplayId, mDisplayInfo)) {
-            Log.e(TAG, "Could not get information about logical display " + mDisplayId);
+        // Note: The display manager caches display info objects on our behalf.
+        DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
+        if (newInfo == null) {
+            // Preserve the old mDisplayInfo after the display is removed.
+            if (mIsValid) {
+                mIsValid = false;
+                if (DEBUG) {
+                    Log.d(TAG, "Logical display " + mDisplayId + " was removed.");
+                }
+            }
+        } else {
+            // Use the new display info.  (It might be the same object if nothing changed.)
+            mDisplayInfo = newInfo;
+            if (!mIsValid) {
+                mIsValid = true;
+                if (DEBUG) {
+                    Log.d(TAG, "Logical display " + mDisplayId + " was recreated.");
+                }
+            }
         }
     }
 
@@ -390,7 +437,7 @@
             updateDisplayInfoLocked();
             mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
             return "Display id " + mDisplayId + ": " + mDisplayInfo
-                    + ", " + mTempMetrics;
+                    + ", " + mTempMetrics + ", isValid=" + mIsValid;
         }
     }
 }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index e38f245..593e8c4 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -138,6 +138,10 @@
     public DisplayInfo() {
     }
 
+    public DisplayInfo(DisplayInfo other) {
+        copyFrom(other);
+    }
+
     private DisplayInfo(Parcel source) {
         readFromParcel(source);
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b1f5e9e..9338a51 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -7347,7 +7348,9 @@
             outRect.bottom -= insets.bottom;
             return;
         }
-        Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+        // The view is not attached to a display so we don't have a context.
+        // Make a best guess about the display size.
+        Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
         d.getRectSize(outRect);
     }
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 123d9e7..51503fd 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -179,7 +179,8 @@
             @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS"),
             @ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"),
             @ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"),
-            @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL")
+            @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"),
+            @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"),
         })
         public int type;
     
@@ -435,6 +436,12 @@
         public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
 
         /**
+         * Window type: Display overlay window.  Used to simulate secondary display devices.
+         * @hide
+         */
+        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index bf061df..aa9179f 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -52,8 +52,9 @@
     private final Window mParentWindow;
 
     public WindowManagerImpl(Context context, int displayId) {
+        DisplayManager dm = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mContext = context;
-        mDisplay = DisplayManager.getInstance().getDisplay(displayId, mContext);
+        mDisplay = dm.getDisplay(displayId);
         mParentWindow = null;
     }
 
diff --git a/core/res/res/layout/overlay_display_window.xml b/core/res/res/layout/overlay_display_window.xml
new file mode 100644
index 0000000..25c792a
--- /dev/null
+++ b/core/res/res/layout/overlay_display_window.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
+    <TextureView android:id="@+id/overlay_display_window_texture"
+               android:layout_width="0px"
+               android:layout_height="0px" />
+    <TextView android:id="@+id/overlay_display_window_title"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d761980..68aa410 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -120,6 +120,8 @@
   <java-symbol type="id" name="old_app_action" />
   <java-symbol type="id" name="old_app_description" />
   <java-symbol type="id" name="old_app_icon" />
+  <java-symbol type="id" name="overlay_display_window_texture" />
+  <java-symbol type="id" name="overlay_display_window_title" />
   <java-symbol type="id" name="package_label" />
   <java-symbol type="id" name="packages_list" />
   <java-symbol type="id" name="pause" />
@@ -479,7 +481,9 @@
   <java-symbol type="string" name="decline" />
   <java-symbol type="string" name="default_text_encoding" />
   <java-symbol type="string" name="description_target_unlock_tablet" />
-  <java-symbol type="string" name="display_manager_built_in_display" />
+  <java-symbol type="string" name="display_manager_built_in_display_name" />
+  <java-symbol type="string" name="display_manager_overlay_display_name" />
+  <java-symbol type="string" name="display_manager_overlay_display_title" />
   <java-symbol type="string" name="double_tap_toast" />
   <java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
   <java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
@@ -1093,6 +1097,7 @@
   <java-symbol type="layout" name="list_menu_item_radio" />
   <java-symbol type="layout" name="locale_picker_item" />
   <java-symbol type="layout" name="media_controller" />
+  <java-symbol type="layout" name="overlay_display_window" />
   <java-symbol type="layout" name="preference" />
   <java-symbol type="layout" name="preference_header_item" />
   <java-symbol type="layout" name="preference_list_content" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e77dde7..985dfc4 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3651,6 +3651,12 @@
     <!-- Display manager service -->
 
     <!-- Name of the built-in display.  [CHAR LIMIT=50] -->
-    <string name="display_manager_built_in_display">Built-in Screen</string>
+    <string name="display_manager_built_in_display_name">Built-in Screen</string>
+
+    <!-- Name of the N'th overlay display for testing.  [CHAR LIMIT=50] -->
+    <string name="display_manager_overlay_display_name">Overlay #<xliff:g id="id">%1$d</xliff:g></string>
+
+    <!-- Title text to show within the overlay.  [CHAR LIMIT=50] -->
+    <string name="display_manager_overlay_display_title">Overlay #<xliff:g id="id">%1$d</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
 
 </resources>
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 209ad38..65e8560 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -108,6 +108,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER;
@@ -218,14 +219,16 @@
     static final int NAVIGATION_BAR_PANEL_LAYER = 20;
     // system-level error dialogs
     static final int SYSTEM_ERROR_LAYER = 21;
+    // used to simulate secondary display devices
+    static final int DISPLAY_OVERLAY_LAYER = 22;
     // the drag layer: input for drag-and-drop is associated with this window,
     // which sits above all other focusable windows
-    static final int DRAG_LAYER = 22;
-    static final int SECURE_SYSTEM_OVERLAY_LAYER = 23;
-    static final int BOOT_PROGRESS_LAYER = 24;
+    static final int DRAG_LAYER = 23;
+    static final int SECURE_SYSTEM_OVERLAY_LAYER = 24;
+    static final int BOOT_PROGRESS_LAYER = 25;
     // the (mouse) pointer layer
-    static final int POINTER_LAYER = 25;
-    static final int HIDDEN_NAV_CONSUMER_LAYER = 26;
+    static final int POINTER_LAYER = 26;
+    static final int HIDDEN_NAV_CONSUMER_LAYER = 27;
 
     static final int APPLICATION_MEDIA_SUBLAYER = -2;
     static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -1327,6 +1330,8 @@
             return SCREENSAVER_LAYER;
         case TYPE_UNIVERSE_BACKGROUND:
             return UNIVERSE_BACKGROUND_LAYER;
+        case TYPE_DISPLAY_OVERLAY:
+            return DISPLAY_OVERLAY_LAYER;
         }
         Log.e(TAG, "Unknown window type: " + type);
         return APPLICATION_LAYER;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7097891..48b1215 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -28,6 +28,8 @@
 import android.content.res.Configuration;
 import android.media.AudioService;
 import android.net.wifi.p2p.WifiP2pService;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SchedulingPolicyService;
@@ -147,7 +149,52 @@
         CommonTimeManagementService commonTimeMgmtService = null;
         InputManagerService inputManager = 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();
+        Handler wmHandler = new Handler(wmHandlerThread.getLooper());
+        wmHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                //Looper.myLooper().setMessageLogging(new LogPrinter(
+                //        android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM));
+                android.os.Process.setThreadPriority(
+                        android.os.Process.THREAD_PRIORITY_DISPLAY);
+                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");
+                }
+            }
+        });
+
         // Critical services...
+        boolean onlyCore = false;
         try {
             Slog.i(TAG, "Entropy Mixer");
             ServiceManager.addService("entropy", new EntropyMixer());
@@ -160,7 +207,7 @@
             context = ActivityManagerService.main(factoryTest);
 
             Slog.i(TAG, "Display Manager");
-            display = new DisplayManagerService(context);
+            display = new DisplayManagerService(context, uiHandler);
             ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
 
             Slog.i(TAG, "Telephony Registry");
@@ -172,10 +219,14 @@
 
             AttributeCache.init(context);
 
+            if (!display.waitForDefaultDisplay()) {
+                reportWtf("Timeout waiting for default display to be initialized.",
+                        new Throwable());
+            }
+
             Slog.i(TAG, "Package Manager");
             // Only run "core" apps if we're encrypting the device.
             String cryptState = SystemProperties.get("vold.decrypt");
-            boolean onlyCore = false;
             if (ENCRYPTING_STATE.equals(cryptState)) {
                 Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
                 onlyCore = true;
@@ -244,6 +295,7 @@
 
             Slog.i(TAG, "Window Manager");
             wm = WindowManagerService.main(context, power, display,
+                    uiHandler, wmHandler,
                     factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
                     !firstBoot, onlyCore);
             ServiceManager.addService(Context.WINDOW_SERVICE, wm);
@@ -753,6 +805,12 @@
             reportWtf("making Package Manager Service ready", e);
         }
 
+        try {
+            display.systemReady(safeMode, onlyCore);
+        } catch (Throwable e) {
+            reportWtf("making Display Manager Service ready", e);
+        }
+
         // These are needed to propagate to the runnable below.
         final Context contextF = context;
         final BatteryService batteryF = battery;
diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java
index f9fa7a8..d19fe01 100644
--- a/services/java/com/android/server/display/DisplayAdapter.java
+++ b/services/java/com/android/server/display/DisplayAdapter.java
@@ -16,33 +16,96 @@
 
 package com.android.server.display;
 
+import android.content.Context;
+import android.os.Handler;
+
+import java.io.PrintWriter;
+
 /**
  * A display adapter makes zero or more display devices available to the system
  * and provides facilities for discovering when displays are connected or disconnected.
  * <p>
  * For now, all display adapters are registered in the system server but
  * in principle it could be done from other processes.
+ * </p><p>
+ * Display devices are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
  * </p>
  */
-public abstract class DisplayAdapter {
+public class DisplayAdapter {
+    private final Context mContext;
+    private final String mName;
+    private final Handler mHandler;
+    private Listener mListener;
+
+    public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
+    public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
+    public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
+
+    public DisplayAdapter(Context context, String name) {
+        mContext = context;
+        mName = name;
+        mHandler = new Handler();
+    }
+
+    public final Context getContext() {
+        return mContext;
+    }
+
+    public final Handler getHandler() {
+        return mHandler;
+    }
+
     /**
      * Gets the display adapter name for debugging purposes.
      *
      * @return The display adapter name.
      */
-    public abstract String getName();
+    public final String getName() {
+        return mName;
+    }
 
     /**
      * Registers the display adapter with the display manager.
-     * The display adapter should register any built-in display devices now.
-     * Other display devices can be registered dynamically later.
      *
-     * @param listener The listener for callbacks.
+     * @param listener The listener for callbacks.  The listener will
+     * be invoked on the display manager service's handler thread.
      */
-    public abstract void register(Listener listener);
+    public final void register(Listener listener) {
+        mListener = listener;
+        onRegister();
+    }
+
+    /**
+     * Dumps the local state of the display adapter.
+     */
+    public void dump(PrintWriter pw) {
+    }
+
+    /**
+     * Called when the display adapter is registered.
+     *
+     * The display adapter should register any built-in display devices as soon as possible.
+     * The boot process will wait for the default display to be registered.
+     *
+     * Other display devices can be registered dynamically later.
+     */
+    protected void onRegister() {
+    }
+
+    /**
+     * Sends a display device event to the display adapter listener asynchronously.
+     */
+    protected void sendDisplayDeviceEvent(final DisplayDevice device, final int event) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayDeviceEvent(device, event);
+            }
+        });
+    }
 
     public interface Listener {
-        public void onDisplayDeviceAdded(DisplayDevice device);
-        public void onDisplayDeviceRemoved(DisplayDevice device);
+        public void onDisplayDeviceEvent(DisplayDevice device, int event);
     }
 }
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index 4a6dd66..c83ce96 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -21,14 +21,28 @@
 /**
  * Represents a physical display device such as the built-in display
  * an external monitor, or a WiFi display.
+ * <p>
+ * Display devices are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
  */
 public abstract class DisplayDevice {
+    private final DisplayAdapter mDisplayAdapter;
+    private final IBinder mDisplayToken;
+
+    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
+        mDisplayAdapter = displayAdapter;
+        mDisplayToken = displayToken;
+    }
+
     /**
-     * Gets the display adapter that makes the display device available.
+     * Gets the display adapter that owns the display device.
      *
      * @return The display adapter.
      */
-    public abstract DisplayAdapter getAdapter();
+    public final DisplayAdapter getAdapter() {
+        return mDisplayAdapter;
+    }
 
     /**
      * Gets the Surface Flinger display token for this display.
@@ -36,7 +50,9 @@
      * @return The display token, or null if the display is not being managed
      * by Surface Flinger.
      */
-    public abstract IBinder getDisplayToken();
+    public final IBinder getDisplayToken() {
+        return mDisplayToken;
+    }
 
     /**
      * Gets information about the display device.
@@ -44,4 +60,12 @@
      * @param outInfo The object to populate with the information.
      */
     public abstract void getInfo(DisplayDeviceInfo outInfo);
+
+    // For debugging purposes.
+    @Override
+    public String toString() {
+        DisplayDeviceInfo info = new DisplayDeviceInfo();
+        getInfo(info);
+        return info.toString() + ", owner=\"" + mDisplayAdapter.getName() + "\"";
+    }
 }
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index 9c0f964..c7b8c24 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -20,6 +20,9 @@
  * Describes the characteristics of a physical display device.
  */
 public final class DisplayDeviceInfo {
+    public static final int FLAG_DEFAULT_DISPLAY = 1 << 0;
+    public static final int FLAG_SECURE = 1 << 1;
+
     /**
      * Gets the name of the display device, which may be derived from
      * EDID or other sources.  The name may be displayed to the user.
@@ -43,6 +46,8 @@
     public float xDpi;
     public float yDpi;
 
+    public int flags;
+
     public void copyFrom(DisplayDeviceInfo other) {
         name = other.name;
         width = other.width;
@@ -51,12 +56,25 @@
         densityDpi = other.densityDpi;
         xDpi = other.xDpi;
         yDpi = other.yDpi;
+        flags = other.flags;
     }
 
     // For debugging purposes
     @Override
     public String toString() {
         return "\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
-                + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi";
+                + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
+                + flagsToString(flags);
+    }
+
+    private static String flagsToString(int flags) {
+        StringBuilder msg = new StringBuilder();
+        if ((flags & FLAG_DEFAULT_DISPLAY) != 0) {
+            msg.append(", FLAG_DEFAULT_DISPLAY");
+        }
+        if ((flags & FLAG_SECURE) != 0) {
+            msg.append(", FLAG_SECURE");
+        }
+        return msg.toString();
     }
 }
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 1463780..cf835ef2 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -16,52 +16,151 @@
 
 package com.android.server.display;
 
+import com.android.internal.util.IndentingPrintWriter;
+
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.IDisplayManager;
+import android.hardware.display.IDisplayManagerCallback;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 
 /**
- * Manages the properties, media routing and power state of attached displays.
+ * Manages attached displays.
  * <p>
- * The display manager service does not own or directly control the displays.
- * Instead, other components in the system register their display adapters with the
- * display manager service which acts as a central controller.
+ * The {@link DisplayManagerService} manages the global lifecycle of displays,
+ * decides how to configure logical displays based on the physical display devices currently
+ * attached, sends notifications to the system and to applications when the state
+ * changes, and so on.
+ * </p><p>
+ * The display manager service relies on a collection of {@link DisplayAdapter} components,
+ * for discovering and configuring physical display devices attached to the system.
+ * There are separate display adapters for each manner that devices are attached:
+ * one display adapter for built-in local displays, one for simulated non-functional
+ * displays when the system is headless, one for simulated overlay displays used for
+ * development, one for wifi displays, etc.
+ * </p><p>
+ * Display adapters are only weakly coupled to the display manager service.
+ * Display adapters communicate changes in display device state to the display manager
+ * service asynchronously via a {@link DisplayAdapter.DisplayAdapterListener} registered
+ * by the display manager service.  This separation of concerns is important for
+ * two main reasons.  First, it neatly encapsulates the responsibilities of these
+ * two classes: display adapters handle individual display devices whereas
+ * the display manager service handles the global state.  Second, it eliminates
+ * the potential for deadlocks resulting from asynchronous display device discovery.
+ * </p><p>
+ * To keep things simple, display adapters and display devices are single-threaded
+ * and are only accessed on the display manager's handler thread.  Of course, the
+ * display manager must be accessible by multiple thread (especially for
+ * incoming binder calls) so all of the display manager's state is synchronized
+ * and guarded by a lock.
  * </p>
  */
 public final class DisplayManagerService extends IDisplayManager.Stub {
     private static final String TAG = "DisplayManagerService";
+    private static final boolean DEBUG = false;
 
     private static final String SYSTEM_HEADLESS = "ro.config.headless";
+    private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
+
+    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1;
+    private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
+    private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
 
     private final Object mLock = new Object();
 
     private final Context mContext;
     private final boolean mHeadless;
 
-    private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
-    private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
-    private DisplayDevice mDefaultDisplayDevice;
+    private final DisplayManagerHandler mHandler;
+    private final DisplayAdapterListener mDisplayAdapterListener = new DisplayAdapterListener();
+    private final SparseArray<CallbackRecord> mCallbacks =
+            new SparseArray<CallbackRecord>();
 
-    public DisplayManagerService(Context context) {
+    // List of all currently registered display adapters.
+    private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
+
+    // List of all currently connected display devices.
+    private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>();
+
+    // List of all logical displays, indexed by logical display id.
+    private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>();
+    private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+
+    // True if in safe mode.
+    // This option may disable certain display adapters.
+    private boolean mSafeMode;
+
+    // True if we are in a special boot mode where only core applications and
+    // services should be started.  This option may disable certain display adapters.
+    private boolean mOnlyCore;
+
+    // Temporary callback list, used when sending display events to applications.
+    private ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
+
+    public DisplayManagerService(Context context, Handler uiHandler) {
         mContext = context;
         mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
 
-        registerDefaultDisplayAdapter();
+        mHandler = new DisplayManagerHandler(uiHandler.getLooper());
+        mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
     }
 
+    /**
+     * Pauses the boot process to wait for the first display to be initialized.
+     */
+    public boolean waitForDefaultDisplay() {
+        synchronized (mLock) {
+            long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
+            while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) {
+                long delay = timeout - SystemClock.uptimeMillis();
+                if (delay <= 0) {
+                    return false;
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay);
+                }
+                try {
+                    mLock.wait(delay);
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called when the system is ready to go.
+     */
+    public void systemReady(boolean safeMode, boolean onlyCore) {
+        synchronized (mLock) {
+            mSafeMode = safeMode;
+            mOnlyCore = onlyCore;
+        }
+        mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
+    }
+
+    // Runs on handler.
     private void registerDefaultDisplayAdapter() {
+        // Register default display adapter.
         if (mHeadless) {
             registerDisplayAdapter(new HeadlessDisplayAdapter(mContext));
         } else {
@@ -69,6 +168,34 @@
         }
     }
 
+    // Runs on handler.
+    private void registerAdditionalDisplayAdapters() {
+        if (shouldRegisterNonEssentialDisplayAdapters()) {
+            registerDisplayAdapter(new OverlayDisplayAdapter(mContext));
+        }
+    }
+
+    private boolean shouldRegisterNonEssentialDisplayAdapters() {
+        // In safe mode, we disable non-essential display adapters to give the user
+        // an opportunity to fix broken settings or other problems that might affect
+        // system stability.
+        // In only-core mode, we disable non-essential display adapters to minimize
+        // the number of dependencies that are started while in this mode and to
+        // prevent problems that might occur due to the device being encrypted.
+        synchronized (mLock) {
+            return !mSafeMode && !mOnlyCore;
+        }
+    }
+
+    // Runs on handler.
+    private void registerDisplayAdapter(DisplayAdapter adapter) {
+        synchronized (mLock) {
+            mDisplayAdapters.add(adapter);
+        }
+
+        adapter.register(mDisplayAdapterListener);
+    }
+
     // FIXME: this isn't the right API for the long term
     public void getDefaultExternalDisplayDeviceInfo(DisplayDeviceInfo info) {
         // hardcoded assuming 720p touch screen plugged into HDMI and USB
@@ -87,95 +214,224 @@
     }
 
     /**
-     * Set the new display orientation.
+     * Sets the new logical display orientation.
+     *
      * @param displayId The logical display id.
      * @param orientation One of the Surface.ROTATION_* constants.
      */
     public void setDisplayOrientation(int displayId, int orientation) {
         synchronized (mLock) {
-            if (displayId != Display.DEFAULT_DISPLAY) {
-                throw new UnsupportedOperationException();
+            // TODO: update mirror transforms
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null && display.mPrimaryDisplayDevice != null) {
+                IBinder displayToken = display.mPrimaryDisplayDevice.getDisplayToken();
+                if (displayToken != null) {
+                    Surface.openTransaction();
+                    try {
+                        Surface.setDisplayOrientation(displayToken, orientation);
+                    } finally {
+                        Surface.closeTransaction();
+                    }
+                }
+
+                display.mBaseDisplayInfo.rotation = orientation;
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+            }
+        }
+    }
+
+    /**
+     * Overrides the display information of a particular logical display.
+     * This is used by the window manager to control the size and characteristics
+     * of the default display.
+     *
+     * @param displayId The logical display id.
+     * @param info The new data to be stored.
+     */
+    public void setDisplayInfoOverrideFromWindowManager(int displayId, DisplayInfo info) {
+        synchronized (mLock) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null) {
+                if (info != null) {
+                    if (display.mOverrideDisplayInfo == null) {
+                        display.mOverrideDisplayInfo = new DisplayInfo();
+                    }
+                    display.mOverrideDisplayInfo.copyFrom(info);
+                } else {
+                    display.mOverrideDisplayInfo = null;
+                }
+
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+            }
+        }
+    }
+
+    /**
+     * Returns information about the specified logical display.
+     *
+     * @param displayId The logical display id.
+     * @param The logical display info, or null if the display does not exist.
+     */
+    @Override // Binder call
+    public DisplayInfo getDisplayInfo(int displayId) {
+        synchronized (mLock) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null) {
+                if (display.mOverrideDisplayInfo != null) {
+                    return new DisplayInfo(display.mOverrideDisplayInfo);
+                }
+                return new DisplayInfo(display.mBaseDisplayInfo);
+            }
+            return null;
+        }
+    }
+
+    @Override // Binder call
+    public int[] getDisplayIds() {
+        synchronized (mLock) {
+            final int count = mLogicalDisplays.size();
+            int[] displayIds = new int[count];
+            for (int i = 0; i > count; i++) {
+                displayIds[i] = mLogicalDisplays.keyAt(i);
+            }
+            return displayIds;
+        }
+    }
+
+    @Override // Binder call
+    public void registerCallback(IDisplayManagerCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mLock) {
+            int callingPid = Binder.getCallingPid();
+            if (mCallbacks.get(callingPid) != null) {
+                throw new SecurityException("The calling process has already "
+                        + "registered an IDisplayManagerCallback.");
             }
 
-            IBinder displayToken = mDefaultDisplayDevice.getDisplayToken();
-            if (displayToken != null) {
-                Surface.openTransaction();
-                try {
-                    Surface.setDisplayOrientation(displayToken, orientation);
-                } finally {
-                    Surface.closeTransaction();
+            CallbackRecord record = new CallbackRecord(callingPid, callback);
+            try {
+                IBinder binder = callback.asBinder();
+                binder.linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                // give up
+                throw new RuntimeException(ex);
+            }
+
+            mCallbacks.put(callingPid, record);
+        }
+    }
+
+    private void onCallbackDied(int pid) {
+        synchronized (mLock) {
+            mCallbacks.remove(pid);
+        }
+    }
+
+    // Runs on handler.
+    private void handleDisplayDeviceAdded(DisplayDevice device) {
+        synchronized (mLock) {
+            if (mDisplayDevices.contains(device)) {
+                Slog.w(TAG, "Attempted to add already added display device: " + device);
+                return;
+            }
+
+            mDisplayDevices.add(device);
+
+            LogicalDisplay display = new LogicalDisplay(device);
+            display.updateFromPrimaryDisplayDevice();
+
+            boolean isDefault = (display.mPrimaryDisplayDeviceInfo.flags
+                    & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+            if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
+                Slog.w(TAG, "Attempted to add a second default device: " + device);
+                isDefault = false;
+            }
+
+            int displayId = isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
+            mLogicalDisplays.put(displayId, display);
+
+            sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+
+            // Wake up waitForDefaultDisplay.
+            if (isDefault) {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    // Runs on handler.
+    private void handleDisplayDeviceChanged(DisplayDevice device) {
+        synchronized (mLock) {
+            if (!mDisplayDevices.contains(device)) {
+                Slog.w(TAG, "Attempted to change non-existent display device: " + device);
+                return;
+            }
+
+            for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+                LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                if (display.mPrimaryDisplayDevice == device) {
+                    final int displayId = mLogicalDisplays.keyAt(i);
+                    display.updateFromPrimaryDisplayDevice();
+                    sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
                 }
             }
         }
     }
 
-    /**
-     * Save away new DisplayInfo data.
-     * @param displayId The logical display id.
-     * @param info The new data to be stored.
-     */
-    public void setDisplayInfo(int displayId, DisplayInfo info) {
+    // Runs on handler.
+    private void handleDisplayDeviceRemoved(DisplayDevice device) {
         synchronized (mLock) {
-            if (displayId != Display.DEFAULT_DISPLAY) {
-                throw new UnsupportedOperationException();
+            if (!mDisplayDevices.remove(device)) {
+                Slog.w(TAG, "Attempted to remove non-existent display device: " + device);
+                return;
             }
-            mDefaultDisplayInfo.copyFrom(info);
+
+            for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+                LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                if (display.mPrimaryDisplayDevice == device) {
+                    final int displayId = mLogicalDisplays.keyAt(i);
+                    mLogicalDisplays.removeAt(i);
+                    sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+                }
+            }
         }
     }
 
-    /**
-     * Return requested DisplayInfo.
-     * @param displayId The data to retrieve.
-     * @param outInfo The structure to receive the data.
-     */
-    @Override // Binder call
-    public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) {
-        synchronized (mLock) {
-            if (displayId != Display.DEFAULT_DISPLAY) {
-                return false;
-            }
-            outInfo.copyFrom(mDefaultDisplayInfo);
-            return true;
+    // Posts a message to send a display event at the next opportunity.
+    private void sendDisplayEventLocked(int displayId, int event) {
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+        mHandler.sendMessage(msg);
+    }
+
+    // Runs on handler.
+    // This method actually sends display event notifications.
+    // Note that it must be very careful not to be holding the lock while sending
+    // is in progress.
+    private void deliverDisplayEvent(int displayId, int event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event);
         }
-    }
 
-    private void registerDisplayAdapter(DisplayAdapter adapter) {
-        mDisplayAdapters.add(adapter);
-        adapter.register(new DisplayAdapter.Listener() {
-            @Override
-            public void onDisplayDeviceAdded(DisplayDevice device) {
-                mDefaultDisplayDevice = device;
-                DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-                device.getInfo(deviceInfo);
-                copyDisplayInfoFromDeviceInfo(mDefaultDisplayInfo, deviceInfo);
+        final int count;
+        synchronized (mLock) {
+            count = mCallbacks.size();
+            mTempCallbacks.clear();
+            for (int i = 0; i < count; i++) {
+                mTempCallbacks.add(mCallbacks.valueAt(i));
             }
+        }
 
-            @Override
-            public void onDisplayDeviceRemoved(DisplayDevice device) {
-            }
-        });
-    }
-
-    private void copyDisplayInfoFromDeviceInfo(
-            DisplayInfo displayInfo, DisplayDeviceInfo deviceInfo) {
-        // Bootstrap the logical display using the physical display.
-        displayInfo.appWidth = deviceInfo.width;
-        displayInfo.appHeight = deviceInfo.height;
-        displayInfo.logicalWidth = deviceInfo.width;
-        displayInfo.logicalHeight = deviceInfo.height;
-        displayInfo.rotation = Surface.ROTATION_0;
-        displayInfo.refreshRate = deviceInfo.refreshRate;
-        displayInfo.logicalDensityDpi = deviceInfo.densityDpi;
-        displayInfo.physicalXDpi = deviceInfo.xDpi;
-        displayInfo.physicalYDpi = deviceInfo.yDpi;
-        displayInfo.smallestNominalAppWidth = deviceInfo.width;
-        displayInfo.smallestNominalAppHeight = deviceInfo.height;
-        displayInfo.largestNominalAppWidth = deviceInfo.width;
-        displayInfo.largestNominalAppHeight = deviceInfo.height;
+        for (int i = 0; i < count; i++) {
+            mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
+        }
+        mTempCallbacks.clear();
     }
 
     @Override // Binder call
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (mContext == null
                 || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
                         != PackageManager.PERMISSION_GRANTED) {
@@ -184,19 +440,159 @@
             return;
         }
 
-        pw.println("DISPLAY MANAGER (dumpsys display)\n");
+        pw.println("DISPLAY MANAGER (dumpsys display)");
+        pw.println("  mHeadless=" + mHeadless);
 
-        pw.println("Headless: " + mHeadless);
+        mHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                dumpLocal(pw);
+            }
+        });
+    }
 
+    // Runs on handler.
+    private void dumpLocal(PrintWriter pw) {
         synchronized (mLock) {
+            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+
+            pw.println();
+            pw.println("Display Adapters: size=" + mDisplayAdapters.size());
             for (DisplayAdapter adapter : mDisplayAdapters) {
-                pw.println("Adapter: " + adapter.getName());
+                pw.println("  " + adapter.getName());
+                adapter.dump(ipw);
             }
 
-            pw.println("Default display info: " + mDefaultDisplayInfo);
+            pw.println();
+            pw.println("Display Devices: size=" + mDisplayDevices.size());
+            for (DisplayDevice device : mDisplayDevices) {
+                pw.println("  " + device);
+            }
+
+            final int logicalDisplayCount = mLogicalDisplays.size();
+            pw.println();
+            pw.println("Logical Displays: size=" + logicalDisplayCount);
+            for (int i = 0; i < logicalDisplayCount; i++) {
+                int displayId = mLogicalDisplays.keyAt(i);
+                LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                pw.println("  Display " + displayId + ":");
+                pw.println("    mPrimaryDisplayDevice=" + display.mPrimaryDisplayDevice);
+                pw.println("    mBaseDisplayInfo=" + display.mBaseDisplayInfo);
+                pw.println("    mOverrideDisplayInfo="
+                        + display.mOverrideDisplayInfo);
+            }
+        }
+    }
+
+    private final class DisplayManagerHandler extends Handler {
+        public DisplayManagerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
         }
 
-        pw.println("Default display: "
-                + DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY));
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:
+                    registerDefaultDisplayAdapter();
+                    break;
+
+                case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:
+                    registerAdditionalDisplayAdapters();
+                    break;
+
+                case MSG_DELIVER_DISPLAY_EVENT:
+                    deliverDisplayEvent(msg.arg1, msg.arg2);
+                    break;
+            }
+        }
+    }
+
+    private final class DisplayAdapterListener implements DisplayAdapter.Listener {
+        @Override
+        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+            switch (event) {
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
+                    handleDisplayDeviceAdded(device);
+                    break;
+
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
+                    handleDisplayDeviceChanged(device);
+                    break;
+
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
+                    handleDisplayDeviceRemoved(device);
+                    break;
+            }
+        }
+    }
+
+    private final class CallbackRecord implements DeathRecipient {
+        private final int mPid;
+        private final IDisplayManagerCallback mCallback;
+
+        public CallbackRecord(int pid, IDisplayManagerCallback callback) {
+            mPid = pid;
+            mCallback = callback;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Display listener for pid " + mPid + " died.");
+            }
+            onCallbackDied(mPid);
+        }
+
+        public void notifyDisplayEventAsync(int displayId, int event) {
+            try {
+                mCallback.onDisplayEvent(displayId, event);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that displays changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+
+    /**
+     * Each logical display is primarily associated with one display device.
+     * The primary display device is nominally responsible for the basic properties
+     * of the logical display such as its size, refresh rate, and dpi.
+     *
+     * A logical display may be mirrored onto other display devices besides its
+     * primary display device, but it always remains bound to its primary.
+     * Note that the contents of a logical display may not always be visible, even
+     * on its primary display device, such as in the case where the logical display's
+     * primary display device is currently mirroring content from a different logical display.
+     */
+    private final static class LogicalDisplay {
+        public final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
+        public DisplayInfo mOverrideDisplayInfo; // set by the window manager
+
+        public final DisplayDevice mPrimaryDisplayDevice;
+        public final DisplayDeviceInfo mPrimaryDisplayDeviceInfo = new DisplayDeviceInfo();
+
+        public LogicalDisplay(DisplayDevice primaryDisplayDevice) {
+            mPrimaryDisplayDevice = primaryDisplayDevice;
+        }
+
+        public void updateFromPrimaryDisplayDevice() {
+            // Bootstrap the logical display using its associated primary physical display.
+            mPrimaryDisplayDevice.getInfo(mPrimaryDisplayDeviceInfo);
+
+            mBaseDisplayInfo.appWidth = mPrimaryDisplayDeviceInfo.width;
+            mBaseDisplayInfo.appHeight = mPrimaryDisplayDeviceInfo.height;
+            mBaseDisplayInfo.logicalWidth = mPrimaryDisplayDeviceInfo.width;
+            mBaseDisplayInfo.logicalHeight = mPrimaryDisplayDeviceInfo.height;
+            mBaseDisplayInfo.rotation = Surface.ROTATION_0;
+            mBaseDisplayInfo.refreshRate = mPrimaryDisplayDeviceInfo.refreshRate;
+            mBaseDisplayInfo.logicalDensityDpi = mPrimaryDisplayDeviceInfo.densityDpi;
+            mBaseDisplayInfo.physicalXDpi = mPrimaryDisplayDeviceInfo.xDpi;
+            mBaseDisplayInfo.physicalYDpi = mPrimaryDisplayDeviceInfo.yDpi;
+            mBaseDisplayInfo.smallestNominalAppWidth = mPrimaryDisplayDeviceInfo.width;
+            mBaseDisplayInfo.smallestNominalAppHeight = mPrimaryDisplayDeviceInfo.height;
+            mBaseDisplayInfo.largestNominalAppWidth = mPrimaryDisplayDeviceInfo.width;
+            mBaseDisplayInfo.largestNominalAppHeight = mPrimaryDisplayDeviceInfo.height;
+        }
     }
 }
diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
index f984c5d..f5c78b9 100644
--- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java
+++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
@@ -17,52 +17,44 @@
 package com.android.server.display;
 
 import android.content.Context;
-import android.os.IBinder;
 import android.util.DisplayMetrics;
 
 /**
  * Provides a fake default display for headless systems.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
  */
 public final class HeadlessDisplayAdapter extends DisplayAdapter {
-    private final Context mContext;
-    private final HeadlessDisplayDevice mDefaultDisplayDevice;
+    private static final String TAG = "HeadlessDisplayAdapter";
 
     public HeadlessDisplayAdapter(Context context) {
-        mContext = context;
-        mDefaultDisplayDevice = new HeadlessDisplayDevice();
+        super(context, TAG);
     }
 
     @Override
-    public String getName() {
-        return "HeadlessDisplayAdapter";
-    }
-
-    @Override
-    public void register(Listener listener) {
-        listener.onDisplayDeviceAdded(mDefaultDisplayDevice);
+    protected void onRegister() {
+        sendDisplayDeviceEvent(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED);
     }
 
     private final class HeadlessDisplayDevice extends DisplayDevice {
-        @Override
-        public DisplayAdapter getAdapter() {
-            return HeadlessDisplayAdapter.this;
-        }
-
-        @Override
-        public IBinder getDisplayToken() {
-            return null;
+        public HeadlessDisplayDevice() {
+            super(HeadlessDisplayAdapter.this, null);
         }
 
         @Override
         public void getInfo(DisplayDeviceInfo outInfo) {
-            outInfo.name = mContext.getResources().getString(
-                    com.android.internal.R.string.display_manager_built_in_display);
+            outInfo.name = getContext().getResources().getString(
+                    com.android.internal.R.string.display_manager_built_in_display_name);
             outInfo.width = 640;
             outInfo.height = 480;
             outInfo.refreshRate = 60;
             outInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
             outInfo.xDpi = 160;
             outInfo.yDpi = 160;
+            outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+                    | DisplayDeviceInfo.FLAG_SECURE;
         }
     }
 }
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index fa77878..73544fc 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -23,58 +23,52 @@
 
 /**
  * A display adapter for the local displays managed by Surface Flinger.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
  */
 public final class LocalDisplayAdapter extends DisplayAdapter {
-    private final Context mContext;
-    private final LocalDisplayDevice mDefaultDisplayDevice;
+    private static final String TAG = "LocalDisplayAdapter";
 
     public LocalDisplayAdapter(Context context) {
-        mContext = context;
-
-        IBinder token = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN);
-        mDefaultDisplayDevice = new LocalDisplayDevice(token);
+        super(context, TAG);
     }
 
     @Override
-    public String getName() {
-        return "LocalDisplayAdapter";
-    }
-
-    @Override
-    public void register(Listener listener) {
-        listener.onDisplayDeviceAdded(mDefaultDisplayDevice);
+    protected void onRegister() {
+        // TODO: listen for notifications from Surface Flinger about
+        // built-in displays being added or removed and rescan as needed.
+        IBinder displayToken = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN);
+        sendDisplayDeviceEvent(new LocalDisplayDevice(displayToken, true),
+                DISPLAY_DEVICE_EVENT_ADDED);
     }
 
     private final class LocalDisplayDevice extends DisplayDevice {
-        private final IBinder mDisplayToken;
+        private final boolean mIsDefault;
 
-        public LocalDisplayDevice(IBinder token) {
-            mDisplayToken = token;
-        }
-
-        @Override
-        public DisplayAdapter getAdapter() {
-            return LocalDisplayAdapter.this;
-        }
-
-        @Override
-        public IBinder getDisplayToken() {
-            return mDisplayToken;
+        public LocalDisplayDevice(IBinder displayToken, boolean isDefault) {
+            super(LocalDisplayAdapter.this, displayToken);
+            mIsDefault = isDefault;
         }
 
         @Override
         public void getInfo(DisplayDeviceInfo outInfo) {
             PhysicalDisplayInfo phys = new PhysicalDisplayInfo();
-            Surface.getDisplayInfo(mDisplayToken, phys);
+            Surface.getDisplayInfo(getDisplayToken(), phys);
 
-            outInfo.name = mContext.getResources().getString(
-                    com.android.internal.R.string.display_manager_built_in_display);
+            outInfo.name = getContext().getResources().getString(
+                    com.android.internal.R.string.display_manager_built_in_display_name);
             outInfo.width = phys.width;
             outInfo.height = phys.height;
             outInfo.refreshRate = phys.refreshRate;
             outInfo.densityDpi = (int)(phys.density * 160 + 0.5f);
             outInfo.xDpi = phys.xDpi;
             outInfo.yDpi = phys.yDpi;
+            if (mIsDefault) {
+                outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+                        | DisplayDeviceInfo.FLAG_SECURE;
+            }
         }
     }
 }
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
new file mode 100644
index 0000000..476d21a
--- /dev/null
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2012 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.display;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A display adapter that uses overlay windows to simulate secondary displays
+ * for development purposes.  Use Development Settings to enable one or more
+ * overlay displays.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
+ */
+public final class OverlayDisplayAdapter extends DisplayAdapter {
+    private static final String TAG = "OverlayDisplayAdapter";
+
+    private static final int MIN_WIDTH = 100;
+    private static final int MIN_HEIGHT = 100;
+    private static final int MAX_WIDTH = 4096;
+    private static final int MAX_HEIGHT = 4096;
+
+    private static final Pattern SETTING_PATTERN =
+            Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
+
+    private final ArrayList<Overlay> mOverlays = new ArrayList<Overlay>();
+    private String mCurrentOverlaySetting = "";
+
+    public OverlayDisplayAdapter(Context context) {
+        super(context, TAG);
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
+        pw.println("mOverlays: size=" + mOverlays.size());
+        for (Overlay overlay : mOverlays) {
+            overlay.dump(pw);
+        }
+    }
+
+    @Override
+    protected void onRegister() {
+        getContext().getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
+                new ContentObserver(getHandler()) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateOverlayDisplayDevices();
+                    }
+                });
+        updateOverlayDisplayDevices();
+    }
+
+    private void updateOverlayDisplayDevices() {
+        String value = Settings.System.getString(getContext().getContentResolver(),
+                Settings.Secure.OVERLAY_DISPLAY_DEVICES);
+        if (value == null) {
+            value = "";
+        }
+
+        if (value.equals(mCurrentOverlaySetting)) {
+            return;
+        }
+        mCurrentOverlaySetting = value;
+
+        if (!mOverlays.isEmpty()) {
+            Slog.i(TAG, "Dismissing all overlay display devices.");
+            for (Overlay overlay : mOverlays) {
+                overlay.dismiss();
+            }
+            mOverlays.clear();
+        }
+
+        int number = 1;
+        for (String part : value.split(";")) {
+            if (number > 4) {
+                Slog.w(TAG, "Too many overlay display devices.");
+            }
+            Matcher matcher = SETTING_PATTERN.matcher(part);
+            if (matcher.matches()) {
+                try {
+                    int width = Integer.parseInt(matcher.group(1), 10);
+                    int height = Integer.parseInt(matcher.group(2), 10);
+                    int densityDpi = Integer.parseInt(matcher.group(3), 10);
+                    if (width >= MIN_WIDTH && width <= MAX_WIDTH
+                            && height >= MIN_HEIGHT && height <= MAX_HEIGHT
+                            && densityDpi >= DisplayMetrics.DENSITY_LOW
+                            && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
+                        Slog.i(TAG, "Showing overlay display device #" + number
+                                + ": width=" + width + ", height=" + height
+                                + ", densityDpi=" + densityDpi);
+                        mOverlays.add(new Overlay(number++, width, height, densityDpi));
+                        continue;
+                    }
+                } catch (NumberFormatException ex) {
+                }
+            } else if (part.isEmpty()) {
+                continue;
+            }
+            Slog.w(TAG, "Malformed overlay display devices setting: \"" + value + "\"");
+        }
+
+        for (Overlay overlay : mOverlays) {
+            overlay.show();
+        }
+    }
+
+    // Manages an overlay window.
+    private final class Overlay {
+        private final float INITIAL_SCALE = 0.5f;
+        private final float MIN_SCALE = 0.3f;
+        private final float MAX_SCALE = 1.0f;
+        private final float WINDOW_ALPHA = 0.8f;
+
+        // When true, disables support for moving and resizing the overlay.
+        // The window is made non-touchable, which makes it possible to
+        // directly interact with the content underneath.
+        private final boolean DISABLE_MOVE_AND_RESIZE = false;
+
+        private final DisplayManager mDisplayManager;
+        private final WindowManager mWindowManager;
+
+        private final int mNumber;
+        private final int mWidth;
+        private final int mHeight;
+        private final int mDensityDpi;
+
+        private final String mName;
+        private final String mTitle;
+
+        private final Display mDefaultDisplay;
+        private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+        private final IBinder mDisplayToken;
+        private final OverlayDisplayDevice mDisplayDevice;
+
+        private View mWindowContent;
+        private WindowManager.LayoutParams mWindowParams;
+        private TextureView mTextureView;
+        private TextView mTitleTextView;
+        private ScaleGestureDetector mScaleGestureDetector;
+
+        private boolean mWindowVisible;
+        private int mWindowX;
+        private int mWindowY;
+        private float mWindowScale;
+
+        private int mLiveTranslationX;
+        private int mLiveTranslationY;
+        private float mLiveScale = 1.0f;
+
+        private int mDragPointerId;
+        private float mDragTouchX;
+        private float mDragTouchY;
+
+        public Overlay(int number, int width, int height, int densityDpi) {
+            Context context = getContext();
+            mDisplayManager = (DisplayManager)context.getSystemService(
+                    Context.DISPLAY_SERVICE);
+            mWindowManager = (WindowManager)context.getSystemService(
+                    Context.WINDOW_SERVICE);
+
+            mNumber = number;
+            mWidth = width;
+            mHeight = height;
+            mDensityDpi = densityDpi;
+
+            mName = context.getResources().getString(
+                    com.android.internal.R.string.display_manager_overlay_display_name, number);
+            mTitle = context.getResources().getString(
+                    com.android.internal.R.string.display_manager_overlay_display_title,
+                    mNumber, mWidth, mHeight, mDensityDpi);
+
+            mDefaultDisplay = mWindowManager.getDefaultDisplay();
+            updateDefaultDisplayInfo();
+
+            mDisplayToken = Surface.createDisplay(mName);
+            mDisplayDevice = new OverlayDisplayDevice(mDisplayToken, mName,
+                    mDefaultDisplayInfo.refreshRate, mDensityDpi);
+
+            createWindow();
+        }
+
+        public void show() {
+            if (!mWindowVisible) {
+                mDisplayManager.registerDisplayListener(mDisplayListener, null);
+                if (!updateDefaultDisplayInfo()) {
+                    mDisplayManager.unregisterDisplayListener(mDisplayListener);
+                    return;
+                }
+
+                clearLiveState();
+                updateWindowParams();
+                mWindowManager.addView(mWindowContent, mWindowParams);
+                mWindowVisible = true;
+            }
+        }
+
+        public void dismiss() {
+            if (mWindowVisible) {
+                mDisplayManager.unregisterDisplayListener(mDisplayListener);
+                mWindowManager.removeView(mWindowContent);
+                mWindowVisible = false;
+            }
+        }
+
+        public void relayout() {
+            if (mWindowVisible) {
+                updateWindowParams();
+                mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+            }
+        }
+
+        public void dump(PrintWriter pw) {
+            pw.println("  #" + mNumber + ": "
+                    + mWidth + "x" + mHeight + ", " + mDensityDpi + " dpi");
+            pw.println("    mName=" + mName);
+            pw.println("    mWindowVisible=" + mWindowVisible);
+            pw.println("    mWindowX=" + mWindowX);
+            pw.println("    mWindowY=" + mWindowY);
+            pw.println("    mWindowScale=" + mWindowScale);
+            pw.println("    mWindowParams=" + mWindowParams);
+            pw.println("    mLiveTranslationX=" + mLiveTranslationX);
+            pw.println("    mLiveTranslationY=" + mLiveTranslationY);
+            pw.println("    mLiveScale=" + mLiveScale);
+        }
+
+        private boolean updateDefaultDisplayInfo() {
+            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
+                Slog.w(TAG, "Cannot show overlay display because there is no "
+                        + "default display upon which to show it.");
+                return false;
+            }
+            return true;
+        }
+
+        private void createWindow() {
+            Context context = getContext();
+            LayoutInflater inflater = LayoutInflater.from(context);
+
+            mWindowContent = inflater.inflate(
+                    com.android.internal.R.layout.overlay_display_window, null);
+            mWindowContent.setOnTouchListener(mOnTouchListener);
+
+            mTextureView = (TextureView)mWindowContent.findViewById(
+                    com.android.internal.R.id.overlay_display_window_texture);
+            mTextureView.setPivotX(0);
+            mTextureView.setPivotY(0);
+            mTextureView.getLayoutParams().width = mWidth;
+            mTextureView.getLayoutParams().height = mHeight;
+            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+
+            mTitleTextView = (TextView)mWindowContent.findViewById(
+                    com.android.internal.R.id.overlay_display_window_title);
+            mTitleTextView.setText(mTitle);
+
+            mWindowParams = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
+            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            if (DISABLE_MOVE_AND_RESIZE) {
+                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            }
+            mWindowParams.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+            mWindowParams.alpha = WINDOW_ALPHA;
+            mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
+            mWindowParams.setTitle(mTitle);
+
+            mScaleGestureDetector = new ScaleGestureDetector(context, mOnScaleGestureListener);
+
+            // By default, arrange the displays in the four corners.
+            mWindowVisible = false;
+            mWindowScale = INITIAL_SCALE;
+            if (mNumber == 2 || mNumber == 3) {
+                mWindowX = mDefaultDisplayInfo.logicalWidth;
+            } else {
+                mWindowX = 0;
+            }
+            if (mNumber == 2 || mNumber == 4) {
+                mWindowY = mDefaultDisplayInfo.logicalHeight;
+            } else {
+                mWindowY = 0;
+            }
+        }
+
+        private void updateWindowParams() {
+            float scale = mWindowScale * mLiveScale;
+            if (mWidth * scale > mDefaultDisplayInfo.logicalWidth) {
+                scale = mDefaultDisplayInfo.logicalWidth / mWidth;
+            }
+            if (mHeight * scale > mDefaultDisplayInfo.logicalHeight) {
+                scale = mDefaultDisplayInfo.logicalHeight / mHeight;
+            }
+            scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
+
+            float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
+            int width = (int)(mWidth * scale);
+            int height = (int)(mHeight * scale);
+            int x = mWindowX + mLiveTranslationX - (int)(width * offsetScale);
+            int y = mWindowY + mLiveTranslationY - (int)(height * offsetScale);
+            x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
+            y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
+
+            mTextureView.setScaleX(scale);
+            mTextureView.setScaleY(scale);
+
+            mWindowParams.x = x;
+            mWindowParams.y = y;
+            mWindowParams.width = width;
+            mWindowParams.height = height;
+        }
+
+        private void saveWindowParams() {
+            mWindowX = mWindowParams.x;
+            mWindowY = mWindowParams.y;
+            mWindowScale = mTextureView.getScaleX();
+            clearLiveState();
+        }
+
+        private void clearLiveState() {
+            mLiveTranslationX = 0;
+            mLiveTranslationY = 0;
+            mLiveScale = 1.0f;
+        }
+
+        private final DisplayManager.DisplayListener mDisplayListener =
+                new DisplayManager.DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                if (displayId == mDefaultDisplay.getDisplayId()) {
+                    if (updateDefaultDisplayInfo()) {
+                        relayout();
+                    } else {
+                        dismiss();
+                    }
+                }
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                if (displayId == mDefaultDisplay.getDisplayId()) {
+                    dismiss();
+                }
+            }
+        };
+
+        private final SurfaceTextureListener mSurfaceTextureListener =
+                new SurfaceTextureListener() {
+            @Override
+            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+                Surface.openTransaction();
+                try {
+                    Surface.setDisplaySurface(mDisplayToken, surface);
+                } finally {
+                    Surface.closeTransaction();
+                }
+
+                mDisplayDevice.setSize(width, height);
+                sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
+            }
+
+            @Override
+            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+                sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+
+                Surface.openTransaction();
+                try {
+                    Surface.setDisplaySurface(mDisplayToken, null);
+                } finally {
+                    Surface.closeTransaction();
+                }
+                return true;
+            }
+
+            @Override
+            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+                mDisplayDevice.setSize(width, height);
+                sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
+            }
+
+            @Override
+            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+            }
+        };
+
+        private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent event) {
+                // Work in screen coordinates.
+                final float oldX = event.getX();
+                final float oldY = event.getY();
+                event.setLocation(event.getRawX(), event.getRawY());
+
+                mScaleGestureDetector.onTouchEvent(event);
+
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN:
+                        resetDrag(event);
+                        break;
+
+                    case MotionEvent.ACTION_MOVE:
+                        if (event.getPointerCount() == 1) {
+                            int index = event.findPointerIndex(mDragPointerId);
+                            if (index < 0) {
+                                resetDrag(event);
+                            } else {
+                                mLiveTranslationX = (int)(event.getX(index) - mDragTouchX);
+                                mLiveTranslationY = (int)(event.getY(index) - mDragTouchY);
+                                relayout();
+                            }
+                        }
+                        break;
+
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        saveWindowParams();
+                        break;
+                }
+
+                // Revert to window coordinates.
+                event.setLocation(oldX, oldY);
+                return true;
+            }
+
+            private void resetDrag(MotionEvent event) {
+                saveWindowParams();
+                mDragPointerId = event.getPointerId(0);
+                mDragTouchX = event.getX();
+                mDragTouchY = event.getY();
+            }
+        };
+
+        private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+                new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+            @Override
+            public boolean onScaleBegin(ScaleGestureDetector detector) {
+                saveWindowParams();
+                mDragPointerId = -1; // cause drag to be reset
+                return true;
+            }
+
+            @Override
+            public boolean onScale(ScaleGestureDetector detector) {
+                mLiveScale = detector.getScaleFactor();
+                relayout();
+                return false;
+            }
+        };
+    }
+
+    private final class OverlayDisplayDevice extends DisplayDevice {
+        private final String mName;
+        private final float mRefreshRate;
+        private final int mDensityDpi;
+        private int mWidth;
+        private int mHeight;
+
+        public OverlayDisplayDevice(IBinder displayToken, String name,
+                float refreshRate, int densityDpi) {
+            super(OverlayDisplayAdapter.this, displayToken);
+            mName = name;
+            mRefreshRate = refreshRate;
+            mDensityDpi = densityDpi;
+        }
+
+        public void setSize(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public void getInfo(DisplayDeviceInfo outInfo) {
+            outInfo.name = mName;
+            outInfo.width = mWidth;
+            outInfo.height = mHeight;
+            outInfo.refreshRate = mRefreshRate;
+            outInfo.densityDpi = mDensityDpi;
+            outInfo.xDpi = mDensityDpi;
+            outInfo.yDpi = mDensityDpi;
+            outInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+        }
+    }
+}
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index cd211da..6b6d899 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -45,7 +45,6 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 
 /**
@@ -169,6 +168,9 @@
     // The twilight service.
     private final TwilightService mTwilight;
 
+    // The display manager.
+    private final DisplayManager mDisplayManager;
+
     // The sensor manager.
     private final SensorManager mSensorManager;
 
@@ -330,6 +332,7 @@
         mLights = lights;
         mTwilight = twilight;
         mSensorManager = new SystemSensorManager(mHandler.getLooper());
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         final Resources resources = context.getResources();
         mScreenBrightnessDimConfig = resources.getInteger(
@@ -475,7 +478,7 @@
 
     private void initialize() {
         final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR;
-        Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+        Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
         mPowerState = new DisplayPowerState(new ElectronBeam(display),
                 new PhotonicModulator(executor,
                         mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT),
@@ -980,7 +983,7 @@
         }
     };
 
-    public void dump(PrintWriter pw) {
+    public void dump(final PrintWriter pw) {
         synchronized (mLock) {
             pw.println();
             pw.println("Display Controller Locked State:");
@@ -1000,33 +1003,12 @@
         pw.println("  mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
         pw.println("  mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
 
-        if (Looper.myLooper() == mHandler.getLooper()) {
-            dumpLocal(pw);
-        } else {
-            final StringWriter out = new StringWriter();
-            final CountDownLatch latch = new CountDownLatch(1);
-            Message msg = Message.obtain(mHandler,  new Runnable() {
-                @Override
-                public void run() {
-                    PrintWriter localpw = new PrintWriter(out);
-                    try {
-                        dumpLocal(localpw);
-                    } finally {
-                        localpw.flush();
-                        latch.countDown();
-                    }
-                }
-            });
-            msg.setAsynchronous(true);
-            mHandler.sendMessage(msg);
-            try {
-                latch.await();
-                pw.print(out.toString());
-            } catch (InterruptedException ex) {
-                pw.println();
-                pw.println("Failed to dump thread state due to interrupted exception!");
+        mHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                dumpLocal(pw);
             }
-        }
+        });
     }
 
     private void dumpLocal(PrintWriter pw) {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index c1047a9..0d9db90 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -739,116 +739,38 @@
     // For example, when this flag is true, there will be no wallpaper service.
     final boolean mOnlyCore;
 
-    public static WindowManagerService main(Context context,
-            PowerManagerService pm, DisplayManagerService dm,
-            boolean haveInputMethods, boolean allowBootMsgs,
-            boolean onlyCore) {
-        WMThread thr = new WMThread(context, pm, dm, haveInputMethods, allowBootMsgs, onlyCore);
-        thr.start();
-
-        synchronized (thr) {
-            while (thr.mService == null) {
-                try {
-                    thr.wait();
-                } catch (InterruptedException e) {
-                }
+    public static WindowManagerService main(final Context context,
+            final PowerManagerService pm, final DisplayManagerService dm,
+            final Handler uiHandler, final Handler wmHandler,
+            final boolean haveInputMethods, final boolean showBootMsgs,
+            final boolean onlyCore) {
+        final WindowManagerService[] holder = new WindowManagerService[1];
+        wmHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                holder[0] = new WindowManagerService(context, pm, dm,
+                        uiHandler, haveInputMethods, showBootMsgs, onlyCore);
             }
-            return thr.mService;
-        }
+        });
+        return holder[0];
     }
 
-    static class WMThread extends Thread {
-        WindowManagerService mService;
+    private void initPolicy(Handler uiHandler) {
+        uiHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
 
-        private final Context mContext;
-        private final PowerManagerService mPM;
-        private final DisplayManagerService mDisplayManager;
-        private final boolean mHaveInputMethods;
-        private final boolean mAllowBootMessages;
-        private final boolean mOnlyCore;
-
-        public WMThread(Context context, PowerManagerService pm,
-                DisplayManagerService dm,
-                boolean haveInputMethods, boolean allowBootMsgs, boolean onlyCore) {
-            super("WindowManager");
-            mContext = context;
-            mPM = pm;
-            mDisplayManager = dm;
-            mHaveInputMethods = haveInputMethods;
-            mAllowBootMessages = allowBootMsgs;
-            mOnlyCore = onlyCore;
-        }
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            //Looper.myLooper().setMessageLogging(new LogPrinter(
-            //        android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM));
-            WindowManagerService s = new WindowManagerService(mContext, mPM, mDisplayManager,
-                    mHaveInputMethods, mAllowBootMessages, mOnlyCore);
-            android.os.Process.setThreadPriority(
-                    android.os.Process.THREAD_PRIORITY_DISPLAY);
-            android.os.Process.setCanSelfBackground(false);
-
-            synchronized (this) {
-                mService = s;
-                notifyAll();
+                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+                mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
+                        * TYPE_LAYER_MULTIPLIER
+                        + TYPE_LAYER_OFFSET;
             }
-
-            // For debug builds, log event loop stalls to dropbox for analysis.
-            if (StrictMode.conditionallyEnableDebugLogging()) {
-                Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper");
-            }
-
-            Looper.loop();
-        }
-    }
-
-    static class PolicyThread extends Thread {
-        private final WindowManagerPolicy mPolicy;
-        private final WindowManagerService mService;
-        private final Context mContext;
-        boolean mRunning = false;
-
-        public PolicyThread(WindowManagerPolicy policy,
-                WindowManagerService service, Context context) {
-            super("WindowManagerPolicy");
-            mPolicy = policy;
-            mService = service;
-            mContext = context;
-        }
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            WindowManagerPolicyThread.set(this, Looper.myLooper());
-
-            //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);
-            mPolicy.init(mContext, mService, mService);
-            mService.mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
-                    * TYPE_LAYER_MULTIPLIER
-                    + TYPE_LAYER_OFFSET;
-
-            synchronized (this) {
-                mRunning = true;
-                notifyAll();
-            }
-
-            // For debug builds, log event loop stalls to dropbox for analysis.
-            if (StrictMode.conditionallyEnableDebugLogging()) {
-                Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper");
-            }
-
-            Looper.loop();
-        }
+        });
     }
 
     private WindowManagerService(Context context, PowerManagerService pm,
-            DisplayManagerService displayManager,
+            DisplayManagerService displayManager, Handler uiHandler,
             boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
         mContext = context;
         mHaveInputMethods = haveInputMethods;
@@ -857,7 +779,7 @@
         mLimitedAlphaCompositing = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_sf_limitedAlpha);
         mDisplayManagerService = displayManager;
-        mDisplayManager = DisplayManager.getInstance();
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mHeadless = displayManager.isHeadless();
 
         mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
@@ -895,17 +817,7 @@
         mFxSession = new SurfaceSession();
         mAnimator = new WindowAnimator(this);
 
-        PolicyThread thr = new PolicyThread(mPolicy, this, context);
-        thr.start();
-
-        synchronized (thr) {
-            while (!thr.mRunning) {
-                try {
-                    thr.wait();
-                } catch (InterruptedException e) {
-                }
-            }
-        }
+        initPolicy(uiHandler);
 
         mInputManager.start();
 
@@ -6562,7 +6474,8 @@
             displayInfo.appHeight = appHeight;
             displayInfo.getLogicalMetrics(mRealDisplayMetrics, null);
             displayInfo.getAppMetrics(mDisplayMetrics, null);
-            mDisplayManagerService.setDisplayInfo(displayContent.getDisplayId(), displayInfo);
+            mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
+                    displayContent.getDisplayId(), displayInfo);
 
             mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight);
         }
@@ -6914,7 +6827,10 @@
             synchronized(displayContent.mDisplaySizeLock) {
                 // Bootstrap the default logical display from the display manager.
                 displayInfo = displayContent.getDisplayInfo();
-                mDisplayManagerService.getDisplayInfo(displayId, displayInfo);
+                DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId);
+                if (newDisplayInfo != null) {
+                    displayInfo.copyFrom(newDisplayInfo);
+                }
                 displayContent.mInitialDisplayWidth = displayInfo.logicalWidth;
                 displayContent.mInitialDisplayHeight = displayInfo.logicalHeight;
                 displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi;
@@ -10365,7 +10281,7 @@
     public DisplayContent getDisplayContent(final int displayId) {
         DisplayContent displayContent = mDisplayContents.get(displayId);
         if (displayContent == null) {
-            displayContent = new DisplayContent(mDisplayManager.getRealDisplay(displayId));
+            displayContent = new DisplayContent(mDisplayManager.getDisplay(displayId));
             mDisplayContents.put(displayId, displayContent);
         }
         return displayContent;