Merge "Paramaterize and adjust the glyph cache sizes" into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 31d0c46..fbda3c6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -773,6 +773,7 @@
     field public static final int preferenceLayoutChild = 16842900; // 0x1010094
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
     field public static final int preferenceStyle = 16842894; // 0x101008e
+    field public static final int presentationTheme = 16843712; // 0x10103c0
     field public static final int previewImage = 16843482; // 0x10102da
     field public static final int priority = 16842780; // 0x101001c
     field public static final int privateImeOptions = 16843299; // 0x1010223
@@ -3901,6 +3902,15 @@
     method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
   }
 
+  public class Presentation extends android.app.Dialog {
+    ctor public Presentation(android.content.Context, android.view.Display);
+    ctor public Presentation(android.content.Context, android.view.Display, int);
+    method public android.view.Display getDisplay();
+    method public android.content.res.Resources getResources();
+    method public void onDisplayChanged();
+    method public void onDisplayRemoved();
+  }
+
   public class ProgressDialog extends android.app.AlertDialog {
     ctor public ProgressDialog(android.content.Context);
     ctor public ProgressDialog(android.content.Context, int);
@@ -5268,6 +5278,7 @@
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createDisplayContext(android.view.Display);
     method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract java.lang.String[] databaseList();
     method public abstract boolean deleteDatabase(java.lang.String);
@@ -5417,6 +5428,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] databaseList();
     method public boolean deleteDatabase(java.lang.String);
@@ -21228,6 +21240,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] databaseList();
     method public boolean deleteDatabase(java.lang.String);
@@ -22911,6 +22924,7 @@
 
   public class DisplayMetrics {
     ctor public DisplayMetrics();
+    method public boolean equals(android.util.DisplayMetrics);
     method public void setTo(android.util.DisplayMetrics);
     method public void setToDefaults();
     field public static final int DENSITY_DEFAULT = 160; // 0xa0
@@ -23459,6 +23473,7 @@
     method public int getDisplayId();
     method public deprecated int getHeight();
     method public void getMetrics(android.util.DisplayMetrics);
+    method public java.lang.String getName();
     method public deprecated int getOrientation();
     method public deprecated int getPixelFormat();
     method public void getRealMetrics(android.util.DisplayMetrics);
@@ -27677,6 +27692,7 @@
     method public void setDropDownVerticalOffset(int);
     method public void setDropDownWidth(int);
     method public void setListSelection(int);
+    method public void setOnDismissListener(android.widget.AutoCompleteTextView.OnDismissListener);
     method public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener);
     method public void setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
     method public void setText(java.lang.CharSequence, boolean);
@@ -27685,6 +27701,10 @@
     method public void showDropDown();
   }
 
+  public static abstract interface AutoCompleteTextView.OnDismissListener {
+    method public abstract void onDismiss();
+  }
+
   public static abstract interface AutoCompleteTextView.Validator {
     method public abstract java.lang.CharSequence fixText(java.lang.CharSequence);
     method public abstract boolean isValid(java.lang.CharSequence);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4a1bf75..1b788c2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -203,7 +203,7 @@
             = new HashMap<String, WeakReference<LoadedApk>>();
     final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
             = new HashMap<String, WeakReference<LoadedApk>>();
-    final HashMap<CompatibilityInfo, DisplayMetrics> mDisplayMetrics
+    final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics
             = new HashMap<CompatibilityInfo, DisplayMetrics>();
     final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
             = new HashMap<ResourcesKey, WeakReference<Resources> >();
@@ -1475,12 +1475,14 @@
 
     private static class ResourcesKey {
         final private String mResDir;
+        final private int mDisplayId;
         final private Configuration mOverrideConfiguration;
         final private float mScale;
         final private int mHash;
 
-        ResourcesKey(String resDir, Configuration overrideConfiguration, float scale) {
+        ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
             mResDir = resDir;
+            mDisplayId = displayId;
             if (overrideConfiguration != null) {
                 if (Configuration.EMPTY.equals(overrideConfiguration)) {
                     overrideConfiguration = null;
@@ -1490,6 +1492,7 @@
             mScale = scale;
             int hash = 17;
             hash = 31 * hash + mResDir.hashCode();
+            hash = 31 * hash + mDisplayId;
             hash = 31 * hash + (mOverrideConfiguration != null
                     ? mOverrideConfiguration.hashCode() : 0);
             hash = 31 * hash + Float.floatToIntBits(mScale);
@@ -1510,6 +1513,9 @@
             if (!mResDir.equals(peer.mResDir)) {
                 return false;
             }
+            if (mDisplayId != peer.mDisplayId) {
+                return false;
+            }
             if (mOverrideConfiguration != peer.mOverrideConfiguration) {
                 if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) {
                     return false;
@@ -1552,28 +1558,32 @@
         return sPackageManager;
     }
 
-    DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) {
-        DisplayMetrics dm = mDisplayMetrics.get(ci);
-        if (dm != null && !forceUpdate) {
+    private void flushDisplayMetricsLocked() {
+        mDefaultDisplayMetrics.clear();
+    }
+
+    DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) {
+        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+        DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null;
+        if (dm != null) {
             return dm;
         }
+        dm = new DisplayMetrics();
 
         DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
         if (displayManager == null) {
             // may be null early in system startup
-            dm = new DisplayMetrics();
             dm.setToDefaults();
             return dm;
         }
 
-        if (dm == null) {
-            dm = new DisplayMetrics();
-            mDisplayMetrics.put(ci, dm);
+        if (isDefaultDisplay) {
+            mDefaultDisplayMetrics.put(ci, dm);
         }
 
         CompatibilityInfoHolder cih = new CompatibilityInfoHolder();
         cih.set(ci);
-        Display d = displayManager.getCompatibleDisplay(Display.DEFAULT_DISPLAY, cih);
+        Display d = displayManager.getCompatibleDisplay(displayId, cih);
         d.getMetrics(dm);
         //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
         //        + metrics.heightPixels + " den=" + metrics.density
@@ -1602,9 +1612,11 @@
      * @param compInfo the compability info. It will use the default compatibility info when it's
      * null.
      */
-    Resources getTopLevelResources(String resDir, Configuration overrideConfiguration,
+    Resources getTopLevelResources(String resDir,
+            int displayId, Configuration overrideConfiguration,
             CompatibilityInfo compInfo) {
-        ResourcesKey key = new ResourcesKey(resDir, overrideConfiguration,
+        ResourcesKey key = new ResourcesKey(resDir,
+                displayId, overrideConfiguration,
                 compInfo.applicationScale);
         Resources r;
         synchronized (mPackages) {
@@ -1636,15 +1648,21 @@
         }
 
         //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
-        DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
+        DisplayMetrics dm = getDisplayMetricsLocked(displayId, null);
         Configuration config;
-        if (key.mOverrideConfiguration != null) {
+        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+        if (!isDefaultDisplay || key.mOverrideConfiguration != null) {
             config = new Configuration(getConfiguration());
-            config.updateFrom(key.mOverrideConfiguration);
+            if (!isDefaultDisplay) {
+                applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
+            }
+            if (key.mOverrideConfiguration != null) {
+                config.updateFrom(key.mOverrideConfiguration);
+            }
         } else {
             config = getConfiguration();
         }
-        r = new Resources(assets, metrics, config, compInfo);
+        r = new Resources(assets, dm, config, compInfo);
         if (false) {
             Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
                     + r.getConfiguration() + " appScale="
@@ -1670,9 +1688,10 @@
     /**
      * Creates the top level resources for the given package.
      */
-    Resources getTopLevelResources(String resDir, Configuration overrideConfiguration,
+    Resources getTopLevelResources(String resDir,
+            int displayId, Configuration overrideConfiguration,
             LoadedApk pkgInfo) {
-        return getTopLevelResources(resDir, overrideConfiguration,
+        return getTopLevelResources(resDir, displayId, overrideConfiguration,
                 pkgInfo.mCompatibilityInfo.get());
     }
 
@@ -1844,7 +1863,8 @@
                 context.init(info, null, this);
                 context.getResources().updateConfiguration(
                         getConfiguration(), getDisplayMetricsLocked(
-                                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, false));
+                                Display.DEFAULT_DISPLAY,
+                                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO));
                 mSystemContext = context;
                 //Slog.i(TAG, "Created system resources " + context.getResources()
                 //        + ": " + context.getResources().getConfiguration());
@@ -3707,7 +3727,9 @@
             return false;
         }
         int changes = mResConfiguration.updateFrom(config);
-        DisplayMetrics dm = getDisplayMetricsLocked(null, true);
+        flushDisplayMetricsLocked();
+        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(
+                Display.DEFAULT_DISPLAY, null);
 
         if (compat != null && (mResCompatibilityInfo == null ||
                 !mResCompatibilityInfo.equals(compat))) {
@@ -3722,7 +3744,7 @@
             Locale.setDefault(config.locale);
         }
 
-        Resources.updateSystemConfiguration(config, dm, compat);
+        Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
 
         ApplicationPackageManager.configurationChanged();
         //Slog.i(TAG, "Configuration changed in " + currentPackageName());
@@ -3737,13 +3759,22 @@
             if (r != null) {
                 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
                         + r + " config to: " + config);
-                Configuration override = entry.getKey().mOverrideConfiguration;
-                if (override != null) {
+                int displayId = entry.getKey().mDisplayId;
+                boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+                DisplayMetrics dm = defaultDisplayMetrics;
+                Configuration overrideConfig = entry.getKey().mOverrideConfiguration;
+                if (!isDefaultDisplay || overrideConfig != null) {
                     if (tmpConfig == null) {
                         tmpConfig = new Configuration();
                     }
                     tmpConfig.setTo(config);
-                    tmpConfig.updateFrom(override);
+                    if (!isDefaultDisplay) {
+                        dm = getDisplayMetricsLocked(displayId, null);
+                        applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
+                    }
+                    if (overrideConfig != null) {
+                        tmpConfig.updateFrom(overrideConfig);
+                    }
                     r.updateConfiguration(tmpConfig, dm, compat);
                 } else {
                     r.updateConfiguration(config, dm, compat);
@@ -3759,6 +3790,22 @@
         return changes != 0;
     }
 
+    final void applyNonDefaultDisplayMetricsToConfigurationLocked(
+            DisplayMetrics dm, Configuration config) {
+        config.screenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE
+                | Configuration.SCREENLAYOUT_LONG_NO;
+        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+        config.orientation = (dm.widthPixels >= dm.heightPixels) ?
+                Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+        config.densityDpi = dm.densityDpi;
+        config.screenWidthDp = (int)(dm.widthPixels / dm.density);
+        config.screenHeightDp = (int)(dm.heightPixels / dm.density);
+        config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
+        config.compatScreenWidthDp = config.screenWidthDp;
+        config.compatScreenHeightDp = config.screenHeightDp;
+        config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
+    }
+
     final Configuration applyCompatConfiguration(int displayDensity) {
         Configuration config = mConfiguration;
         if (mCompatConfiguration == null) {
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 7809e73..6ab2bd1 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -110,8 +110,9 @@
         this(context, theme, true);
     }
 
-    AlertDialog(Context context, int theme, boolean createContextWrapper) {
-        super(context, resolveDialogTheme(context, theme), createContextWrapper);
+    AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
+        super(context, resolveDialogTheme(context, theme), createThemeContextWrapper);
+
         mWindow.alwaysReadCloseOnTouchAttr();
         mAlert = new AlertController(getContext(), this, getWindow());
     }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 91753aa..c4f1371 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -54,6 +54,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.Display;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -722,8 +723,8 @@
             return mContext.mMainThread.getSystemContext().getResources();
         }
         Resources r = mContext.mMainThread.getTopLevelResources(
-            app.uid == Process.myUid() ? app.sourceDir
-            : app.publicSourceDir, null, mContext.mPackageInfo);
+                app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
+                        Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
         if (r != null) {
             return r;
         }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1b6f84b..ea77bcd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -168,6 +168,7 @@
     private int mThemeResource = 0;
     private Resources.Theme mTheme = null;
     private PackageManager mPackageManager;
+    private Display mDisplay; // may be null if default display
     private Context mReceiverRestrictedContext = null;
     private boolean mRestricted;
 
@@ -502,8 +503,13 @@
 
         registerService(WINDOW_SERVICE, new ServiceFetcher() {
                 public Object getService(ContextImpl ctx) {
-                    return new WindowManagerImpl(ctx.getOuterContext(),
-                            Display.DEFAULT_DISPLAY);
+                    Display display = ctx.mDisplay;
+                    if (display == null) {
+                        DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService(
+                                Context.DISPLAY_SERVICE);
+                        display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+                    }
+                    return new WindowManagerImpl(display);
                 }});
 
         registerService(USER_SERVICE, new ServiceFetcher() {
@@ -1676,22 +1682,52 @@
 
     @Override
     public Context createConfigurationContext(Configuration overrideConfiguration) {
+        if (overrideConfiguration == null) {
+            throw new IllegalArgumentException("overrideConfiguration must not be null");
+        }
+
         ContextImpl c = new ContextImpl();
         c.init(mPackageInfo, null, mMainThread);
         c.mResources = mMainThread.getTopLevelResources(
-                mPackageInfo.getResDir(), overrideConfiguration,
+                mPackageInfo.getResDir(),
+                getDisplayId(), overrideConfiguration,
                 mResources.getCompatibilityInfo());
         return c;
     }
 
     @Override
+    public Context createDisplayContext(Display display) {
+        if (display == null) {
+            throw new IllegalArgumentException("display must not be null");
+        }
+
+        int displayId = display.getDisplayId();
+        CompatibilityInfo ci = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        CompatibilityInfoHolder cih = getCompatibilityInfo(displayId);
+        if (cih != null) {
+            ci = cih.get();
+        }
+
+        ContextImpl context = new ContextImpl();
+        context.init(mPackageInfo, null, mMainThread);
+        context.mDisplay = display;
+        context.mResources = mMainThread.getTopLevelResources(
+                mPackageInfo.getResDir(), displayId, null, ci);
+        return context;
+    }
+
+    private int getDisplayId() {
+        return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+    }
+
+    @Override
     public boolean isRestricted() {
         return mRestricted;
     }
 
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo() {
-        return mPackageInfo.mCompatibilityInfo;
+    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+        return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null;
     }
 
     private File getDataDirFile() {
@@ -1735,6 +1771,7 @@
         mResources = context.mResources;
         mMainThread = context.mMainThread;
         mContentResolver = context.mContentResolver;
+        mDisplay = context.mDisplay;
         mOuterContext = this;
     }
 
@@ -1758,7 +1795,8 @@
                         " compatiblity info:" + container.getDisplayMetrics());
             }
             mResources = mainThread.getTopLevelResources(
-                    mPackageInfo.getResDir(), null, container.getCompatibilityInfo());
+                    mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
+                    null, container.getCompatibilityInfo());
         }
         mMainThread = mainThread;
         mContentResolver = new ApplicationContentResolver(this, mainThread);
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 16112cb3..b3d99c5 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -147,15 +147,19 @@
         this(context, theme, true);
     }
 
-    Dialog(Context context, int theme, boolean createContextWrapper) {
-        if (theme == 0) {
-            TypedValue outValue = new TypedValue();
-            context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
-                    outValue, true);
-            theme = outValue.resourceId;
+    Dialog(Context context, int theme, boolean createContextThemeWrapper) {
+        if (createContextThemeWrapper) {
+            if (theme == 0) {
+                TypedValue outValue = new TypedValue();
+                context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
+                        outValue, true);
+                theme = outValue.resourceId;
+            }
+            mContext = new ContextThemeWrapper(context, theme);
+        } else {
+            mContext = context;
         }
 
-        mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context;
         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
         Window w = PolicyManager.makeNewWindow(mContext);
         mWindow = w;
@@ -164,7 +168,7 @@
         w.setGravity(Gravity.CENTER);
         mListenersHandler = new ListenersHandler(this);
     }
-    
+
     /**
      * @deprecated
      * @hide
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1e89bb2..6197b1a 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -41,6 +41,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.Slog;
 import android.view.CompatibilityInfoHolder;
+import android.view.Display;
 
 import java.io.File;
 import java.io.IOException;
@@ -139,7 +140,8 @@
                     ContextImpl.createSystemContext(mainThread);
                 ActivityThread.mSystemContext.getResources().updateConfiguration(
                          mainThread.getConfiguration(),
-                         mainThread.getDisplayMetricsLocked(compatInfo, false),
+                         mainThread.getDisplayMetricsLocked(
+                                 Display.DEFAULT_DISPLAY, compatInfo),
                          compatInfo);
                 //Slog.i(TAG, "Created system resources "
                 //        + mSystemContext.getResources() + ": "
@@ -471,7 +473,8 @@
 
     public Resources getResources(ActivityThread mainThread) {
         if (mResources == null) {
-            mResources = mainThread.getTopLevelResources(mResDir, null, this);
+            mResources = mainThread.getTopLevelResources(mResDir,
+                    Display.DEFAULT_DISPLAY, null, this);
         }
         return mResources;
     }
diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java
new file mode 100644
index 0000000..eb5a652
--- /dev/null
+++ b/core/java/android/app/Presentation.java
@@ -0,0 +1,256 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.WindowManagerImpl;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+/**
+ * Base class for presentations.
+ *
+ * A presentation is a special kind of dialog whose purpose is to present
+ * content on a secondary display.  A {@link Presentation} is associated with
+ * the target {@link Display} at creation time and configures its context and
+ * resource configuration according to the display's metrics.
+ *
+ * Notably, the {@link Context} of a presentation is different from the context
+ * of its containing {@link Activity}.  It is important to inflate the layout
+ * of a presentation and load other resources using the presentation's own context
+ * to ensure that assets of the correct size and density for the target display
+ * are loaded.
+ *
+ * A presentation is automatically canceled (see {@link Dialog#cancel()}) when
+ * the display to which it is attached is removed.  An activity should take
+ * care of pausing and resuming whatever content is playing within the presentation
+ * whenever the activity itself is paused or resume.
+ *
+ * @see {@link DisplayManager} for information on how to enumerate displays.
+ */
+public class Presentation extends Dialog {
+    private static final String TAG = "Presentation";
+
+    private static final int MSG_CANCEL = 1;
+
+    private final Display mDisplay;
+    private final DisplayManager mDisplayManager;
+
+    /**
+     * Creates a new presentation that is attached to the specified display
+     * using the default theme.
+     *
+     * @param outerContext The context of the application that is showing the presentation.
+     * The presentation will create its own context (see {@link #getContext()}) based
+     * on this context and information about the associated display.
+     * @param display The display to which the presentation should be attached.
+     */
+    public Presentation(Context outerContext, Display display) {
+        this(outerContext, display, 0);
+    }
+
+    /**
+     * Creates a new presentation that is attached to the specified display
+     * using the optionally specified theme.
+     *
+     * @param outerContext The context of the application that is showing the presentation.
+     * The presentation will create its own context (see {@link #getContext()}) based
+     * on this context and information about the associated display.
+     * @param display The display to which the presentation should be attached.
+     * @param theme A style resource describing the theme to use for the window.
+     * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
+     * Style and Theme Resources</a> for more information about defining and using
+     * styles.  This theme is applied on top of the current theme in
+     * <var>outerContext</var>.  If 0, the default presentation theme will be used.
+     */
+    public Presentation(Context outerContext, Display display, int theme) {
+        super(createPresentationContext(outerContext, display, theme), theme, false);
+
+        mDisplay = display;
+        mDisplayManager = (DisplayManager)getContext().getSystemService(Context.DISPLAY_SERVICE);
+
+        getWindow().setGravity(Gravity.FILL);
+        setCanceledOnTouchOutside(false);
+    }
+
+    /**
+     * Gets the {@link Display} that this presentation appears on.
+     *
+     * @return The display.
+     */
+    public Display getDisplay() {
+        return mDisplay;
+    }
+
+    /**
+     * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
+     * This resources object has been configured according to the metrics of the
+     * display that the presentation appears on.
+     *
+     * @return The presentation resources object.
+     */
+    public Resources getResources() {
+        return getContext().getResources();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mDisplayManager.registerDisplayListener(mDisplayListener, null);
+
+        // Since we were not watching for display changes until just now, there is a
+        // chance that the display metrics have changed.  If so, we will need to
+        // dismiss the presentation immediately.  This case is expected
+        // to be rare but surprising, so we'll write a log message about it.
+        if (!isConfigurationStillValid()) {
+            Log.i(TAG, "Presentation is being immediately dismissed because the "
+                    + "display metrics have changed since it was created.");
+            mHandler.sendEmptyMessage(MSG_CANCEL);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        super.onStop();
+    }
+
+    /**
+     * Called by the system when the {@link Display} to which the presentation
+     * is attached has been removed.
+     *
+     * The system automatically calls {@link #cancel} to dismiss the presentation
+     * after sending this event.
+     *
+     * @see #getDisplay
+     */
+    public void onDisplayRemoved() {
+    }
+
+    /**
+     * Called by the system when the properties of the {@link Display} to which
+     * the presentation is attached have changed.
+     *
+     * If the display metrics have changed (for example, if the display has been
+     * resized or rotated), then the system automatically calls
+     * {@link #cancel} to dismiss the presentation.
+     *
+     * @see #getDisplay
+     */
+    public void onDisplayChanged() {
+    }
+
+    private void handleDisplayRemoved() {
+        onDisplayRemoved();
+        cancel();
+    }
+
+    private void handleDisplayChanged() {
+        onDisplayChanged();
+
+        // We currently do not support configuration changes for presentations
+        // (although we could add that feature with a bit more work).
+        // If the display metrics have changed in any way then the current configuration
+        // is invalid and the application must recreate the presentation to get
+        // a new context.
+        if (!isConfigurationStillValid()) {
+            cancel();
+        }
+    }
+
+    private boolean isConfigurationStillValid() {
+        DisplayMetrics dm = new DisplayMetrics();
+        mDisplay.getMetrics(dm);
+        return dm.equals(getResources().getDisplayMetrics());
+    }
+
+    private static Context createPresentationContext(
+            Context outerContext, Display display, int theme) {
+        if (outerContext == null) {
+            throw new IllegalArgumentException("outerContext must not be null");
+        }
+        if (display == null) {
+            throw new IllegalArgumentException("display must not be null");
+        }
+
+        Context displayContext = outerContext.createDisplayContext(display);
+        if (theme == 0) {
+            TypedValue outValue = new TypedValue();
+            displayContext.getTheme().resolveAttribute(
+                    com.android.internal.R.attr.presentationTheme, outValue, true);
+            theme = outValue.resourceId;
+        }
+
+        // Derive the display's window manager from the outer window manager.
+        // We do this because the outer window manager have some extra information
+        // such as the parent window, which is important if the presentation uses
+        // an application window type.
+        final WindowManagerImpl outerWindowManager =
+                (WindowManagerImpl)outerContext.getSystemService(Context.WINDOW_SERVICE);
+        final WindowManagerImpl displayWindowManager =
+                outerWindowManager.createPresentationWindowManager(display);
+        return new ContextThemeWrapper(displayContext, theme) {
+            @Override
+            public Object getSystemService(String name) {
+                if (Context.WINDOW_SERVICE.equals(name)) {
+                    return displayWindowManager;
+                }
+                return super.getSystemService(name);
+            }
+        };
+    }
+
+    private final DisplayListener mDisplayListener = new DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            if (displayId == mDisplay.getDisplayId()) {
+                handleDisplayRemoved();
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId == mDisplay.getDisplayId()) {
+                handleDisplayChanged();
+            }
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CANCEL:
+                    cancel();
+                    break;
+            }
+        }
+    };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c2b796a..8a4cb44 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -35,6 +35,8 @@
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.CompatibilityInfoHolder;
+import android.view.Display;
+import android.view.WindowManager;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -2547,7 +2549,7 @@
     /**
      * Return a new Context object for the current Context but whose resources
      * are adjusted to match the given Configuration.  Each call to this method
-     * returns a new instance of a Contex object; Context objects are not
+     * returns a new instance of a Context object; Context objects are not
      * shared, however common state (ClassLoader, other Resources for the
      * same configuration) may be so the Context itself can be fairly lightweight.
      *
@@ -2557,19 +2559,40 @@
      * orientation change), the resources of this context will also change except
      * for those that have been explicitly overridden with a value here.
      *
-     * @return A Context for the application.
+     * @return A Context with the given configuration override.
      */
     public abstract Context createConfigurationContext(Configuration overrideConfiguration);
 
     /**
+     * Return a new Context object for the current Context but whose resources
+     * are adjusted to match the metrics of the given Display.  Each call to this method
+     * returns a new instance of a Context object; Context objects are not
+     * shared, however common state (ClassLoader, other Resources for the
+     * same configuration) may be so the Context itself can be fairly lightweight.
+     *
+     * The returned display Context provides a {@link WindowManager}
+     * (see {@link #getSystemService(String)}) that is configured to show windows
+     * on the given display.  The WindowManager's {@link WindowManager#getDefaultDisplay}
+     * method can be used to retrieve the Display from the returned Context.
+     *
+     * @param display A {@link Display} object specifying the display
+     * for whose metrics the Context's resources should be tailored and upon which
+     * new windows should be shown.
+     *
+     * @return A Context for the display.
+     */
+    public abstract Context createDisplayContext(Display display);
+
+    /**
      * Gets the compatibility info holder for this context.  This information
      * is provided on a per-application basis and is used to simulate lower density
      * display metrics for legacy applications.
      *
+     * @param displayId The display id for which to get compatibility info.
      * @return The compatibility info holder, or null if not required by the application.
      * @hide
      */
-    public abstract CompatibilityInfoHolder getCompatibilityInfo();
+    public abstract CompatibilityInfoHolder getCompatibilityInfo(int displayId);
 
     /**
      * Indicates whether this Context is restricted.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e503388..e8c63d6 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.view.CompatibilityInfoHolder;
+import android.view.Display;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -582,13 +583,18 @@
     }
 
     @Override
+    public Context createDisplayContext(Display display) {
+        return mBase.createDisplayContext(display);
+    }
+
+    @Override
     public boolean isRestricted() {
         return mBase.isRestricted();
     }
 
     /** @hide */
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo() {
-        return mBase.getCompatibilityInfo();
+    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+        return mBase.getCompatibilityInfo(displayId);
     }
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 74996da..2814301 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.os.Handler;
 import android.util.SparseArray;
-import android.view.CompatibilityInfoHolder;
 import android.view.Display;
 
 /**
@@ -92,7 +91,7 @@
         Display display = mDisplays.get(displayId);
         if (display == null) {
             display = mGlobal.getCompatibleDisplay(displayId,
-                    getCompatibilityInfoForDisplayLocked(displayId));
+                    mContext.getCompatibilityInfo(displayId));
             if (display != null) {
                 mDisplays.put(displayId, display);
             }
@@ -102,14 +101,6 @@
         return display;
     }
 
-    private CompatibilityInfoHolder getCompatibilityInfoForDisplayLocked(int displayId) {
-        CompatibilityInfoHolder cih = null;
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            cih = mContext.getCompatibilityInfo();
-        }
-        return cih;
-    }
-
     /**
      * Registers an display listener to receive notifications about when
      * displays are added, removed or changed.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 69c0319..4077964 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -42,6 +42,16 @@
     private static final String TAG = "DisplayManager";
     private static final boolean DEBUG = false;
 
+    // True if display info and display ids should be cached.
+    //
+    // FIXME: The cache is currently disabled because it's unclear whether we have the
+    // necessary guarantees that the caches will always be flushed before clients
+    // attempt to observe their new state.  For example, depending on the order
+    // in which the binder transactions take place, we might have a problem where
+    // an application could start processing a configuration change due to a display
+    // orientation change before the display info cache has actually been invalidated.
+    private static final boolean USE_CACHE = 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;
@@ -91,21 +101,27 @@
     public DisplayInfo getDisplayInfo(int displayId) {
         try {
             synchronized (mLock) {
-                DisplayInfo info = mDisplayInfoCache.get(displayId);
-                if (info != null) {
-                    return info;
+                DisplayInfo info;
+                if (USE_CACHE) {
+                    info = mDisplayInfoCache.get(displayId);
+                    if (info != null) {
+                        return info;
+                    }
                 }
 
                 info = mDm.getDisplayInfo(displayId);
                 if (info == null) {
                     return null;
                 }
+
+                if (USE_CACHE) {
+                    mDisplayInfoCache.put(displayId, info);
+                }
+                registerCallbackIfNeededLocked();
+
                 if (DEBUG) {
                     Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
                 }
-
-                mDisplayInfoCache.put(displayId, info);
-                registerCallbackIfNeededLocked();
                 return info;
             }
         } catch (RemoteException ex) {
@@ -122,11 +138,18 @@
     public int[] getDisplayIds() {
         try {
             synchronized (mLock) {
-                if (mDisplayIdCache == null) {
-                    mDisplayIdCache = mDm.getDisplayIds();
-                    registerCallbackIfNeededLocked();
+                if (USE_CACHE) {
+                    if (mDisplayIdCache != null) {
+                        return mDisplayIdCache;
+                    }
                 }
-                return mDisplayIdCache;
+
+                int[] displayIds = mDm.getDisplayIds();
+                if (USE_CACHE) {
+                    mDisplayIdCache = displayIds;
+                }
+                registerCallbackIfNeededLocked();
+                return displayIds;
             }
         } catch (RemoteException ex) {
             Log.e(TAG, "Could not get display ids from display manager.", ex);
@@ -215,10 +238,12 @@
 
     private void handleDisplayEvent(int displayId, int event) {
         synchronized (mLock) {
-            mDisplayInfoCache.remove(displayId);
+            if (USE_CACHE) {
+                mDisplayInfoCache.remove(displayId);
 
-            if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
-                mDisplayIdCache = null;
+                if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
+                    mDisplayIdCache = null;
+                }
             }
 
             final int numListeners = mDisplayListeners.size();
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 0f9be9c..94de448 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -431,7 +431,12 @@
      * set up a Handler thread and need to perform some initialization steps on
      * it before continuing execution.
      *
+     * If timeout occurs then this method returns <code>false</code> but the runnable
+     * will remain posted on the handler and may already be in progress or
+     * complete at a later time.
+     *
      * @param r The Runnable that will be executed synchronously.
+     * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
      *
      * @return Returns true if the Runnable was successfully executed.
      *         Returns false on failure, usually because the
@@ -441,10 +446,13 @@
      * If we ever do make it part of the API, we might want to rename it to something
      * less funny like runUnsafe().
      */
-    public final boolean runWithScissors(final Runnable r) {
+    public final boolean runWithScissors(final Runnable r, long timeout) {
         if (r == null) {
             throw new IllegalArgumentException("runnable must not be null");
         }
+        if (timeout < 0) {
+            throw new IllegalArgumentException("timeout must be non-negative");
+        }
 
         if (Looper.myLooper() == mLooper) {
             r.run();
@@ -452,7 +460,7 @@
         }
 
         BlockingRunnable br = new BlockingRunnable(r);
-        return br.postAndWait(this);
+        return br.postAndWait(this, timeout);
     }
 
     /**
@@ -743,16 +751,30 @@
             }
         }
 
-        public boolean postAndWait(Handler handler) {
+        public boolean postAndWait(Handler handler, long timeout) {
             if (!handler.post(this)) {
                 return false;
             }
 
             synchronized (this) {
-                while (!mDone) {
-                    try {
-                        wait();
-                    } catch (InterruptedException ex) {
+                if (timeout > 0) {
+                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
+                    while (!mDone) {
+                        long delay = expirationTime - SystemClock.uptimeMillis();
+                        if (delay <= 0) {
+                            return false; // timeout
+                        }
+                        try {
+                            wait(delay);
+                        } catch (InterruptedException ex) {
+                        }
+                    }
+                } else {
+                    while (!mDone) {
+                        try {
+                            wait();
+                        } catch (InterruptedException ex) {
+                        }
                     }
                 }
             }
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 506594b..85e4b9d 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -206,13 +206,52 @@
     public void setToDefaults() {
         widthPixels = 0;
         heightPixels = 0;
-        density = noncompatDensity = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
-        densityDpi = noncompatDensityDpi = DENSITY_DEVICE;
+        density =  DENSITY_DEVICE / (float) DENSITY_DEFAULT;
+        densityDpi =  DENSITY_DEVICE;
         scaledDensity = density;
-        xdpi = noncompatXdpi = DENSITY_DEVICE;
-        ydpi = noncompatYdpi = DENSITY_DEVICE;
-        noncompatWidthPixels = 0;
-        noncompatHeightPixels = 0;
+        xdpi = DENSITY_DEVICE;
+        ydpi = DENSITY_DEVICE;
+        noncompatWidthPixels = widthPixels;
+        noncompatHeightPixels = heightPixels;
+        noncompatDensity = density;
+        noncompatDensityDpi = densityDpi;
+        noncompatScaledDensity = scaledDensity;
+        noncompatXdpi = xdpi;
+        noncompatYdpi = ydpi;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof DisplayMetrics && equals((DisplayMetrics)o);
+    }
+
+    /**
+     * Returns true if these display metrics equal the other display metrics.
+     *
+     * @param other The display metrics with which to compare.
+     * @return True if the display metrics are equal.
+     */
+    public boolean equals(DisplayMetrics other) {
+        return other != null
+                && widthPixels == other.widthPixels
+                && heightPixels == other.heightPixels
+                && density == other.density
+                && densityDpi == other.densityDpi
+                && scaledDensity == other.scaledDensity
+                && xdpi == other.xdpi
+                && ydpi == other.ydpi
+                && noncompatWidthPixels == other.noncompatWidthPixels
+                && noncompatHeightPixels == other.noncompatHeightPixels
+                && noncompatDensity == other.noncompatDensity
+                && noncompatDensityDpi == other.noncompatDensityDpi
+                && noncompatScaledDensity == other.noncompatScaledDensity
+                && noncompatXdpi == other.noncompatXdpi
+                && noncompatYdpi == other.noncompatYdpi;
+    }
+
+    @Override
+    public int hashCode() {
+        return widthPixels * heightPixels * densityDpi;
     }
 
     @Override
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ec635a2..8ac84f7a 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -53,6 +53,8 @@
 
     private final DisplayManagerGlobal mGlobal;
     private final int mDisplayId;
+    private final int mLayerStack;
+    private final String mName;
     private final CompatibilityInfoHolder mCompatibilityInfo;
 
     private DisplayInfo mDisplayInfo; // never null
@@ -90,6 +92,8 @@
         mGlobal = global;
         mDisplayId = displayId;
         mDisplayInfo = displayInfo;
+        mLayerStack = displayInfo.layerStack; // can never change as long as the display is valid
+        mName = displayInfo.name; // cannot change as long as the display is valid
         mCompatibilityInfo = compatibilityInfo;
         mIsValid = true;
     }
@@ -146,13 +150,11 @@
      * Each display has its own independent layer stack upon which surfaces
      * are placed to be managed by surface flinger.
      *
-     * @return The layer stack number.
+     * @return The display's layer stack number.
      * @hide
      */
     public int getLayerStack() {
-        // Note: This is the current convention but there is no requirement that
-        // the display id and layer stack id be the same.
-        return mDisplayId;
+        return mLayerStack;
     }
 
     /**
@@ -166,6 +168,14 @@
     }
 
     /**
+     * Gets the name of the display.
+     * @return The display's name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
      * Gets the size of the display, in pixels.
      * <p>
      * Note that this value should <em>not</em> be used for computing layouts,
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 593e8c4..b728d71 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -21,12 +21,24 @@
 import android.os.Parcelable;
 import android.util.DisplayMetrics;
 
+import libcore.util.Objects;
+
 /**
  * Describes the characteristics of a particular logical display.
  * @hide
  */
 public final class DisplayInfo implements Parcelable {
     /**
+     * The surface flinger layer stack associated with this logical display.
+     */
+    public int layerStack;
+
+    /**
+     * The human-readable name of the display.
+     */
+    public String name;
+
+    /**
      * The width of the portion of the display that is available to applications, in pixels.
      * Represents the size of the display minus any system decorations.
      */
@@ -147,11 +159,37 @@
     }
 
     @Override
-    public int describeContents() {
-        return 0;
+    public boolean equals(Object o) {
+        return o instanceof DisplayInfo && equals((DisplayInfo)o);
+    }
+
+    public boolean equals(DisplayInfo other) {
+        return other != null
+                && layerStack == other.layerStack
+                && Objects.equal(name, other.name)
+                && appWidth == other.appWidth
+                && appHeight == other.appHeight
+                && smallestNominalAppWidth == other.smallestNominalAppWidth
+                && smallestNominalAppHeight == other.smallestNominalAppHeight
+                && largestNominalAppWidth == other.largestNominalAppWidth
+                && largestNominalAppHeight == other.largestNominalAppHeight
+                && logicalWidth == other.logicalWidth
+                && logicalHeight == other.logicalHeight
+                && rotation == other.rotation
+                && refreshRate == other.refreshRate
+                && logicalDensityDpi == other.logicalDensityDpi
+                && physicalXDpi == other.physicalXDpi
+                && physicalYDpi == other.physicalYDpi;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
     }
 
     public void copyFrom(DisplayInfo other) {
+        layerStack = other.layerStack;
+        name = other.name;
         appWidth = other.appWidth;
         appHeight = other.appHeight;
         smallestNominalAppWidth = other.smallestNominalAppWidth;
@@ -168,6 +206,8 @@
     }
 
     public void readFromParcel(Parcel source) {
+        layerStack = source.readInt();
+        name = source.readString();
         appWidth = source.readInt();
         appHeight = source.readInt();
         smallestNominalAppWidth = source.readInt();
@@ -185,6 +225,8 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(layerStack);
+        dest.writeString(name);
         dest.writeInt(appWidth);
         dest.writeInt(appHeight);
         dest.writeInt(smallestNominalAppWidth);
@@ -200,6 +242,11 @@
         dest.writeFloat(physicalYDpi);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
     public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
         getMetricsWithSize(outMetrics, cih, appWidth, appHeight);
     }
@@ -231,13 +278,14 @@
     // For debugging purposes
     @Override
     public String toString() {
-        return "app " + appWidth + " x " + appHeight
+        return "DisplayInfo{\"" + name + "\", app " + appWidth + " x " + appHeight
                 + ", real " + logicalWidth + " x " + logicalHeight
                 + ", largest app " + largestNominalAppWidth + " x " + largestNominalAppHeight
                 + ", smallest app " + smallestNominalAppWidth + " x " + smallestNominalAppHeight
                 + ", " + refreshRate + " fps"
                 + ", rotation " + rotation
                 + ", density " + logicalDensityDpi
-                + ", " + physicalXDpi + " x " + physicalYDpi + " dpi";
+                + ", " + physicalXDpi + " x " + physicalYDpi + " dpi"
+                + ", layerStack " + layerStack + "}";
     }
 }
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 23337f0..4bbdd4e 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -453,7 +453,8 @@
         }
         mVelocityTracker.addMovement(ev);
 
-        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+        final boolean pointerUp =
+                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
         final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
 
         // Determine focal point
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index cf1767d7..6616894 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -743,17 +743,59 @@
     }
 
     /**
-     * Describes the properties of a physical display.
+     * Describes the properties of a physical display known to surface flinger.
      * @hide
      */
     public static final class PhysicalDisplayInfo {
-        // TODO: redesign this
         public int width;
         public int height;
         public float refreshRate;
         public float density;
         public float xDpi;
         public float yDpi;
+
+        public PhysicalDisplayInfo() {
+        }
+
+        public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
+            copyFrom(other);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
+        }
+
+        public boolean equals(PhysicalDisplayInfo other) {
+            return other != null
+                    && width == other.width
+                    && height == other.height
+                    && refreshRate == other.refreshRate
+                    && density == other.density
+                    && xDpi == other.xDpi
+                    && yDpi == other.yDpi;
+        }
+
+        @Override
+        public int hashCode() {
+            return 0; // don't care
+        }
+
+        public void copyFrom(PhysicalDisplayInfo other) {
+            width = other.width;
+            height = other.height;
+            refreshRate = other.refreshRate;
+            density = other.density;
+            xDpi = other.xDpi;
+            yDpi = other.yDpi;
+        }
+
+        // For debugging purposes
+        @Override
+        public String toString() {
+            return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
+                    + "density " + density + ", " + xDpi + " x " + yDpi + " dpi}";
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f30952c..7412f39 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.app.Presentation;
+import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.PixelFormat;
 import android.os.IBinder;
@@ -29,6 +31,17 @@
  * The interface that apps use to talk to the window manager.
  * <p>
  * Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these.
+ * </p><p>
+ * Each window manager instance is bound to a particular {@link Display}.
+ * To obtain a {@link WindowManager} for a different display, use
+ * {@link Context#createDisplayContext} to obtain a {@link Context} for that
+ * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
+ * to get the WindowManager.
+ * </p><p>
+ * The simplest way to show a window on another display is to create a
+ * {@link Presentation}.  The presentation will automatically obtain a
+ * {@link WindowManager} and {@link Context} for that display.
+ * </p>
  *
  * @see android.content.Context#getSystemService
  * @see android.content.Context#WINDOW_SERVICE
@@ -49,12 +62,24 @@
     }
 
     /**
-     * Use this method to get the default Display object.
-     * 
-     * @return default Display object
+     * Returns the {@link Display} upon which this {@link WindowManager} instance
+     * will create new windows.
+     * <p>
+     * Despite the name of this method, the display that is returned is not
+     * necessarily the primary display of the system (see {@link Display#DEFAULT_DISPLAY}).
+     * The returned display could instead be a secondary display that this
+     * window manager instance is managing.  Think of it as the display that
+     * this {@link WindowManager} instance uses by default.
+     * </p><p>
+     * To create windows on a different display, you need to obtain a
+     * {@link WindowManager} for that {@link Display}.  (See the {@link WindowManager}
+     * class documentation for more information.)
+     * </p>
+     *
+     * @return The display that this window manager is managing.
      */
     public Display getDefaultDisplay();
-    
+
     /**
      * Special variation of {@link #removeView} that immediately invokes
      * the given view hierarchy's {@link View#onDetachedFromWindow()
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index aa9179f..52d79f8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -16,9 +16,6 @@
 
 package android.view;
 
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-
 /**
  * Provides low-level communication with the system window manager for
  * operations that are bound to a particular context, display or parent window.
@@ -47,25 +44,24 @@
  */
 public final class WindowManagerImpl implements WindowManager {
     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
-    private final Context mContext;
     private final Display mDisplay;
     private final Window mParentWindow;
 
-    public WindowManagerImpl(Context context, int displayId) {
-        DisplayManager dm = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
-        mContext = context;
-        mDisplay = dm.getDisplay(displayId);
-        mParentWindow = null;
+    public WindowManagerImpl(Display display) {
+        this(display, null);
     }
 
-    private WindowManagerImpl(Context context, Display display, Window parentWindow) {
-        mContext = context;
+    private WindowManagerImpl(Display display, Window parentWindow) {
         mDisplay = display;
         mParentWindow = parentWindow;
     }
 
     public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
-        return new WindowManagerImpl(mContext, mDisplay, parentWindow);
+        return new WindowManagerImpl(mDisplay, parentWindow);
+    }
+
+    public WindowManagerImpl createPresentationWindowManager(Display display) {
+        return new WindowManagerImpl(display, mParentWindow);
     }
 
     @Override
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 41e8452..e4d4981 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.app.SearchManager.OnDismissListener;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.database.DataSetObserver;
@@ -579,6 +580,23 @@
     }
 
     /**
+     * Set a listener that will be invoked whenever the AutoCompleteTextView's
+     * list of completions is dismissed.
+     * @param dismissListener Listener to invoke when completions are dismissed
+     */
+    public void setOnDismissListener(final OnDismissListener dismissListener) {
+        PopupWindow.OnDismissListener wrappedListener = null;
+        if (dismissListener != null) {
+            wrappedListener = new PopupWindow.OnDismissListener() {
+                @Override public void onDismiss() {
+                    dismissListener.onDismiss();
+                }
+            };
+        }
+        mPopup.setOnDismissListener(wrappedListener);
+    }
+
+    /**
      * <p>Returns a filterable list adapter used for auto completion.</p>
      *
      * @return a data adapter used for auto completion
@@ -1207,6 +1225,19 @@
     }
     
     /**
+     * Listener to respond to the AutoCompleteTextView's completion list being dismissed.
+     * @see AutoCompleteTextView#setOnDismissListener(OnDismissListener)
+     */
+    public interface OnDismissListener {
+        /**
+         * This method will be invoked whenever the AutoCompleteTextView's list
+         * of completion options has been dismissed and is no longer available
+         * for user interaction.
+         */
+        void onDismiss();
+    }
+
+    /**
      * Allows us a private hook into the on click event without preventing users from setting
      * their own click listener.
      */
diff --git a/core/res/res/anim/keyguard_security_animate_in.xml b/core/res/res/anim/keyguard_security_animate_in.xml
index 6e1e17a..4ee30c3 100644
--- a/core/res/res/anim/keyguard_security_animate_in.xml
+++ b/core/res/res/anim/keyguard_security_animate_in.xml
@@ -26,8 +26,8 @@
         android:pivotY="50%"
         android:fillEnabled="true"
         android:fillAfter="true"
-        android:duration="@integer/flip_duration"
-        android:startOffset="@integer/flip_duration" />
+        android:duration="@integer/kg_security_flip_duration"
+        android:startOffset="@integer/kg_security_flip_duration" />
 
 </set>
 
diff --git a/core/res/res/anim/keyguard_security_animate_out.xml b/core/res/res/anim/keyguard_security_animate_out.xml
index 5d65cd0..76d065c 100644
--- a/core/res/res/anim/keyguard_security_animate_out.xml
+++ b/core/res/res/anim/keyguard_security_animate_out.xml
@@ -26,7 +26,7 @@
         android:pivotY="50%"
         android:fillEnabled="true"
         android:fillAfter="true"
-        android:duration="@integer/flip_duration" />
+        android:duration="@integer/kg_security_flip_duration" />
 
 </set>
 
diff --git a/core/res/res/layout/keyguard_navigation.xml b/core/res/res/layout/keyguard_navigation.xml
index 569f93d..f9e3ef8 100644
--- a/core/res/res/layout/keyguard_navigation.xml
+++ b/core/res/res/layout/keyguard_navigation.xml
@@ -23,12 +23,15 @@
     android:gravity="left">
 
     <LinearLayout
+        android:id="@+id/keyguard_click_area"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        style="?android:attr/buttonBarButtonStyle"
+        android:padding="10dip"
+        android:clickable="true">
 
         <TextView
-            android:id="@+id/back"
             android:layout_width="20dip"
             android:layout_height="wrap_content"
             android:textSize="28dp"
@@ -36,7 +39,7 @@
 
         <!-- message area for security screen -->
         <TextView
-            android:id="@+id/message_area"
+            android:id="@+id/keyguard_message_area"
             android:layout_width="0dip"
             android:layout_height="wrap_content"
             android:layout_weight="1"
@@ -48,14 +51,4 @@
 
     </LinearLayout>
 
-    <!-- This is currently only uses for pattern unlock -->
-    <Button android:id="@+id/forgot_password_button"
-        android:layout_gravity="right"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
-        android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
-        android:drawablePadding="0dip"
-        android:visibility="gone"/>
-
 </LinearLayout>
diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml
index b9a70e5..4ea471e 100644
--- a/core/res/res/layout/keyguard_password_view.xml
+++ b/core/res/res/layout/keyguard_password_view.xml
@@ -26,11 +26,6 @@
 
     <include layout="@layout/keyguard_navigation"/>
 
-    <Space
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1"/>
-
     <!-- Password entry field -->
     <!-- Note: the entire container is styled to look like the edit field,
          since the backspace/IME switcher looks better inside -->
@@ -84,6 +79,11 @@
 
     </LinearLayout>
 
+    <Space
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"/>
+
     <!-- Numeric keyboard -->
     <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/keyguard_pattern_view.xml b/core/res/res/layout/keyguard_pattern_view.xml
index 9cba609..954a92c 100644
--- a/core/res/res/layout/keyguard_pattern_view.xml
+++ b/core/res/res/layout/keyguard_pattern_view.xml
@@ -36,6 +36,15 @@
 
         <Space android:layout_gravity="fill" />
 
+        <Button android:id="@+id/forgot_password_button"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+            android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
+            android:drawablePadding="0dip"
+            android:visibility="gone"/>
+
         <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to
         the pattern view for it to compute its size. This is an unusual case, caused by
         LockPatternView's requirement to maintain a square aspect ratio based on the width
diff --git a/core/res/res/layout/overlay_display_window.xml b/core/res/res/layout/overlay_display_window.xml
index 25c792a..36b4a0d 100644
--- a/core/res/res/layout/overlay_display_window.xml
+++ b/core/res/res/layout/overlay_display_window.xml
@@ -16,7 +16,8 @@
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
-      android:layout_height="match_parent">
+      android:layout_height="match_parent"
+      android:background="#000000">
     <TextureView android:id="@+id/overlay_display_window_texture"
                android:layout_width="0px"
                android:layout_height="0px" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 209fff0..3757afc 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -768,11 +768,15 @@
         <attr name="dialogCustomTitleDecorLayout" format="reference" />
         <!-- Window decor layout to use in dialog mode with title only -->
         <attr name="dialogTitleDecorLayout" format="reference" />
+
         <!-- Theme to use for alert dialogs spawned from this theme. -->
         <attr name="alertDialogTheme" format="reference" />
         <!-- Icon drawable to use for alerts -->
         <attr name="alertDialogIcon" format="reference" />
 
+        <!-- Theme to use for presentations spawned from this theme. -->
+        <attr name="presentationTheme" format="reference" />
+
         <!-- Drawable to use for generic vertical dividers. -->
         <attr name="dividerVertical" format="reference" />
 
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
index 603fd7e..859eefc 100644
--- a/core/res/res/values/integers.xml
+++ b/core/res/res/values/integers.xml
@@ -17,5 +17,5 @@
 */
 -->
 <resources>
-    <integer name="flip_duration">300</integer>
+    <integer name="kg_security_flip_duration">150</integer>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6414df8..8e0eb15 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -482,6 +482,7 @@
   <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_name" />
+  <java-symbol type="string" name="display_manager_hdmi_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" />
@@ -1309,7 +1310,8 @@
   <java-symbol type="id" name="two" />
   <java-symbol type="id" name="unlock_widget" />
   <java-symbol type="id" name="zero" />
-  <java-symbol type="id" name="message_area" />
+  <java-symbol type="id" name="keyguard_message_area" />
+  <java-symbol type="id" name="keyguard_click_area" />
   <java-symbol type="id" name="keyguard_selector_view" />
   <java-symbol type="id" name="keyguard_pattern_view" />
   <java-symbol type="id" name="keyguard_password_view" />
@@ -1338,6 +1340,7 @@
   <java-symbol type="integer" name="config_lidNavigationAccessibility" />
   <java-symbol type="integer" name="config_lidOpenRotation" />
   <java-symbol type="integer" name="config_longPressOnHomeBehavior" />
+  <java-symbol type="integer" name="kg_security_flip_duration" />
   <java-symbol type="layout" name="global_actions_item" />
   <java-symbol type="layout" name="global_actions_silent_mode" />
   <java-symbol type="layout" name="keyguard_screen_glogin_unlock" />
@@ -1440,7 +1443,12 @@
   <java-symbol type="string" name="kg_login_invalid_input" />
   <java-symbol type="string" name="kg_login_account_recovery_hint" />
   <java-symbol type="string" name="kg_login_checking_password" />
-
+  <java-symbol type="string" name="kg_too_many_failed_pin_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_too_many_failed_pattern_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_too_many_failed_password_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_failed_attempts_almost_at_wipe" />
+  <java-symbol type="string" name="kg_failed_attempts_now_wiping" />
+  <java-symbol type="string" name="kg_failed_attempts_almost_at_login" />
 
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
@@ -3752,5 +3760,6 @@
   <public type="attr" name="listPreferredItemPaddingStart" />
   <public type="attr" name="listPreferredItemPaddingEnd" />
   <public type="attr" name="singleUser" />
+  <public type="attr" name="presentationTheme" />
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3178af0..381055f 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3658,40 +3658,121 @@
     <!-- Name of the built-in display.  [CHAR LIMIT=50] -->
     <string name="display_manager_built_in_display_name">Built-in Screen</string>
 
+    <!-- Name of the HDMI display.  [CHAR LIMIT=50] -->
+    <string name="display_manager_hdmi_display_name">HDMI 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>
+    <string name="display_manager_overlay_display_title"><xliff:g id="name">%1$s</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>
 
     <!-- Keyguard strings -->
+    <!-- Label shown on emergency call button in keyguard -->
     <string name="kg_emergency_call_label">Emergency call</string>
+    <!-- Message shown in pattern unlock after some number of unsuccessful attempts -->
     <string name="kg_forgot_pattern_button_text">Forgot Pattern</string>
+    <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong Pattern</string>
+    <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong Password</string>
+    <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
-    <string name="kg_too_many_failed_attempts_countdown">Too many attempts</string>
+    <!-- Countdown message shown after too many failed unlock attempts -->
+    <string name="kg_too_many_failed_attempts_countdown">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+    <!-- Instructions for using the pattern unlock screen -->
     <string name="kg_pattern_instructions">Draw your pattern</string>
+    <!-- Instructions for using the SIM PIN unlock screen -->
     <string name="kg_sim_pin_instructions">Enter SIM PIN</string>
+    <!-- Instructions for using the PIN unlock screen -->
     <string name="kg_pin_instructions">Enter PIN</string>
+    <!-- Instructions for using the password unlock screen -->
     <string name="kg_password_instructions">Enter Password</string>
+    <!-- Hint shown in the PUK unlock screen PUK TextView -->
     <string name="kg_puk_enter_puk_hint">PUK code</string>
+    <!-- Hint shown in the PUK unlock screen PIN TextView -->
     <string name="kg_puk_enter_pin_hint">New PIN code</string>
+    <!-- Message shown in dialog while the device is unlocking the SIM card -->
     <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <!-- Message shown when the user enters the wrong PIN code -->
     <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string>
+    <!-- Message shown when the user enters an invalid SIM pin password in PUK screen -->
     <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
+    <!-- Message shown when the user enters an invalid PUK code in the PUK screen -->
     <string name="kg_invalid_sim_puk_hint">Type a PUK that is 8 numbers or longer.</string>
+    <!-- Instructions for PUK unlock screen -->
     <string name="kg_sim_puk_recovery_hint">Type PUK and new PIN code</string>
+    <!-- Message shown when the user enters an invalid PUK code -->
     <string name="kg_invalid_puk">The PUK you typed isn\'t correct.</string>
-
+    <!-- Message shown when the user exceeds the maximum number of pattern attempts -->
     <string name="kg_login_too_many_attempts">Too many pattern attempts</string>
+    <!-- Instructions show in account unlock screen allowing user to enter their email password -->
     <string name="kg_login_instructions">To unlock, sign in with your Google account.</string>
+    <!-- Hint shown in TextView in account unlock screen of keyguard -->
     <string name="kg_login_username_hint">Username (email)</string>
+    <!-- Hint shown in TextView in account unlock screen of keyguard -->
     <string name="kg_login_password_hint">Password</string>
+    <!-- Label shown on sign in button on account unlock screen of keyguard -->
     <string name="kg_login_submit_button">Sign in</string>
+    <!-- Message shown when the user enters an invalid username/password combination in account unlock screen of keyguard -->
     <string name="kg_login_invalid_input">Invalid username or password.</string>
+    <!-- Hint text shown when user has too many failed password attempts in account unlock screen of keyguard -->
     <string name="kg_login_account_recovery_hint">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string>
+    <!-- Message shown while device checks username/password in account unlock screen of keyguard -->
     <string name="kg_login_checking_password">Checking\u2026</string>
+    <!-- Message shown in dialog when max number of attempts are reached for PIN screen of keyguard -->
+    <string name="kg_too_many_failed_pin_attempts_dialog_message">
+        You have incorrectly typed your PIN <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- Message shown in dialog when max number of attempts are reached for password screen of keyguard -->
+    <string name="kg_too_many_failed_password_attempts_dialog_message">
+        You have incorrectly typed your password <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message">
+        You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- Message shown when user is almost at the limit of password attempts where the device will be wiped. -->
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet">
+       You have incorrectly attempted to unlock the tablet <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       the tablet will be reset to factory default and all user data will be lost.
+    </string>
+    <!-- Message shown when user is almost at the limit of password attempts where the device will be wiped. -->
+    <string name="kg_failed_attempts_almost_at_wipe" product="default">
+       You have incorrectly attempted to unlock the phone <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       the phone will be reset to factory default and all user data will be lost.
+    </string>
+    <!-- Message shown in dialog when user has exceeded the maximum attempts and the device will now be wiped -->
+    <string name="kg_failed_attempts_now_wiping" product="tablet">
+       You have incorrectly attempted to unlock the tablet <xliff:g id="number">%d</xliff:g> times.
+       The tablet will now be reset to factory default.
+    </string>
+    <!-- Message shown in dialog when user has exceeded the maximum attempts and the device will now be wiped -->
+    <string name="kg_failed_attempts_now_wiping" product="default">
+       You have incorrectly attempted to unlock the phone <xliff:g id="number">%d</xliff:g> times.
+       The phone will now be reset to factory default.
+    </string>
+    <!-- Message shown in dialog when user is almost at the limit where they will be
+    locked out and may have to enter an alternate username/password to unlock the phone -->
+    <string name="kg_failed_attempts_almost_at_login" product="tablet">
+       You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       you will be asked to unlock your tablet using an email account.\n\n
+       Try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- Message shown in dialog when user is almost at the limit where they will be
+    locked out and may have to enter an alternate username/password to unlock the phone -->
+    <string name="kg_failed_attempts_almost_at_login" product="default">
+       You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       you will be asked to unlock your phone using an email account.\n\n
+       Try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
     <string name="kg_temp_back_string"> &lt; </string> <!-- TODO: remove this -->
 
 </resources>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 28fed45..d465356 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -673,7 +673,6 @@
 
     </style>
 
-
     <!-- Animation Styles -->
     <style name="Animation.DeviceDefault.Activity" parent="Animation.Holo.Activity">
 
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 2f93335..215551b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -186,17 +186,24 @@
         <item name="windowFixedHeightMinor">0dp</item>
 
         <!-- Dialog attributes -->
-        <item name="alertDialogStyle">@android:style/AlertDialog</item>
         <item name="dialogTheme">@android:style/Theme.Dialog</item>
         <item name="dialogTitleIconsDecorLayout">@layout/dialog_title_icons</item>
         <item name="dialogCustomTitleDecorLayout">@layout/dialog_custom_title</item>
         <item name="dialogTitleDecorLayout">@layout/dialog_title</item>
+
+        <!-- AlertDialog attributes -->
         <item name="alertDialogTheme">@android:style/Theme.Dialog.Alert</item>
+        <item name="alertDialogStyle">@android:style/AlertDialog</item>
         <item name="alertDialogCenterButtons">true</item>
         <item name="alertDialogIcon">@android:drawable/ic_dialog_alert</item>
 
+        <!-- Presentation attributes (introduced after API level 10 so does not
+             have a special old-style theme. -->
+        <item name="presentationTheme">@android:style/Theme.DeviceDefault.Dialog.Presentation</item>
+
+        <!-- Toast attributes -->
         <item name="toastFrameBackground">@android:drawable/toast_frame</item>
-        
+
         <!-- Panel attributes -->
         <item name="panelBackground">@android:drawable/menu_background</item>
         <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
@@ -1012,17 +1019,23 @@
         <item name="windowActionModeOverlay">false</item>
 
         <!-- Dialog attributes -->
-        <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item>
         <item name="dialogTheme">@android:style/Theme.Holo.Dialog</item>
         <item name="dialogTitleIconsDecorLayout">@layout/dialog_title_icons_holo</item>
         <item name="dialogCustomTitleDecorLayout">@layout/dialog_custom_title_holo</item>
         <item name="dialogTitleDecorLayout">@layout/dialog_title_holo</item>
+
+        <!-- AlertDialog attributes -->
         <item name="alertDialogTheme">@android:style/Theme.Holo.Dialog.Alert</item>
+        <item name="alertDialogStyle">@android:style/AlertDialog.Holo</item>
         <item name="alertDialogCenterButtons">false</item>
         <item name="alertDialogIcon">@android:drawable/ic_dialog_alert_holo_dark</item>
 
+        <!-- Presentation attributes -->
+        <item name="presentationTheme">@android:style/Theme.Holo.Dialog.Presentation</item>
+
+        <!-- Toast attributes -->
         <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
-        
+
         <!-- Panel attributes -->
         <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_dark</item>
         <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
@@ -1319,17 +1332,23 @@
         <item name="windowActionModeOverlay">false</item>
 
         <!-- Dialog attributes -->
-        <item name="alertDialogStyle">@android:style/AlertDialog.Holo.Light</item>
         <item name="dialogTheme">@android:style/Theme.Holo.Light.Dialog</item>
         <item name="dialogTitleIconsDecorLayout">@layout/dialog_title_icons_holo</item>
         <item name="dialogCustomTitleDecorLayout">@layout/dialog_custom_title_holo</item>
         <item name="dialogTitleDecorLayout">@layout/dialog_title_holo</item>
-        <item name="alertDialogCenterButtons">false</item>
+
+        <!-- AlertDialog attributes -->
         <item name="alertDialogTheme">@android:style/Theme.Holo.Light.Dialog.Alert</item>
+        <item name="alertDialogStyle">@android:style/AlertDialog.Holo.Light</item>
+        <item name="alertDialogCenterButtons">false</item>
         <item name="alertDialogIcon">@android:drawable/ic_dialog_alert_holo_light</item>
 
+        <!-- Presentation attributes -->
+        <item name="presentationTheme">@android:style/Theme.Holo.Light.Dialog.Presentation</item>
+
+        <!-- Toast attributes -->
         <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
-        
+
         <!-- Panel attributes -->
         <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_light</item>
         <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
@@ -1663,6 +1682,10 @@
     <style name="Theme.Holo.DialogWhenLarge.NoActionBar" parent="@android:style/Theme.Holo.NoActionBar">
     </style>
 
+    <!-- Theme for a presentation window on a secondary display. -->
+    <style name="Theme.Holo.Dialog.Presentation" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
+    </style>
+
     <!-- Light holo dialog themes -->
 
     <!-- Holo light theme for dialog windows and activities, which is used by the
@@ -1760,6 +1783,10 @@
         <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
     </style>
 
+    <!-- Theme for a presentation window on a secondary display. -->
+    <style name="Theme.Holo.Light.Dialog.Presentation" parent="@android:style/Theme.Holo.Light.NoActionBar.Fullscreen" >
+    </style>
+
     <!-- Default holographic (dark) for windows that want to have the user's selected
          wallpaper appear behind them.  -->
     <style name="Theme.Holo.Wallpaper">
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index eef831f..2a2b9e0 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -87,9 +87,14 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Activity</item>
 
         <!-- Dialog attributes -->
-        <item name="alertDialogStyle">@android:style/AlertDialog.DeviceDefault</item>
         <item name="dialogTheme">@android:style/Theme.DeviceDefault.Dialog</item>
+
+        <!-- AlertDialog attributes -->
         <item name="alertDialogTheme">@android:style/Theme.DeviceDefault.Dialog.Alert</item>
+        <item name="alertDialogStyle">@android:style/AlertDialog.DeviceDefault</item>
+
+        <!-- Presentation attributes -->
+        <item name="presentationTheme">@android:style/Theme.DeviceDefault.Dialog.Presentation</item>
 
         <!-- Text selection handle attributes -->
         <item name="textSelectHandleWindowStyle">@android:style/Widget.DeviceDefault.TextSelectHandle</item>
@@ -239,9 +244,14 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.DeviceDefault.Activity</item>
 
         <!-- Dialog attributes -->
-        <item name="alertDialogStyle">@android:style/AlertDialog.DeviceDefault.Light</item>
         <item name="dialogTheme">@android:style/Theme.DeviceDefault.Light.Dialog</item>
+
+        <!-- AlertDialog attributes -->
         <item name="alertDialogTheme">@android:style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+        <item name="alertDialogStyle">@android:style/AlertDialog.DeviceDefault.Light</item>
+
+        <!-- Presentation attributes -->
+        <item name="presentationTheme">@android:style/Theme.DeviceDefault.Light.Dialog.Presentation</item>
 
         <!-- Text selection handle attributes -->
         <item name="textSelectHandleWindowStyle">@android:style/Widget.DeviceDefault.TextSelectHandle</item>
@@ -460,6 +470,15 @@
     <style name="Theme.DeviceDefault.Light.DialogWhenLarge.NoActionBar" parent="Theme.Holo.Light.DialogWhenLarge.NoActionBar" >
 
     </style>
+
+    <!-- DeviceDefault theme for a presentation window on a secondary display. -->
+    <style name="Theme.DeviceDefault.Dialog.Presentation" parent="Theme.Holo.Dialog.Presentation">
+    </style>
+
+    <!-- DeviceDefault light theme for a presentation window on a secondary display. -->
+    <style name="Theme.DeviceDefault.Light.Dialog.Presentation" parent="Theme.Holo.Light.Dialog.Presentation">
+    </style>
+
     <!-- DeviceDefault theme for panel windows. This removes all extraneous window
     decorations, so you basically have an empty rectangle in which to place your content. It makes
     the window floating, with a transparent background, and turns off dimming behind the window. -->
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png
index 6ae32f1..84e6bc8 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png
index fdc56bb..782d214 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png
index ea7c4e3..a00bc5b 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png
index 49d5101..8605701 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_back.png
index 33e56e8..38bd0cd 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png
index 2fb191d..0c12c16 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png
index ce008f3..477df5f 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png
index b971088..bd60cd6 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png
index d2d7842..5272c91 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9b4ee38..31bc8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -451,6 +451,7 @@
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_SCREEN_ON);
         context.registerReceiver(mBroadcastReceiver, filter);
 
         return mStatusBarView;
@@ -788,11 +789,6 @@
         setAreThereNotifications();
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        updateShowSearchHoldoff();
-    }
-
     private void updateShowSearchHoldoff() {
         mShowSearchHoldoff = mContext.getResources().getInteger(
             R.integer.config_show_search_delay);
@@ -1804,9 +1800,17 @@
                 makeExpandedInvisible();
             }
             else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+                if (DEBUG) {
+                    Slog.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
+                }
                 updateResources();
                 repositionNavigationBar();
                 updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+                updateShowSearchHoldoff();
+            }
+            else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                // work around problem where mDisplay.getRotation() is not stable while screen is off (bug 7086018)
+                repositionNavigationBar();
             }
         }
     };
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index d74a5e7..2551c04e 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -16,8 +16,11 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
@@ -31,9 +34,10 @@
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.widget.Button;
 import android.widget.ViewFlipper;
@@ -74,8 +78,8 @@
     private ViewFlipper mViewFlipper;
     private Button mEmergencyDialerButton;
     private boolean mEnableMenuKey;
-    private boolean mScreenOn;
     private boolean mIsVerifyUnlockOnly;
+    private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView
     private int mCurrentSecurityId = SECURITY_SELECTOR_ID;
 
     // KeyguardSecurityViews
@@ -116,7 +120,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        mCallback.keyguardDoneDrawing();
+        mViewMediatorCallback.keyguardDoneDrawing();
     }
 
     @Override
@@ -196,29 +200,25 @@
         }
 
         public boolean isVerifyUnlockOnly() {
-            // TODO
-            return false;
+            return mIsVerifyUnlockOnly;
         }
 
         public void reportSuccessfulUnlockAttempt() {
-            KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts();
+            KeyguardUpdateMonitor.getInstance(mContext).clearFailedUnlockAttempts();
         }
 
         public void reportFailedUnlockAttempt() {
             // TODO: handle biometric attempt differently.
-            KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt();
+            KeyguardHostView.this.reportFailedUnlockAttempt();
         }
 
         public int getFailedAttempts() {
-            return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts();
+            return KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts();
         }
 
-        public void showBackupUnlock() {
-            // TODO
-        }
-
-        public void keyguardDoneDrawing() {
-            mViewMediatorCallback.keyguardDoneDrawing();
+        @Override
+        public void showBackupSecurity() {
+            KeyguardHostView.this.showBackupSecurity();
         }
 
         @Override
@@ -228,6 +228,9 @@
 
     };
 
+    /**
+     * Shows the emergency dialer or returns the user to the existing call.
+     */
     public void takeEmergencyCallAction() {
         mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
         if (TelephonyManager.getDefault().getCallState()
@@ -241,19 +244,147 @@
         }
     }
 
-    protected void showNextSecurityScreenOrFinish(boolean authenticated) {
+    private void showDialog(String title, String message) {
+        final AlertDialog dialog = new AlertDialog.Builder(mContext)
+            .setTitle(title)
+            .setMessage(message)
+            .setNeutralButton(com.android.internal.R.string.ok, null)
+            .create();
+        if (!(mContext instanceof Activity)) {
+            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        }
+        dialog.show();
+    }
+
+    private void showTimeoutDialog() {
+        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+        int messageId = 0;
+
+        switch (mSecurityModel.getSecurityMode()) {
+            case Pattern:
+                messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
+                break;
+
+            case Password: {
+                    final boolean isPin = mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
+                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+                    messageId = isPin ? R.string.kg_too_many_failed_pin_attempts_dialog_message
+                            : R.string.kg_too_many_failed_password_attempts_dialog_message;
+                }
+                break;
+        }
+
+        if (messageId != 0) {
+            final String message = mContext.getString(messageId,
+                    KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(),
+                    timeoutInSeconds);
+            showDialog(null, message);
+        }
+    }
+
+    private void showAlmostAtWipeDialog(int attempts, int remaining) {
+        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+        String message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
+                attempts, remaining);
+        showDialog(null, message);
+    }
+
+    private void showWipeDialog(int attempts) {
+        String message = mContext.getString(R.string.kg_failed_attempts_now_wiping, attempts);
+        showDialog(null, message);
+    }
+
+    private void showAlmostAtAccountLoginDialog() {
+        final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+        final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+                - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+        String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login,
+                count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds);
+        showDialog(null, message);
+    }
+
+    private void reportFailedUnlockAttempt() {
+        final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time
+
+        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+
+        SecurityMode mode = mSecurityModel.getSecurityMode();
+        final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern;
+
+        final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager()
+                .getMaximumFailedPasswordsForWipe(null);
+
+        final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+                - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+
+        final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
+                (failedAttemptsBeforeWipe - failedAttempts)
+                : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
+
+        if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
+            // If we reach this code, it means the user has installed a DevicePolicyManager
+            // that requests device wipe after N attempts.  Once we get below the grace
+            // period, we'll post this dialog every time as a clear warning until the
+            // bombshell hits and the device is wiped.
+            if (remainingBeforeWipe > 0) {
+                showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
+            } else {
+                // Too many attempts. The device will be wiped shortly.
+                Slog.i(TAG, "Too many unlock attempts; device will be wiped!");
+                showWipeDialog(failedAttempts);
+            }
+        } else {
+            boolean showTimeout =
+                (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
+            if (usingPattern && mEnableFallback) {
+                if (failedAttempts == failedAttemptWarning) {
+                    showAlmostAtAccountLoginDialog();
+                    showTimeout = false; // don't show both dialogs
+                } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
+                    mLockPatternUtils.setPermanentlyLocked(true);
+                    showSecurityScreen(SECURITY_ACCOUNT_ID);
+                    // don't show timeout dialog because we show account unlock screen next
+                    showTimeout = false;
+                }
+            }
+            if (showTimeout) {
+                showTimeoutDialog();
+            }
+        }
+        monitor.reportFailedUnlockAttempt();
+        mLockPatternUtils.reportFailedPasswordAttempt();
+    }
+
+    /**
+     * Shows the backup security screen for the current security mode.  This could be used for
+     * password recovery screens but is currently only used for pattern unlock to show the
+     * account unlock screen and biometric unlock to show the user's normal unlock.
+     */
+    private void showBackupSecurity() {
+        SecurityMode currentMode = mSecurityModel.getSecurityMode();
+        SecurityMode backup = mSecurityModel.getBackupFor(currentMode);
+        showSecurityScreen(getSecurityViewIdForMode(backup));
+    }
+
+    private void showNextSecurityScreenOrFinish(boolean authenticated) {
         boolean finish = false;
         if (SECURITY_SELECTOR_ID == mCurrentSecurityId) {
-            int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode());
-            if (realSecurityId == mCurrentSecurityId) {
+            SecurityMode securityMode = mSecurityModel.getSecurityMode();
+            // Allow an alternate, such as biometric unlock
+            // TODO: un-comment when face unlock is working again:
+            // securityMode = mSecurityModel.getAlternateFor(securityMode);
+            int realSecurityId = getSecurityViewIdForMode(securityMode);
+            if (SECURITY_SELECTOR_ID == realSecurityId) {
                 finish = true; // no security required
             } else {
                 showSecurityScreen(realSecurityId); // switch to the "real" security view
             }
         } else if (authenticated) {
-            if ((mCurrentSecurityId == SECURITY_PATTERN_ID
+            if (mCurrentSecurityId == SECURITY_PATTERN_ID
                 || mCurrentSecurityId == SECURITY_PASSWORD_ID
-                || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) {
+                || mCurrentSecurityId == SECURITY_ACCOUNT_ID
+                || mCurrentSecurityId == SECURITY_BIOMETRIC_ID) {
                 finish = true;
             }
         } else {
@@ -309,6 +440,7 @@
 
     @Override
     public void reset() {
+        mIsVerifyUnlockOnly = false;
         requestFocus();
     }
 
@@ -330,6 +462,12 @@
         return null;
     }
 
+    /**
+     * Switches to the given security view unless it's already being shown, in which case
+     * this is a no-op.
+     *
+     * @param securityViewId
+     */
     private void showSecurityScreen(int securityViewId) {
 
         if (securityViewId == mCurrentSecurityId) return;
@@ -352,19 +490,22 @@
                 break;
             }
         }
+
+        // Discard current runnable if we're switching back to the selector view
+        if (securityViewId == SECURITY_SELECTOR_ID) {
+            setOnDismissRunnable(null);
+        }
     }
 
     @Override
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on");
-        mScreenOn = true;
         showSecurityScreen(mCurrentSecurityId);
     }
 
     @Override
     public void onScreenTurnedOff() {
         if (DEBUG) Log.d(TAG, "screen off");
-        mScreenOn = false;
         showSecurityScreen(SECURITY_SELECTOR_ID);
     }
 
@@ -469,7 +610,8 @@
     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
     private boolean shouldEnableMenuKey() {
         final Resources res = getResources();
-        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
+        final boolean configDisabled = res.getBoolean(
+                com.android.internal.R.bool.config_disableMenuKeyInLockScreen);
         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
         return !configDisabled || isTestHarness || fileOverride;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
index d3feced..4f29825 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
@@ -25,12 +25,15 @@
 
     private TextView mMessageArea;
     private KeyguardSecurityView mKeyguardSecurityView;
+    private View mClickArea;
 
     public KeyguardNavigationManager(KeyguardSecurityView view) {
         mKeyguardSecurityView = view;
-        mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area);
+        mMessageArea = (TextView) ((View) view).findViewById(R.id.keyguard_message_area);
         mMessageArea.setSelected(true); // Make marquee work
-        mMessageArea.setOnClickListener(new View.OnClickListener() {
+
+        mClickArea = ((View) view).findViewById(R.id.keyguard_click_area);
+        mClickArea.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 mKeyguardSecurityView.getCallback().dismiss(false);
             }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
index a95cfcb..3a32b5b 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
@@ -129,7 +129,7 @@
         mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
         mForgotPatternButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
-                mCallback.showBackupUnlock();
+                mCallback.showBackupSecurity();
             }
         });
 
@@ -233,6 +233,7 @@
                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
                 mCallback.dismiss(true); // keyguardDone(true)
                 KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
+                mTotalFailedPatternAttempts = 0;
             } else {
                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                     mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
@@ -313,13 +314,15 @@
         mLockPatternView.clearPattern();
         mLockPatternView.setEnabled(false);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
+        updateFooter(FooterMode.ForgotLockPattern);
+
         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
 
             @Override
             public void onTick(long millisUntilFinished) {
                 final int secondsRemaining = (int) (millisUntilFinished / 1000);
                 mNavigationManager.setMessage(
-                        R.string.kg_too_many_failed_attempts_countdown,secondsRemaining);
+                        R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
             }
 
             @Override
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
index 36342a5..3b8df5d 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
@@ -53,17 +53,12 @@
     int getFailedAttempts();
 
     /**
-     * Shows the backup unlock for the current method.  If none available, this call is a NOP.
+     * Shows the backup security for the current method.  If none available, this call is a no-op.
      */
-    void showBackupUnlock();
+    void showBackupSecurity();
 
     /**
-     * Used to inform keyguard when the current view is done drawing.
-     */
-    void keyguardDoneDrawing();
-
-    /**
-     * Sets a runnable to launch after the user enters their
+     * Sets a runnable to launch after the user successfully enters their credentials.
      * @param runnable
      */
     void setOnDismissRunnable(Runnable runnable);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
index d041dd3..a19b35d 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -48,38 +48,85 @@
         mLockPatternUtils = utils;
     }
 
+    /**
+     * This returns false if there is any condition that indicates that the biometric unlock should
+     * not be used before the next time the unlock screen is recreated.  In other words, if this
+     * returns false there is no need to even construct the biometric unlock.
+     */
+    private boolean isBiometricUnlockEnabled() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        final boolean backupIsTimedOut =
+                monitor.getFailedUnlockAttempts() >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+        return mLockPatternUtils.usingBiometricWeak()
+                && mLockPatternUtils.isBiometricWeakInstalled()
+                && !monitor.getMaxBiometricUnlockAttemptsReached()
+                && !backupIsTimedOut;
+    }
+
     SecurityMode getSecurityMode() {
         KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         final IccCardConstants.State simState = mUpdateMonitor.getSimState();
+        SecurityMode mode = SecurityMode.None;
         if (simState == IccCardConstants.State.PIN_REQUIRED) {
-            return SecurityMode.SimPin;
+            mode = SecurityMode.SimPin;
         } else if (simState == IccCardConstants.State.PUK_REQUIRED) {
-            return SecurityMode.SimPuk;
+            mode = SecurityMode.SimPuk;
         } else {
-            final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
-            switch (mode) {
+            final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+            switch (security) {
                 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
-                    return mLockPatternUtils.isLockPasswordEnabled() ?
+                    mode = mLockPatternUtils.isLockPasswordEnabled() ?
                             SecurityMode.Password : SecurityMode.None;
+                    break;
 
                 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
                 case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
                     if (mLockPatternUtils.isLockPatternEnabled()) {
-                        return mLockPatternUtils.isPermanentlyLocked() ?
+                        mode = mLockPatternUtils.isPermanentlyLocked() ?
                             SecurityMode.Account : SecurityMode.Pattern;
-                    } else {
-                        return SecurityMode.None;
                     }
+                    break;
+
                 default:
-                   throw new IllegalStateException("Unknown unlock mode:" + mode);
+                    throw new IllegalStateException("Unknown unlock mode:" + mode);
             }
         }
+        return mode;
     }
 
+    /**
+     * Some unlock methods can have an alternate, such as biometric unlocks (e.g. face unlock).
+     * This function decides if an alternate unlock is available and returns it. Otherwise,
+     * returns @param mode.
+     *
+     * @param mode the mode we want the alternate for
+     * @return alternate or the given mode
+     */
+    SecurityMode getAlternateFor(SecurityMode mode) {
+        if (isBiometricUnlockEnabled()
+                && (mode == SecurityMode.Password || mode == SecurityMode.Pattern)) {
+            return SecurityMode.Biometric;
+        }
+        return mode; // no alternate, return what was given
+    }
+
+    /**
+     * Some unlock methods can have a backup which gives the user another way to get into
+     * the device. This is currently only supported for Biometric and Pattern unlock.
+     *
+     * @param mode the mode we want the backup for
+     * @return backup method or given mode
+     */
     SecurityMode getBackupFor(SecurityMode mode) {
-        return SecurityMode.None;  // TODO: handle biometric unlock, etc.
+        switch(mode) {
+            case Biometric:
+                return getSecurityMode();
+            case Pattern:
+                return SecurityMode.Account;
+        }
+        return mode; // no backup, return what was given
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
index dad0dff..39df5a2 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -53,8 +53,8 @@
  *
  * Note: under time crunch, this has been extended to include some stuff that
  * doesn't really belong here.  see {@link #handleBatteryUpdate} where it shutdowns
- * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
- * and {@link #clearFailedAttempts()}.  Maybe we should rename this 'KeyguardContext'...
+ * the device, and {@link #getFailedUnlockAttempts()}, {@link #reportFailedAttempt()}
+ * and {@link #clearFailedUnlockAttempts()}.  Maybe we should rename this 'KeyguardContext'...
  */
 public class KeyguardUpdateMonitor {
 
@@ -658,16 +658,16 @@
         return mDeviceProvisioned;
     }
 
-    public int getFailedAttempts() {
+    public int getFailedUnlockAttempts() {
         return mFailedAttempts;
     }
 
-    public void clearFailedAttempts() {
+    public void clearFailedUnlockAttempts() {
         mFailedAttempts = 0;
         mFailedBiometricUnlockAttempts = 0;
     }
 
-    public void reportFailedAttempt() {
+    public void reportFailedUnlockAttempt() {
         mFailedAttempts++;
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
index d6733ea..3ee82f7 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -986,7 +986,7 @@
             mHandler.sendMessage(msg);
 
             if (authenticated) {
-                mUpdateMonitor.clearFailedAttempts();
+                mUpdateMonitor.clearFailedUnlockAttempts();
             }
 
             if (mExitSecureCallback != null) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
index d778129..e8078fd 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
@@ -53,12 +53,9 @@
         super(context, attrs, defStyle);
         if (sLeftOverscrollDrawable == null) {
             Resources res = context.getResources();
-            sLeftOverscrollDrawable = res.getDrawable(
-                    com.android.internal.R.drawable.kg_widget_overscroll_layer_left);
-            sRightOverscrollDrawable = res.getDrawable(
-                    com.android.internal.R.drawable.kg_widget_overscroll_layer_right);
-            sWidgetPagePadding =
-                    res.getDimensionPixelSize(com.android.internal.R.dimen.kg_widget_page_padding);
+            sLeftOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_left);
+            sRightOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_right);
+            sWidgetPagePadding = res.getDimensionPixelSize(R.dimen.kg_widget_page_padding);
         }
         setPadding(sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding);
     }
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index ac8a514..f4ad756 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -243,32 +243,39 @@
                 "android.permission.SET_TIME_ZONE",
                 "setTimeZone");
 
-        if (TextUtils.isEmpty(tz)) return;
-        TimeZone zone = TimeZone.getTimeZone(tz);
-        // Prevent reentrant calls from stepping on each other when writing
-        // the time zone property
-        boolean timeZoneWasChanged = false;
-        synchronized (this) {
-            String current = SystemProperties.get(TIMEZONE_PROPERTY);
-            if (current == null || !current.equals(zone.getID())) {
-                if (localLOGV) Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
-                timeZoneWasChanged = true; 
-                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
-            }
-            
-            // Update the kernel timezone information
-            // Kernel tracks time offsets as 'minutes west of GMT'
-            int gmtOffset = zone.getOffset(System.currentTimeMillis());
-            setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
-        }
+        long oldId = Binder.clearCallingIdentity();
+        try {
+            if (TextUtils.isEmpty(tz)) return;
+            TimeZone zone = TimeZone.getTimeZone(tz);
+            // Prevent reentrant calls from stepping on each other when writing
+            // the time zone property
+            boolean timeZoneWasChanged = false;
+            synchronized (this) {
+                String current = SystemProperties.get(TIMEZONE_PROPERTY);
+                if (current == null || !current.equals(zone.getID())) {
+                    if (localLOGV) {
+                        Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
+                    }
+                    timeZoneWasChanged = true;
+                    SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
+                }
 
-        TimeZone.setDefault(null);
-        
-        if (timeZoneWasChanged) {
-            Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra("time-zone", zone.getID());
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                // Update the kernel timezone information
+                // Kernel tracks time offsets as 'minutes west of GMT'
+                int gmtOffset = zone.getOffset(System.currentTimeMillis());
+                setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
+            }
+
+            TimeZone.setDefault(null);
+
+            if (timeZoneWasChanged) {
+                Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                intent.putExtra("time-zone", zone.getID());
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
         }
     }
     
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 48b1215..1aad9b3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -207,7 +207,7 @@
             context = ActivityManagerService.main(factoryTest);
 
             Slog.i(TAG, "Display Manager");
-            display = new DisplayManagerService(context, uiHandler);
+            display = new DisplayManagerService(context, wmHandler, uiHandler);
             ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
 
             Slog.i(TAG, "Telephony Registry");
@@ -303,6 +303,7 @@
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
 
             ActivityManagerService.self().setWindowManager(wm);
+            display.setWindowManager(wm);
 
             // Skip Bluetooth if we have an emulator kernel
             // TODO: Use a more reliable check to see if this product should
diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java
index d19fe01..abc1d32 100644
--- a/services/java/com/android/server/display/DisplayAdapter.java
+++ b/services/java/com/android/server/display/DisplayAdapter.java
@@ -28,38 +28,53 @@
  * 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.
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
  */
-public class DisplayAdapter {
+abstract class DisplayAdapter {
+    private final DisplayManagerService.SyncRoot mSyncRoot;
     private final Context mContext;
-    private final String mName;
     private final Handler mHandler;
-    private Listener mListener;
+    private final Listener mListener;
+    private final String mName;
 
     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) {
+    public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener, String name) {
+        mSyncRoot = syncRoot;
         mContext = context;
+        mHandler = handler;
+        mListener = listener;
         mName = name;
-        mHandler = new Handler();
     }
 
+    /**
+     * Gets the object that the display adapter should synchronize on when handling
+     * calls that come in from outside of the display manager service.
+     */
+    public final DisplayManagerService.SyncRoot getSyncRoot() {
+        return mSyncRoot;
+    }
+
+    /**
+     * Gets the display adapter's context.
+     */
     public final Context getContext() {
         return mContext;
     }
 
+    /**
+     * Gets a handler that the display adapter may use to post asynchronous messages.
+     */
     public final Handler getHandler() {
         return mHandler;
     }
 
     /**
      * Gets the display adapter name for debugging purposes.
-     *
-     * @return The display adapter name.
      */
     public final String getName() {
         return mName;
@@ -68,35 +83,24 @@
     /**
      * Registers the display adapter with the display manager.
      *
-     * @param listener The listener for callbacks.  The listener will
-     * be invoked on the display manager service's handler thread.
+     * 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.
      */
-    public final void register(Listener listener) {
-        mListener = listener;
-        onRegister();
+    public void registerLocked() {
     }
 
     /**
      * 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() {
+    public void dumpLocked(PrintWriter pw) {
     }
 
     /**
      * Sends a display device event to the display adapter listener asynchronously.
      */
-    protected void sendDisplayDeviceEvent(final DisplayDevice device, final int event) {
+    protected final void sendDisplayDeviceEventLocked(
+            final DisplayDevice device, final int event) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -105,7 +109,20 @@
         });
     }
 
+    /**
+     * Sends a request to perform traversals.
+     */
+    protected final void sendTraversalRequestLocked() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onTraversalRequested();
+            }
+        });
+    }
+
     public interface Listener {
         public void onDisplayDeviceEvent(DisplayDevice device, int event);
+        public void onTraversalRequested();
     }
 }
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index c83ce96..69d8385 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -16,20 +16,35 @@
 
 package com.android.server.display;
 
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.os.IBinder;
+import android.view.Surface;
+
+import java.io.PrintWriter;
 
 /**
  * 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.
+ * Display devices are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
  */
-public abstract class DisplayDevice {
+abstract class DisplayDevice {
     private final DisplayAdapter mDisplayAdapter;
     private final IBinder mDisplayToken;
 
+    // The display device does not manage these properties itself, they are set by
+    // the display manager service.  The display device shouldn't really be looking at these.
+    private int mCurrentLayerStack = -1;
+    private int mCurrentOrientation = -1;
+    private Rect mCurrentViewport;
+    private Rect mCurrentFrame;
+
+    // The display device does own its surface texture, but it should only set it
+    // within a transaction from performTraversalInTransactionLocked.
+    private SurfaceTexture mCurrentSurfaceTexture;
+
     public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
         mDisplayAdapter = displayAdapter;
         mDisplayToken = displayToken;
@@ -40,7 +55,7 @@
      *
      * @return The display adapter.
      */
-    public final DisplayAdapter getAdapter() {
+    public final DisplayAdapter getAdapterLocked() {
         return mDisplayAdapter;
     }
 
@@ -50,22 +65,119 @@
      * @return The display token, or null if the display is not being managed
      * by Surface Flinger.
      */
-    public final IBinder getDisplayToken() {
+    public final IBinder getDisplayTokenLocked() {
         return mDisplayToken;
     }
 
     /**
+     * Gets the name of the display device.
+     *
+     * @return The display device name.
+     */
+    public final String getNameLocked() {
+        return getDisplayDeviceInfoLocked().name;
+    }
+
+    /**
      * Gets information about the display device.
      *
-     * @param outInfo The object to populate with the information.
+     * The information returned should not change between calls unless the display
+     * adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and
+     * {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply
+     * the pending changes.
+     *
+     * @return The display device info, which should be treated as immutable by the caller.
+     * The display device should allocate a new display device info object whenever
+     * the data changes.
      */
-    public abstract void getInfo(DisplayDeviceInfo outInfo);
+    public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked();
 
-    // For debugging purposes.
-    @Override
-    public String toString() {
-        DisplayDeviceInfo info = new DisplayDeviceInfo();
-        getInfo(info);
-        return info.toString() + ", owner=\"" + mDisplayAdapter.getName() + "\"";
+    /**
+     * Applies any pending changes to the observable state of the display device
+     * if the display adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event.
+     */
+    public void applyPendingDisplayDeviceInfoChangesLocked() {
+    }
+
+    /**
+     * Gives the display device a chance to update its properties while in a transaction.
+     */
+    public void performTraversalInTransactionLocked() {
+    }
+
+    /**
+     * Sets the display layer stack while in a transaction.
+     */
+    public final void setLayerStackInTransactionLocked(int layerStack) {
+        if (mCurrentLayerStack == layerStack) {
+            return;
+        }
+        mCurrentLayerStack = layerStack;
+        Surface.setDisplayLayerStack(mDisplayToken, layerStack);
+    }
+
+    /**
+     * Sets the display orientation while in a transaction.
+     */
+    public final void setOrientationInTransactionLocked(int orientation) {
+        if (mCurrentOrientation == orientation) {
+            return;
+        }
+        mCurrentOrientation = orientation;
+        Surface.setDisplayOrientation(mDisplayToken, orientation);
+    }
+
+    /**
+     * Sets the display viewport while in a transaction.
+     */
+    public final void setViewportInTransactionLocked(Rect viewport) {
+        if (mCurrentViewport != null) {
+            if (mCurrentViewport.equals(viewport)) {
+                return;
+            }
+        } else {
+            mCurrentViewport = new Rect();
+        }
+        mCurrentViewport.set(viewport);
+        Surface.setDisplayViewport(mDisplayToken, viewport);
+    }
+
+    /**
+     * Sets the display frame while in a transaction.
+     */
+    public final void setFrameInTransactionLocked(Rect frame) {
+        if (mCurrentFrame != null) {
+            if (mCurrentFrame.equals(frame)) {
+                return;
+            }
+        } else {
+            mCurrentFrame = new Rect();
+        }
+        mCurrentFrame.set(frame);
+        Surface.setDisplayFrame(mDisplayToken, frame);
+    }
+
+    /**
+     * Sets the surface texture while in a transaction.
+     */
+    public final void setSurfaceTextureInTransactionLocked(SurfaceTexture surfaceTexture) {
+        if (mCurrentSurfaceTexture == surfaceTexture) {
+            return;
+        }
+        mCurrentSurfaceTexture = surfaceTexture;
+        Surface.setDisplaySurface(mDisplayToken, surfaceTexture);
+    }
+
+    /**
+     * Dumps the local state of the display device.
+     * Does not need to dump the display device info because that is already dumped elsewhere.
+     */
+    public void dumpLocked(PrintWriter pw) {
+        pw.println("mAdapter=" + mDisplayAdapter.getName());
+        pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
+        pw.println("mCurrentOrientation=" + mCurrentOrientation);
+        pw.println("mCurrentViewport=" + mCurrentViewport);
+        pw.println("mCurrentFrame=" + mCurrentFrame);
+        pw.println("mCurrentSurfaceTexture=" + mCurrentSurfaceTexture);
     }
 }
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index c7b8c24..6f82119 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -16,14 +16,31 @@
 
 package com.android.server.display;
 
+import libcore.util.Objects;
+
 /**
  * Describes the characteristics of a physical display device.
  */
-public final class DisplayDeviceInfo {
+final class DisplayDeviceInfo {
+    /**
+     * Flag: Indicates that this display device should be considered the default display
+     * device of the system.
+     */
     public static final int FLAG_DEFAULT_DISPLAY = 1 << 0;
+
+    /**
+     * Flag: Indicates that this display device can show secure surfaces.
+     */
     public static final int FLAG_SECURE = 1 << 1;
 
     /**
+     * Flag: Indicates that this display device can rotate to show contents in a
+     * different orientation.  Otherwise the rotation is assumed to be fixed in the
+     * natural orientation and the display manager should transform the content to fit.
+     */
+    public static final int FLAG_SUPPORTS_ROTATION = 1 << 2;
+
+    /**
      * Gets the name of the display device, which may be derived from
      * EDID or other sources.  The name may be displayed to the user.
      */
@@ -48,6 +65,28 @@
 
     public int flags;
 
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof DisplayDeviceInfo && equals((DisplayDeviceInfo)o);
+    }
+
+    public boolean equals(DisplayDeviceInfo other) {
+        return other != null
+                && Objects.equal(name, other.name)
+                && width == other.width
+                && height == other.height
+                && refreshRate == other.refreshRate
+                && densityDpi == other.densityDpi
+                && xDpi == other.xDpi
+                && yDpi == other.yDpi
+                && flags == other.flags;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
     public void copyFrom(DisplayDeviceInfo other) {
         name = other.name;
         width = other.width;
@@ -62,9 +101,9 @@
     // For debugging purposes
     @Override
     public String toString() {
-        return "\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
+        return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
                 + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
-                + flagsToString(flags);
+                + flagsToString(flags) + "}";
     }
 
     private static String flagsToString(int flags) {
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index cf835ef2..706007a 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -36,11 +36,9 @@
 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;
 
 /**
@@ -66,12 +64,24 @@
  * 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>
+ *
+ * <h3>Synchronization</h3>
+ * <p>
+ * Because the display manager may be accessed by multiple threads, the synchronization
+ * story gets a little complicated.  In particular, the window manager may call into
+ * the display manager while holding a surface transaction with the expectation that
+ * it can apply changes immediately.  Unfortunately, that means we can't just do
+ * everything asynchronously (*grump*).
  * </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.
+ * To make this work, all of the objects that belong to the display manager must
+ * use the same lock.  We call this lock the synchronization root and it has a unique
+ * type {@link DisplayManagerService.SyncRoot}.  Methods that require this lock are
+ * named with the "Locked" suffix.
+ * </p><p>
+ * Where things get tricky is that the display manager is not allowed to make
+ * any potentially reentrant calls, especially into the window manager.  We generally
+ * avoid this by making all potentially reentrant out-calls asynchronous.
  * </p>
  */
 public final class DisplayManagerService extends IDisplayManager.Stub {
@@ -84,15 +94,29 @@
     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 static final int MSG_REQUEST_TRAVERSAL = 4;
 
     private final Context mContext;
     private final boolean mHeadless;
-
     private final DisplayManagerHandler mHandler;
-    private final DisplayAdapterListener mDisplayAdapterListener = new DisplayAdapterListener();
-    private final SparseArray<CallbackRecord> mCallbacks =
+    private final Handler mUiHandler;
+    private final DisplayAdapterListener mDisplayAdapterListener;
+    private WindowManagerFuncs mWindowManagerFuncs;
+
+    // The synchronization root for the display manager.
+    // This lock guards most of the display manager's state.
+    private final SyncRoot mSyncRoot = new SyncRoot();
+
+    // True if in safe mode.
+    // This option may disable certain display adapters.
+    public 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.
+    public boolean mOnlyCore;
+
+    // All callback records indexed by calling process id.
+    public final SparseArray<CallbackRecord> mCallbacks =
             new SparseArray<CallbackRecord>();
 
     // List of all currently registered display adapters.
@@ -101,26 +125,33 @@
     // 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>();
+    // List of all removed display devices.
+    private final ArrayList<DisplayDevice> mRemovedDisplayDevices = 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;
+    // Set to true when there are pending display changes that have yet to be applied
+    // to the surface flinger state.
+    private boolean mPendingTraversal;
 
     // Temporary callback list, used when sending display events to applications.
-    private ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
+    // May be used outside of the lock but only on the handler thread.
+    private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
 
-    public DisplayManagerService(Context context, Handler uiHandler) {
+    // Temporary display info, used for comparing display configurations.
+    private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
+
+    public DisplayManagerService(Context context, Handler mainHandler, Handler uiHandler) {
         mContext = context;
         mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
 
-        mHandler = new DisplayManagerHandler(uiHandler.getLooper());
+        mHandler = new DisplayManagerHandler(mainHandler.getLooper());
+        mUiHandler = uiHandler;
+        mDisplayAdapterListener = new DisplayAdapterListener();
+
         mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
     }
 
@@ -128,7 +159,7 @@
      * Pauses the boot process to wait for the first display to be initialized.
      */
     public boolean waitForDefaultDisplay() {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
             while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) {
                 long delay = timeout - SystemClock.uptimeMillis();
@@ -139,7 +170,7 @@
                     Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay);
                 }
                 try {
-                    mLock.wait(delay);
+                    mSyncRoot.wait(delay);
                 } catch (InterruptedException ex) {
                 }
             }
@@ -148,62 +179,28 @@
     }
 
     /**
+     * Called during initialization to associated the display manager with the
+     * window manager.
+     */
+    public void setWindowManager(WindowManagerFuncs windowManagerFuncs) {
+        synchronized (mSyncRoot) {
+            mWindowManagerFuncs = windowManagerFuncs;
+            scheduleTraversalLocked();
+        }
+    }
+
+    /**
      * Called when the system is ready to go.
      */
     public void systemReady(boolean safeMode, boolean onlyCore) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             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 {
-            registerDisplayAdapter(new LocalDisplayAdapter(mContext));
-        }
-    }
-
-    // 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
-        // need to redesign this
-        info.width = 1280;
-        info.height = 720;
-    }
-
     /**
      * Returns true if the device is headless.
      *
@@ -214,55 +211,42 @@
     }
 
     /**
-     * Sets the new logical display orientation.
+     * 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.  It is expected to apply the requested change
+     * to the display information synchronously so that applications will immediately
+     * observe the new state.
      *
      * @param displayId The logical display id.
-     * @param orientation One of the Surface.ROTATION_* constants.
+     * @param info The new data to be stored.
      */
-    public void setDisplayOrientation(int displayId, int orientation) {
-        synchronized (mLock) {
-            // TODO: update mirror transforms
+    public void setDisplayInfoOverrideFromWindowManager(
+            int displayId, DisplayInfo info) {
+        synchronized (mSyncRoot) {
             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();
-                    }
+            if (display != null) {
+                mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
+                display.setDisplayInfoOverrideFromWindowManagerLocked(info);
+                if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
+                    sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                    scheduleTraversalLocked();
                 }
-
-                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.
+     * Called by the window manager to perform traversals while holding a
+     * surface flinger transaction.
      */
-    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);
+    public void performTraversalInTransactionFromWindowManager() {
+        synchronized (mSyncRoot) {
+            if (!mPendingTraversal) {
+                return;
             }
+            mPendingTraversal = false;
+
+            performTraversalInTransactionLocked();
         }
     }
 
@@ -271,27 +255,28 @@
      *
      * @param displayId The logical display id.
      * @param The logical display info, or null if the display does not exist.
+     * This object must be treated as immutable.
      */
     @Override // Binder call
     public DisplayInfo getDisplayInfo(int displayId) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             LogicalDisplay display = mLogicalDisplays.get(displayId);
             if (display != null) {
-                if (display.mOverrideDisplayInfo != null) {
-                    return new DisplayInfo(display.mOverrideDisplayInfo);
-                }
-                return new DisplayInfo(display.mBaseDisplayInfo);
+                return display.getDisplayInfoLocked();
             }
             return null;
         }
     }
 
+    /**
+     * Returns the list of all display ids.
+     */
     @Override // Binder call
     public int[] getDisplayIds() {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             final int count = mLogicalDisplays.size();
             int[] displayIds = new int[count];
-            for (int i = 0; i > count; i++) {
+            for (int i = 0; i < count; i++) {
                 displayIds[i] = mLogicalDisplays.keyAt(i);
             }
             return displayIds;
@@ -304,7 +289,7 @@
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             int callingPid = Binder.getCallingPid();
             if (mCallbacks.get(callingPid) != null) {
                 throw new SecurityException("The calling process has already "
@@ -325,98 +310,234 @@
     }
 
     private void onCallbackDied(int pid) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             mCallbacks.remove(pid);
         }
     }
 
-    // Runs on handler.
+    private void registerDefaultDisplayAdapter() {
+        // Register default display adapter.
+        synchronized (mSyncRoot) {
+            if (mHeadless) {
+                registerDisplayAdapterLocked(new HeadlessDisplayAdapter(
+                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+            } else {
+                registerDisplayAdapterLocked(new LocalDisplayAdapter(
+                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+            }
+        }
+    }
+
+    private void registerAdditionalDisplayAdapters() {
+        synchronized (mSyncRoot) {
+            if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
+                registerDisplayAdapterLocked(new OverlayDisplayAdapter(
+                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+            }
+        }
+    }
+
+    private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() {
+        // 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.
+        return !mSafeMode && !mOnlyCore;
+    }
+
+    private void registerDisplayAdapterLocked(DisplayAdapter adapter) {
+        mDisplayAdapters.add(adapter);
+        adapter.registerLocked();
+    }
+
     private void handleDisplayDeviceAdded(DisplayDevice device) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             if (mDisplayDevices.contains(device)) {
-                Slog.w(TAG, "Attempted to add already added display device: " + device);
+                Slog.w(TAG, "Attempted to add already added display device: "
+                        + device.getDisplayDeviceInfoLocked());
                 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();
-            }
+            addLogicalDisplayLocked(device);
+            scheduleTraversalLocked();
         }
     }
 
-    // Runs on handler.
     private void handleDisplayDeviceChanged(DisplayDevice device) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             if (!mDisplayDevices.contains(device)) {
-                Slog.w(TAG, "Attempted to change non-existent display device: " + device);
+                Slog.w(TAG, "Attempted to change non-existent display device: "
+                        + device.getDisplayDeviceInfoLocked());
                 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);
-                }
+            device.applyPendingDisplayDeviceInfoChangesLocked();
+            if (updateLogicalDisplaysLocked()) {
+                scheduleTraversalLocked();
             }
         }
     }
 
-    // Runs on handler.
     private void handleDisplayDeviceRemoved(DisplayDevice device) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             if (!mDisplayDevices.remove(device)) {
-                Slog.w(TAG, "Attempted to remove non-existent display device: " + device);
+                Slog.w(TAG, "Attempted to remove non-existent display device: "
+                        + device.getDisplayDeviceInfoLocked());
                 return;
             }
 
-            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);
-                }
-            }
+            mRemovedDisplayDevices.add(device);
+            updateLogicalDisplaysLocked();
+            scheduleTraversalLocked();
         }
     }
 
-    // Posts a message to send a display event at the next opportunity.
+    // Adds a new logical display based on the given display device.
+    // Sends notifications if needed.
+    private void addLogicalDisplayLocked(DisplayDevice device) {
+        DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
+        boolean isDefault = (deviceInfo.flags
+                & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+        if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
+            Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
+            isDefault = false;
+        }
+
+        final int displayId = assignDisplayIdLocked(isDefault);
+        final int layerStack = assignLayerStackLocked(displayId);
+
+        LogicalDisplay display = new LogicalDisplay(layerStack, device);
+        display.updateLocked(mDisplayDevices);
+        if (!display.isValidLocked()) {
+            // This should never happen currently.
+            Slog.w(TAG, "Ignoring display device because the logical display "
+                    + "created from it was not considered valid: " + deviceInfo);
+            return;
+        }
+
+        mLogicalDisplays.put(displayId, display);
+
+        // Wake up waitForDefaultDisplay.
+        if (isDefault) {
+            mSyncRoot.notifyAll();
+        }
+
+        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+    }
+
+    private int assignDisplayIdLocked(boolean isDefault) {
+        return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
+    }
+
+    private int assignLayerStackLocked(int displayId) {
+        // Currently layer stacks and display ids are the same.
+        // This need not be the case.
+        return displayId;
+    }
+
+    // Updates all existing logical displays given the current set of display devices.
+    // Removes invalid logical displays.
+    // Sends notifications if needed.
+    private boolean updateLogicalDisplaysLocked() {
+        boolean changed = false;
+        for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+            final int displayId = mLogicalDisplays.keyAt(i);
+            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+
+            mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
+            display.updateLocked(mDisplayDevices);
+            if (!display.isValidLocked()) {
+                mLogicalDisplays.removeAt(i);
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+                changed = true;
+            } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    private void performTraversalInTransactionLocked() {
+        // Perform one last traversal for each removed display device.
+        final int removedCount = mRemovedDisplayDevices.size();
+        for (int i = 0; i < removedCount; i++) {
+            DisplayDevice device = mRemovedDisplayDevices.get(i);
+            device.performTraversalInTransactionLocked();
+        }
+        mRemovedDisplayDevices.clear();
+
+        // Configure each display device.
+        final int count = mDisplayDevices.size();
+        for (int i = 0; i < count; i++) {
+            DisplayDevice device = mDisplayDevices.get(i);
+            configureDisplayInTransactionLocked(device);
+            device.performTraversalInTransactionLocked();
+        }
+    }
+
+    private void configureDisplayInTransactionLocked(DisplayDevice device) {
+        // TODO: add a proper per-display mirroring control
+        boolean isMirroring = SystemProperties.getBoolean("debug.display.mirror", true);
+
+        // Find the logical display that the display device is showing.
+        LogicalDisplay display = null;
+        if (!isMirroring) {
+            display = findLogicalDisplayForDeviceLocked(device);
+        }
+        if (display == null) {
+            display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+        }
+
+        // Apply the logical display configuration to the display device.
+        if (display == null) {
+            // TODO: no logical display for the device, blank it
+            Slog.d(TAG, "Missing logical display to use for physical display device: "
+                    + device.getDisplayDeviceInfoLocked());
+        } else {
+            display.configureDisplayInTransactionLocked(device);
+        }
+    }
+
+    private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) {
+        final int count = mLogicalDisplays.size();
+        for (int i = 0; i < count; i++) {
+            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            if (display.getPrimaryDisplayDeviceLocked() == device) {
+                return display;
+            }
+        }
+        return null;
+    }
+
     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.
+    // Requests that performTraversalsInTransactionFromWindowManager be called at a
+    // later time to apply changes to surfaces and displays.
+    private void scheduleTraversalLocked() {
+        if (!mPendingTraversal && mWindowManagerFuncs != null) {
+            mPendingTraversal = true;
+            mHandler.sendEmptyMessage(MSG_REQUEST_TRAVERSAL);
+        }
+    }
+
+    // Runs on Handler thread.
+    // Delivers display event notifications to callbacks.
     private void deliverDisplayEvent(int displayId, int event) {
         if (DEBUG) {
-            Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event);
+            Slog.d(TAG, "Delivering display event: displayId="
+                    + displayId + ", event=" + event);
         }
 
+        // Grab the lock and copy the callbacks.
         final int count;
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             count = mCallbacks.size();
             mTempCallbacks.clear();
             for (int i = 0; i < count; i++) {
@@ -424,6 +545,7 @@
             }
         }
 
+        // After releasing the lock, send the notifications out.
         for (int i = 0; i < count; i++) {
             mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
         }
@@ -443,30 +565,22 @@
         pw.println("DISPLAY MANAGER (dumpsys display)");
         pw.println("  mHeadless=" + mHeadless);
 
-        mHandler.runWithScissors(new Runnable() {
-            @Override
-            public void run() {
-                dumpLocal(pw);
-            }
-        });
-    }
-
-    // Runs on handler.
-    private void dumpLocal(PrintWriter pw) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+            ipw.increaseIndent();
 
             pw.println();
             pw.println("Display Adapters: size=" + mDisplayAdapters.size());
             for (DisplayAdapter adapter : mDisplayAdapters) {
                 pw.println("  " + adapter.getName());
-                adapter.dump(ipw);
+                adapter.dumpLocked(ipw);
             }
 
             pw.println();
             pw.println("Display Devices: size=" + mDisplayDevices.size());
             for (DisplayDevice device : mDisplayDevices) {
-                pw.println("  " + device);
+                pw.println("  " + device.getDisplayDeviceInfoLocked());
+                device.dumpLocked(ipw);
             }
 
             final int logicalDisplayCount = mLogicalDisplays.size();
@@ -476,14 +590,31 @@
                 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);
+                display.dumpLocked(ipw);
             }
         }
     }
 
+    /**
+     * This is the object that everything in the display manager locks on.
+     * We make it an inner class within the {@link DisplayManagerService} to so that it is
+     * clear that the object belongs to the display manager service and that it is
+     * a unique object with a special purpose.
+     */
+    public static final class SyncRoot {
+    }
+
+    /**
+     * Private interface to the window manager.
+     */
+    public interface WindowManagerFuncs {
+        /**
+         * Request that the window manager call {@link #performTraversalInTransaction}
+         * within a surface transaction at a later time.
+         */
+        void requestTraversal();
+    }
+
     private final class DisplayManagerHandler extends Handler {
         public DisplayManagerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -503,6 +634,10 @@
                 case MSG_DELIVER_DISPLAY_EVENT:
                     deliverDisplayEvent(msg.arg1, msg.arg2);
                     break;
+
+                case MSG_REQUEST_TRAVERSAL:
+                    mWindowManagerFuncs.requestTraversal();
+                    break;
             }
         }
     }
@@ -524,6 +659,13 @@
                     break;
             }
         }
+
+        @Override
+        public void onTraversalRequested() {
+            synchronized (mSyncRoot) {
+                scheduleTraversalLocked();
+            }
+        }
     }
 
     private final class CallbackRecord implements DeathRecipient {
@@ -553,46 +695,4 @@
             }
         }
     }
-
-    /**
-     * 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 f5c78b9..3aaacf1 100644
--- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java
+++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
@@ -17,44 +17,52 @@
 package com.android.server.display;
 
 import android.content.Context;
+import android.os.Handler;
 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.
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
  */
-public final class HeadlessDisplayAdapter extends DisplayAdapter {
+final class HeadlessDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "HeadlessDisplayAdapter";
 
-    public HeadlessDisplayAdapter(Context context) {
-        super(context, TAG);
+    public HeadlessDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener) {
+        super(syncRoot, context, handler, listener, TAG);
     }
 
     @Override
-    protected void onRegister() {
-        sendDisplayDeviceEvent(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED);
+    public void registerLocked() {
+        super.registerLocked();
+        sendDisplayDeviceEventLocked(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED);
     }
 
     private final class HeadlessDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mInfo;
+
         public HeadlessDisplayDevice() {
             super(HeadlessDisplayAdapter.this, null);
         }
 
         @Override
-        public void getInfo(DisplayDeviceInfo outInfo) {
-            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;
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = getContext().getResources().getString(
+                        com.android.internal.R.string.display_manager_built_in_display_name);
+                mInfo.width = 640;
+                mInfo.height = 480;
+                mInfo.refreshRate = 60;
+                mInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+                mInfo.xDpi = 160;
+                mInfo.yDpi = 160;
+                mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+                        | DisplayDeviceInfo.FLAG_SECURE;
+            }
+            return mInfo;
         }
     }
 }
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index 73544fc..4a8829a 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -17,58 +17,132 @@
 package com.android.server.display;
 
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
+import android.util.SparseArray;
 import android.view.Surface;
 import android.view.Surface.PhysicalDisplayInfo;
 
+import java.io.PrintWriter;
+
 /**
  * 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.
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
  */
-public final class LocalDisplayAdapter extends DisplayAdapter {
+final class LocalDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "LocalDisplayAdapter";
 
-    public LocalDisplayAdapter(Context context) {
-        super(context, TAG);
+    private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
+            Surface.BUILT_IN_DISPLAY_ID_MAIN,
+            Surface.BUILT_IN_DISPLAY_ID_HDMI,
+    };
+
+    private final SparseArray<LocalDisplayDevice> mDevices =
+            new SparseArray<LocalDisplayDevice>();
+
+    private final PhysicalDisplayInfo mTempPhys = new PhysicalDisplayInfo();
+
+    public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener) {
+        super(syncRoot, context, handler, listener, TAG);
     }
 
     @Override
-    protected void onRegister() {
+    public void registerLocked() {
         // 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);
+        super.registerLocked();
+        scanDisplaysLocked();
+    }
+
+    private void scanDisplaysLocked() {
+        for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
+            IBinder displayToken = Surface.getBuiltInDisplay(builtInDisplayId);
+            if (displayToken != null && Surface.getDisplayInfo(displayToken, mTempPhys)) {
+                LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+                if (device == null) {
+                    // Display was added.
+                    device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys);
+                    mDevices.put(builtInDisplayId, device);
+                    sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
+                } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) {
+                    // Display properties changed.
+                    sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
+                }
+            } else {
+                LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+                if (device != null) {
+                    // Display was removed.
+                    mDevices.remove(builtInDisplayId);
+                    sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+                }
+            }
+        }
     }
 
     private final class LocalDisplayDevice extends DisplayDevice {
-        private final boolean mIsDefault;
+        private final int mBuiltInDisplayId;
+        private final PhysicalDisplayInfo mPhys;
 
-        public LocalDisplayDevice(IBinder displayToken, boolean isDefault) {
+        private DisplayDeviceInfo mInfo;
+        private boolean mHavePendingChanges;
+
+        public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
+                PhysicalDisplayInfo phys) {
             super(LocalDisplayAdapter.this, displayToken);
-            mIsDefault = isDefault;
+            mBuiltInDisplayId = builtInDisplayId;
+            mPhys = new PhysicalDisplayInfo(phys);
+        }
+
+        public boolean updatePhysicalDisplayInfoLocked(PhysicalDisplayInfo phys) {
+            if (!mPhys.equals(phys)) {
+                mPhys.copyFrom(phys);
+                mHavePendingChanges = true;
+                return true;
+            }
+            return false;
         }
 
         @Override
-        public void getInfo(DisplayDeviceInfo outInfo) {
-            PhysicalDisplayInfo phys = new PhysicalDisplayInfo();
-            Surface.getDisplayInfo(getDisplayToken(), phys);
-
-            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;
+        public void applyPendingDisplayDeviceInfoChangesLocked() {
+            if (mHavePendingChanges) {
+                mInfo = null;
+                mHavePendingChanges = false;
             }
         }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.width = mPhys.width;
+                mInfo.height = mPhys.height;
+                mInfo.refreshRate = mPhys.refreshRate;
+                mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
+                mInfo.xDpi = mPhys.xDpi;
+                mInfo.yDpi = mPhys.yDpi;
+                if (mBuiltInDisplayId == Surface.BUILT_IN_DISPLAY_ID_MAIN) {
+                    mInfo.name = getContext().getResources().getString(
+                            com.android.internal.R.string.display_manager_built_in_display_name);
+                    mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+                            | DisplayDeviceInfo.FLAG_SECURE
+                            | DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION;
+                } else {
+                    mInfo.name = getContext().getResources().getString(
+                            com.android.internal.R.string.display_manager_hdmi_display_name);
+                    mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+                }
+            }
+            return mInfo;
+        }
+
+        @Override
+        public void dumpLocked(PrintWriter pw) {
+            super.dumpLocked(pw);
+            pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
+            pw.println("mPhys=" + mPhys);
+        }
     }
 }
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
new file mode 100644
index 0000000..32ca1c5
--- /dev/null
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -0,0 +1,265 @@
+/*
+ * 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.graphics.Rect;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import libcore.util.Objects;
+
+/**
+ * Describes how a logical display is configured.
+ * <p>
+ * At this time, we only support logical displays that are coupled to a particular
+ * primary display device from which the logical display derives its basic properties
+ * such as its size, density and refresh rate.
+ * </p><p>
+ * A logical display may be mirrored onto multiple display devices in addition to its
+ * primary display device.  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 primary display device is currently mirroring content from a different
+ * logical display.
+ * </p><p>
+ * This object is designed to encapsulate as much of the policy of logical
+ * displays as possible.  The idea is to make it easy to implement new kinds of
+ * logical displays mostly by making local changes to this class.
+ * </p><p>
+ * Note: The display manager architecture does not actually require logical displays
+ * to be associated with any individual display device.  Logical displays and
+ * display devices are orthogonal concepts.  Some mapping will exist between
+ * logical displays and display devices but it can be many-to-many and
+ * and some might have no relation at all.
+ * </p><p>
+ * Logical displays are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class LogicalDisplay {
+    private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
+
+    private final int mLayerStack;
+    private DisplayInfo mOverrideDisplayInfo; // set by the window manager
+    private DisplayInfo mInfo;
+
+    // The display device that this logical display is based on and which
+    // determines the base metrics that it uses.
+    private DisplayDevice mPrimaryDisplayDevice;
+    private DisplayDeviceInfo mPrimaryDisplayDeviceInfo;
+
+    // Temporary rectangle used when needed.
+    private final Rect mTempRect = new Rect();
+
+    public LogicalDisplay(int layerStack, DisplayDevice primaryDisplayDevice) {
+        mLayerStack = layerStack;
+        mPrimaryDisplayDevice = primaryDisplayDevice;
+    }
+
+    /**
+     * Gets the primary display device associated with this logical display.
+     *
+     * @return The primary display device.
+     */
+    public DisplayDevice getPrimaryDisplayDeviceLocked() {
+        return mPrimaryDisplayDevice;
+    }
+
+    /**
+     * Gets information about the logical display.
+     *
+     * @return The device info, which should be treated as immutable by the caller.
+     * The logical display should allocate a new display info object whenever
+     * the data changes.
+     */
+    public DisplayInfo getDisplayInfoLocked() {
+        if (mInfo == null) {
+            mInfo = new DisplayInfo();
+            if (mOverrideDisplayInfo != null) {
+                mInfo.copyFrom(mOverrideDisplayInfo);
+                mInfo.layerStack = mBaseDisplayInfo.layerStack;
+                mInfo.name = mBaseDisplayInfo.name;
+            } else {
+                mInfo.copyFrom(mBaseDisplayInfo);
+            }
+        }
+        return mInfo;
+    }
+
+    /**
+     * Sets overridden logical display information from the window manager.
+     * This method can be used to adjust application insets, rotation, and other
+     * properties that the window manager takes care of.
+     *
+     * @param info The logical display information, may be null.
+     */
+    public void setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
+        if (info != null) {
+            if (mOverrideDisplayInfo == null) {
+                mOverrideDisplayInfo = new DisplayInfo(info);
+                mInfo = null;
+            } else if (!mOverrideDisplayInfo.equals(info)) {
+                mOverrideDisplayInfo.copyFrom(info);
+                mInfo = null;
+            }
+        } else if (mOverrideDisplayInfo != null) {
+            mOverrideDisplayInfo = null;
+            mInfo = null;
+        }
+    }
+
+    /**
+     * Returns true if the logical display is in a valid state.
+     * This method should be checked after calling {@link #update} to handle the
+     * case where a logical display should be removed because all of its associated
+     * display devices are gone or if it is otherwise no longer needed.
+     *
+     * @return True if the logical display is still valid.
+     */
+    public boolean isValidLocked() {
+        return mPrimaryDisplayDevice != null;
+    }
+
+    /**
+     * Updates the state of the logical display based on the available display devices.
+     * The logical display might become invalid if it is attached to a display device
+     * that no longer exists.
+     *
+     * @param devices The list of all connected display devices.
+     */
+    public void updateLocked(List<DisplayDevice> devices) {
+        // Nothing to update if already invalid.
+        if (mPrimaryDisplayDevice == null) {
+            return;
+        }
+
+        // Check whether logical display has become invalid.
+        if (!devices.contains(mPrimaryDisplayDevice)) {
+            mPrimaryDisplayDevice = null;
+            return;
+        }
+
+        // Bootstrap the logical display using its associated primary physical display.
+        // We might use more elaborate configurations later.  It's possible that the
+        // configuration of several physical displays might be used to determine the
+        // logical display that they are sharing.  (eg. Adjust size for pixel-perfect
+        // mirroring over HDMI.)
+        DisplayDeviceInfo deviceInfo = mPrimaryDisplayDevice.getDisplayDeviceInfoLocked();
+        if (!Objects.equal(mPrimaryDisplayDeviceInfo, deviceInfo)) {
+            mBaseDisplayInfo.layerStack = mLayerStack;
+            mBaseDisplayInfo.name = deviceInfo.name;
+            mBaseDisplayInfo.appWidth = deviceInfo.width;
+            mBaseDisplayInfo.appHeight = deviceInfo.height;
+            mBaseDisplayInfo.logicalWidth = deviceInfo.width;
+            mBaseDisplayInfo.logicalHeight = deviceInfo.height;
+            mBaseDisplayInfo.rotation = Surface.ROTATION_0;
+            mBaseDisplayInfo.refreshRate = deviceInfo.refreshRate;
+            mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
+            mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
+            mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
+            mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width;
+            mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height;
+            mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width;
+            mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height;
+
+            mPrimaryDisplayDeviceInfo = deviceInfo;
+            mInfo = null;
+        }
+    }
+
+    /**
+     * Applies the layer stack and transformation to the given display device
+     * so that it shows the contents of this logical display.
+     *
+     * We know that the given display device is only ever showing the contents of
+     * a single logical display, so this method is expected to blow away all of its
+     * transformation properties to make it happen regardless of what the
+     * display device was previously showing.
+     *
+     * The caller must have an open Surface transaction.
+     *
+     * The display device may not be the primary display device, in the case
+     * where the display is being mirrored.
+     *
+     * @param device The display device to modify.
+     */
+    public void configureDisplayInTransactionLocked(DisplayDevice device) {
+        final DisplayInfo displayInfo = getDisplayInfoLocked();
+        final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();
+
+        // Set the layer stack.
+        device.setLayerStackInTransactionLocked(mLayerStack);
+
+        // Set the viewport.
+        // This is the area of the logical display that we intend to show on the
+        // display device.  For now, it is always the full size of the logical display.
+        mTempRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+        device.setViewportInTransactionLocked(mTempRect);
+
+        // Set the orientation.
+        // The orientation specifies how the physical coordinate system of the display
+        // is rotated when the contents of the logical display are rendered.
+        int orientation = Surface.ROTATION_0;
+        if (device == mPrimaryDisplayDevice
+                && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION) != 0) {
+            orientation = displayInfo.rotation;
+        }
+        device.setOrientationInTransactionLocked(orientation);
+
+        // Set the frame.
+        // The frame specifies the rotated physical coordinates into which the viewport
+        // is mapped.  We need to take care to preserve the aspect ratio of the viewport.
+        // Currently we maximize the area to fill the display, but we could try to be
+        // more clever and match resolutions.
+        boolean rotated = (orientation == Surface.ROTATION_90
+                || orientation == Surface.ROTATION_270);
+        int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
+        int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
+
+        // Determine whether the width or height is more constrained to be scaled.
+        //    physWidth / displayInfo.logicalWidth    => letter box
+        // or physHeight / displayInfo.logicalHeight  => pillar box
+        //
+        // We avoid a division (and possible floating point imprecision) here by
+        // multiplying the fractions by the product of their denominators before
+        // comparing them.
+        int frameWidth, frameHeight;
+        if (physWidth * displayInfo.logicalHeight
+                < physHeight * displayInfo.logicalWidth) {
+            // Letter box.
+            frameWidth = physWidth;
+            frameHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+        } else {
+            // Pillar box.
+            frameWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+            frameHeight = physHeight;
+        }
+        int frameTop = (physHeight - frameHeight) / 2;
+        int frameLeft = (physWidth - frameWidth) / 2;
+        mTempRect.set(frameLeft, frameTop, frameLeft + frameWidth, frameTop + frameHeight);
+        device.setFrameInTransactionLocked(mTempRect);
+    }
+
+    public void dumpLocked(PrintWriter pw) {
+        pw.println("mLayerStack=" + mLayerStack);
+        pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
+                mPrimaryDisplayDevice.getNameLocked() : "null"));
+        pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
+        pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
+    }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
index b52ccf2..ea7e88de 100644
--- a/services/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -19,25 +19,16 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.graphics.SurfaceTexture;
-import android.hardware.display.DisplayManager;
+import android.os.Handler;
 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.io.StringWriter;
 import java.util.ArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -47,13 +38,16 @@
  * 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.
+ * This object has two different handlers (which may be the same) which must not
+ * get confused.  The main handler is used to posting messages to the display manager
+ * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
  * </p>
  */
-public final class OverlayDisplayAdapter extends DisplayAdapter {
-    private static final String TAG = "OverlayDisplayAdapter";
-    private static final boolean DEBUG = false;
+final class OverlayDisplayAdapter extends DisplayAdapter {
+    static final String TAG = "OverlayDisplayAdapter";
+    static final boolean DEBUG = false;
 
     private static final int MIN_WIDTH = 100;
     private static final int MIN_HEIGHT = 100;
@@ -63,36 +57,44 @@
     private static final Pattern SETTING_PATTERN =
             Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
 
-    private final ArrayList<Overlay> mOverlays = new ArrayList<Overlay>();
+    private final Handler mUiHandler;
+    private final ArrayList<OverlayDisplayHandle> mOverlays =
+            new ArrayList<OverlayDisplayHandle>();
     private String mCurrentOverlaySetting = "";
 
-    public OverlayDisplayAdapter(Context context) {
-        super(context, TAG);
+    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener, Handler uiHandler) {
+        super(syncRoot, context, handler, listener, TAG);
+        mUiHandler = uiHandler;
     }
 
     @Override
-    public void dump(PrintWriter pw) {
+    public void dumpLocked(PrintWriter pw) {
+        super.dumpLocked(pw);
         pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
         pw.println("mOverlays: size=" + mOverlays.size());
-        for (Overlay overlay : mOverlays) {
-            overlay.dump(pw);
+        for (OverlayDisplayHandle overlay : mOverlays) {
+            overlay.dumpLocked(pw);
         }
     }
 
     @Override
-    protected void onRegister() {
+    public void registerLocked() {
+        super.registerLocked();
         getContext().getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
                 new ContentObserver(getHandler()) {
                     @Override
                     public void onChange(boolean selfChange) {
-                        updateOverlayDisplayDevices();
+                        synchronized (getSyncRoot()) {
+                            updateOverlayDisplayDevicesLocked();
+                        }
                     }
                 });
-        updateOverlayDisplayDevices();
+        updateOverlayDisplayDevicesLocked();
     }
 
-    private void updateOverlayDisplayDevices() {
+    private void updateOverlayDisplayDevicesLocked() {
         String value = Settings.System.getString(getContext().getContentResolver(),
                 Settings.Secure.OVERLAY_DISPLAY_DEVICES);
         if (value == null) {
@@ -106,19 +108,20 @@
 
         if (!mOverlays.isEmpty()) {
             Slog.i(TAG, "Dismissing all overlay display devices.");
-            for (Overlay overlay : mOverlays) {
-                overlay.dismiss();
+            for (OverlayDisplayHandle overlay : mOverlays) {
+                overlay.dismissLocked();
             }
             mOverlays.clear();
         }
 
-        int number = 1;
+        int count = 0;
         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()) {
+                if (count >= 4) {
+                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
+                    break;
+                }
                 try {
                     int width = Integer.parseInt(matcher.group(1), 10);
                     int height = Integer.parseInt(matcher.group(2), 10);
@@ -127,10 +130,18 @@
                             && height >= MIN_HEIGHT && height <= MAX_HEIGHT
                             && densityDpi >= DisplayMetrics.DENSITY_LOW
                             && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
+                        int number = ++count;
+                        String name = getContext().getResources().getString(
+                                com.android.internal.R.string.display_manager_overlay_display_name,
+                                number);
+                        int gravity = chooseOverlayGravity(number);
+
                         Slog.i(TAG, "Showing overlay display device #" + number
-                                + ": width=" + width + ", height=" + height
+                                + ": name=" + name + ", width=" + width + ", height=" + height
                                 + ", densityDpi=" + densityDpi);
-                        mOverlays.add(new Overlay(number++, width, height, densityDpi));
+
+                        mOverlays.add(new OverlayDisplayHandle(name,
+                                width, height, densityDpi, gravity));
                         continue;
                     }
                 } catch (NumberFormatException ex) {
@@ -138,401 +149,195 @@
             } else if (part.isEmpty()) {
                 continue;
             }
-            Slog.w(TAG, "Malformed overlay display devices setting: \"" + value + "\"");
-        }
-
-        for (Overlay overlay : mOverlays) {
-            overlay.show();
+            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
         }
     }
 
-    // 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();
+    private static int chooseOverlayGravity(int overlayNumber) {
+        switch (overlayNumber) {
+            case 1:
+                return Gravity.TOP | Gravity.LEFT;
+            case 2:
+                return Gravity.BOTTOM | Gravity.RIGHT;
+            case 3:
+                return Gravity.TOP | Gravity.RIGHT;
+            case 4:
+            default:
+                return Gravity.BOTTOM | Gravity.LEFT;
         }
-
-        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);
-            if (mTextureView != null) {
-                pw.println("    mTextureView.getScaleX()=" + mTextureView.getScaleX());
-                pw.println("    mTextureView.getScaleY()=" + mTextureView.getScaleY());
-            }
-            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;
-            scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
-            scale = Math.min(scale, (float)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));
-
-            if (DEBUG) {
-                Slog.d(TAG, "updateWindowParams: scale=" + scale
-                        + ", offsetScale=" + offsetScale
-                        + ", x=" + x + ", y=" + y
-                        + ", width=" + width + ", height=" + 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 int mWidth;
+        private final int mHeight;
         private final float mRefreshRate;
         private final int mDensityDpi;
-        private int mWidth;
-        private int mHeight;
+
+        private SurfaceTexture mSurfaceTexture;
+        private boolean mSurfaceTextureChanged;
+
+        private DisplayDeviceInfo mInfo;
 
         public OverlayDisplayDevice(IBinder displayToken, String name,
-                float refreshRate, int densityDpi) {
+                int width, int height, float refreshRate, int densityDpi) {
             super(OverlayDisplayAdapter.this, displayToken);
             mName = name;
+            mWidth = width;
+            mHeight = height;
             mRefreshRate = refreshRate;
             mDensityDpi = densityDpi;
         }
 
-        public void setSize(int width, int height) {
-            mWidth = width;
-            mHeight = height;
+        public void setSurfaceTextureLocked(SurfaceTexture surfaceTexture) {
+            if (surfaceTexture != mSurfaceTexture) {
+                mSurfaceTexture = surfaceTexture;
+                mSurfaceTextureChanged = true;
+                sendTraversalRequestLocked();
+            }
         }
 
         @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;
+        public void performTraversalInTransactionLocked() {
+            if (mSurfaceTextureChanged) {
+                setSurfaceTextureInTransactionLocked(mSurfaceTexture);
+                mSurfaceTextureChanged = false;
+            }
         }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = mName;
+                mInfo.width = mWidth;
+                mInfo.height = mHeight;
+                mInfo.refreshRate = mRefreshRate;
+                mInfo.densityDpi = mDensityDpi;
+                mInfo.xDpi = mDensityDpi;
+                mInfo.yDpi = mDensityDpi;
+                mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+            }
+            return mInfo;
+        }
+    }
+
+    /**
+     * Functions as a handle for overlay display devices which are created and
+     * destroyed asynchronously.
+     *
+     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
+     */
+    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final int mDensityDpi;
+        private final int mGravity;
+
+        private OverlayDisplayWindow mWindow;
+        private OverlayDisplayDevice mDevice;
+
+        public OverlayDisplayHandle(String name,
+                int width, int height, int densityDpi, int gravity) {
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mDensityDpi = densityDpi;
+            mGravity = gravity;
+
+            mUiHandler.post(mShowRunnable);
+        }
+
+        public void dismissLocked() {
+            mUiHandler.removeCallbacks(mShowRunnable);
+            mUiHandler.post(mDismissRunnable);
+        }
+
+        // Called on the UI thread.
+        @Override
+        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
+            synchronized (getSyncRoot()) {
+                IBinder displayToken = Surface.createDisplay(mName);
+                mDevice = new OverlayDisplayDevice(displayToken, mName,
+                        mWidth, mHeight, refreshRate, mDensityDpi);
+                mDevice.setSurfaceTextureLocked(surfaceTexture);
+
+                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
+            }
+        }
+
+        // Called on the UI thread.
+        @Override
+        public void onWindowDestroyed() {
+            synchronized (getSyncRoot()) {
+                if (mDevice != null) {
+                    mDevice.setSurfaceTextureLocked(null);
+
+                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+                }
+            }
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.println("  " + mName + ": ");
+            pw.println("    mWidth=" + mWidth);
+            pw.println("    mHeight=" + mHeight);
+            pw.println("    mDensityDpi=" + mDensityDpi);
+            pw.println("    mGravity=" + mGravity);
+
+            // Try to dump the window state.
+            // This call may hang if the UI thread is waiting to acquire our lock so
+            // we use a short timeout to recover just in case.
+            if (mWindow != null) {
+                final StringWriter sw = new StringWriter();
+                final OverlayDisplayWindow window = mWindow;
+                if (mUiHandler.runWithScissors(new Runnable() {
+                    @Override
+                    public void run() {
+                        PrintWriter lpw = new PrintWriter(sw);
+                        window.dump(lpw);
+                        lpw.close();
+                    }
+                }, 200)) {
+                    for (String line : sw.toString().split("\n")) {
+                        pw.println(line);
+                    }
+                } else {
+                    pw.println("    ... timed out while attempting to dump window state");
+                }
+            }
+        }
+
+        // Runs on the UI thread.
+        private final Runnable mShowRunnable = new Runnable() {
+            @Override
+            public void run() {
+                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
+                        mName, mWidth, mHeight, mDensityDpi, mGravity,
+                        OverlayDisplayHandle.this);
+                window.show();
+
+                synchronized (getSyncRoot()) {
+                    mWindow = window;
+                }
+            }
+        };
+
+        // Runs on the UI thread.
+        private final Runnable mDismissRunnable = new Runnable() {
+            @Override
+            public void run() {
+                OverlayDisplayWindow window;
+                synchronized (getSyncRoot()) {
+                    window = mWindow;
+                    mWindow = null;
+                }
+
+                if (window != null) {
+                    window.dismiss();
+                }
+            }
+        };
     }
 }
diff --git a/services/java/com/android/server/display/OverlayDisplayWindow.java b/services/java/com/android/server/display/OverlayDisplayWindow.java
new file mode 100644
index 0000000..6adfa0f
--- /dev/null
+++ b/services/java/com/android/server/display/OverlayDisplayWindow.java
@@ -0,0 +1,361 @@
+/*
+ * 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.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.TextureView.SurfaceTextureListener;
+import android.widget.TextView;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}.
+ * <p>
+ * This object must only be accessed on the UI thread.
+ * No locks are held by this object and locks must not be held while making called into it.
+ * </p>
+ */
+final class OverlayDisplayWindow {
+    private static final String TAG = "OverlayDisplayWindow";
+    private static final boolean DEBUG = false;
+
+    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 Context mContext;
+    private final String mName;
+    private final int mWidth;
+    private final int mHeight;
+    private final int mDensityDpi;
+    private final int mGravity;
+    private final Listener mListener;
+    private final String mTitle;
+
+    private final DisplayManager mDisplayManager;
+    private final WindowManager mWindowManager;
+
+
+    private final Display mDefaultDisplay;
+    private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+
+    private View mWindowContent;
+    private WindowManager.LayoutParams mWindowParams;
+    private TextureView mTextureView;
+    private TextView mTitleTextView;
+
+    private GestureDetector mGestureDetector;
+    private ScaleGestureDetector mScaleGestureDetector;
+
+    private boolean mWindowVisible;
+    private int mWindowX;
+    private int mWindowY;
+    private float mWindowScale;
+
+    private float mLiveTranslationX;
+    private float mLiveTranslationY;
+    private float mLiveScale = 1.0f;
+
+    public OverlayDisplayWindow(Context context, String name,
+            int width, int height, int densityDpi, int gravity, Listener listener) {
+        mContext = context;
+        mName = name;
+        mWidth = width;
+        mHeight = height;
+        mDensityDpi = densityDpi;
+        mGravity = gravity;
+        mListener = listener;
+        mTitle = context.getResources().getString(
+                com.android.internal.R.string.display_manager_overlay_display_title,
+                mName, mWidth, mHeight, mDensityDpi);
+
+        mDisplayManager = (DisplayManager)context.getSystemService(
+                Context.DISPLAY_SERVICE);
+        mWindowManager = (WindowManager)context.getSystemService(
+                Context.WINDOW_SERVICE);
+
+        mDefaultDisplay = mWindowManager.getDefaultDisplay();
+        updateDefaultDisplayInfo();
+
+        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("    mWindowVisible=" + mWindowVisible);
+        pw.println("    mWindowX=" + mWindowX);
+        pw.println("    mWindowY=" + mWindowY);
+        pw.println("    mWindowScale=" + mWindowScale);
+        pw.println("    mWindowParams=" + mWindowParams);
+        if (mTextureView != null) {
+            pw.println("    mTextureView.getScaleX()=" + mTextureView.getScaleX());
+            pw.println("    mTextureView.getScaleY()=" + mTextureView.getScaleY());
+        }
+        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() {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        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.setOpaque(false);
+        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);
+
+        mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
+        mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
+
+        // Set the initial position and scale.
+        // The position and scale will be clamped when the display is first shown.
+        mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
+                0 : mDefaultDisplayInfo.logicalWidth;
+        mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
+                0 : mDefaultDisplayInfo.logicalHeight;
+        mWindowScale = INITIAL_SCALE;
+    }
+
+    private void updateWindowParams() {
+        float scale = mWindowScale * mLiveScale;
+        scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
+        scale = Math.min(scale, (float)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 = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
+        int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
+        x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
+        y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
+
+        if (DEBUG) {
+            Slog.d(TAG, "updateWindowParams: scale=" + scale
+                    + ", offsetScale=" + offsetScale
+                    + ", x=" + x + ", y=" + y
+                    + ", width=" + width + ", height=" + 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 = 0f;
+        mLiveTranslationY = 0f;
+        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) {
+            mListener.onWindowCreated(surface, mDefaultDisplayInfo.refreshRate);
+        }
+
+        @Override
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+            mListener.onWindowDestroyed();
+            return true;
+        }
+
+        @Override
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        }
+
+        @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());
+
+            mGestureDetector.onTouchEvent(event);
+            mScaleGestureDetector.onTouchEvent(event);
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    saveWindowParams();
+                    break;
+            }
+
+            // Revert to window coordinates.
+            event.setLocation(oldX, oldY);
+            return true;
+        }
+    };
+
+    private final GestureDetector.OnGestureListener mOnGestureListener =
+            new GestureDetector.SimpleOnGestureListener() {
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            mLiveTranslationX -= distanceX;
+            mLiveTranslationY -= distanceY;
+            relayout();
+            return true;
+        }
+    };
+
+    private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+            new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            mLiveScale *= detector.getScaleFactor();
+            relayout();
+            return true;
+        }
+    };
+
+    /**
+     * Watches for significant changes in the overlay display window lifecycle.
+     */
+    public interface Listener {
+        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
+        public void onWindowDestroyed();
+    }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 29c68eb..fd4f5fc 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -282,29 +282,28 @@
         nativeReloadDeviceAliases(mPtr);
     }
 
-    public void setDisplaySize(int displayId, int width, int height,
-            int externalWidth, int externalHeight) {
-        if (width <= 0 || height <= 0 || externalWidth <= 0 || externalHeight <= 0) {
+    public void setDisplaySize(int displayId, int width, int height) {
+        if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("Invalid display id or dimensions.");
         }
         
         if (DEBUG) {
-            Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height
-                    + " external size " + externalWidth + "x" + externalHeight);
+            Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
         }
-        nativeSetDisplaySize(mPtr, displayId, width, height, externalWidth, externalHeight);
+        // FIXME: external size is deprecated
+        nativeSetDisplaySize(mPtr, displayId, width, height, 1280, 720);
     }
-    
-    public void setDisplayOrientation(int displayId, int rotation, int externalRotation) {
+
+    public void setDisplayOrientation(int displayId, int rotation) {
         if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) {
             throw new IllegalArgumentException("Invalid rotation.");
         }
         
         if (DEBUG) {
-            Slog.d(TAG, "Setting display #" + displayId + " orientation to rotation " + rotation
-                    + " external rotation " + externalRotation);
+            Slog.d(TAG, "Setting display #" + displayId + " orientation to rotation " + rotation);
         }
-        nativeSetDisplayOrientation(mPtr, displayId, rotation, externalRotation);
+        // FIXME: external rotation is deprecated
+        nativeSetDisplayOrientation(mPtr, displayId, rotation, Surface.ROTATION_0);
     }
 
     /**
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 6b6d899..4f5561a 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -1008,7 +1008,7 @@
             public void run() {
                 dumpLocal(pw);
             }
-        });
+        }, 1000);
     }
 
     private void dumpLocal(PrintWriter pw) {
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index acf3249..7c7d4b1 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -240,7 +240,7 @@
                     WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
                             "  FREEZE " + mSurface + ": CREATE");
 
-            setRotation(originalRotation);
+            setRotationInTransaction(originalRotation);
         } finally {
             if (!inTransaction) {
                 Surface.closeTransaction();
@@ -260,7 +260,7 @@
         return delta;
     }
 
-    void setSnapshotTransform(Matrix matrix, float alpha) {
+    private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) {
         if (mSurface != null) {
             matrix.getValues(mTmpFloats);
             mSurface.setPosition(mTmpFloats[Matrix.MTRANS_X],
@@ -303,7 +303,7 @@
     }
 
     // Must be called while in a transaction.
-    private void setRotation(int rotation) {
+    private void setRotationInTransaction(int rotation) {
         mCurRotation = rotation;
 
         // Compute the transformation matrix that must be applied
@@ -313,13 +313,13 @@
         createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
 
         if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta);
-        setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
+        setSnapshotTransformInTransaction(mSnapshotInitialMatrix, 1.0f);
     }
 
     // Must be called while in a transaction.
-    public boolean setRotation(int rotation, SurfaceSession session,
+    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
             long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
-        setRotation(rotation);
+        setRotationInTransaction(rotation);
         if (TWO_PHASE_ANIMATION) {
             return startAnimation(session, maxAnimationDuration, animationScale,
                     finalWidth, finalHeight, false);
@@ -515,16 +515,15 @@
                     WindowManagerService.TAG,
                     ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
             Surface.openTransaction();
-
-            // Compute the transformation matrix that must be applied
-            // the the black frame to make it stay in the initial position
-            // before the new screen rotation.  This is different than the
-            // snapshot transformation because the snapshot is always based
-            // of the native orientation of the screen, not the orientation
-            // we were last in.
-            createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
-
             try {
+                // Compute the transformation matrix that must be applied
+                // the the black frame to make it stay in the initial position
+                // before the new screen rotation.  This is different than the
+                // snapshot transformation because the snapshot is always based
+                // of the native orientation of the screen, not the orientation
+                // we were last in.
+                createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
@@ -844,7 +843,7 @@
         return more;
     }
 
-    void updateSurfaces() {
+    void updateSurfacesInTransaction() {
         if (!mStarted) {
             return;
         }
@@ -884,7 +883,7 @@
             }
         }
 
-        setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
+        setSnapshotTransformInTransaction(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
     }
 
     public boolean stepAnimationLocked(long now) {
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 580f00d..232f2ed 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -597,7 +597,7 @@
             // THIRD LOOP: Update the surfaces of all windows.
 
             if (mScreenRotationAnimation != null) {
-                mScreenRotationAnimation.updateSurfaces();
+                mScreenRotationAnimation.updateSurfacesInTransaction();
             }
 
             final int N = winAnimatorList.size();
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 0d9db90..acade04 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -45,7 +45,6 @@
 import com.android.server.EventLogTags;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
-import com.android.server.display.DisplayDeviceInfo;
 import com.android.server.display.DisplayManagerService;
 import com.android.server.input.InputManagerService;
 import com.android.server.power.PowerManagerService;
@@ -163,7 +162,8 @@
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
-        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
+        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
+                DisplayManagerService.WindowManagerFuncs {
     static final String TAG = "WindowManager";
     static final boolean DEBUG = false;
     static final boolean DEBUG_ADD_REMOVE = false;
@@ -751,7 +751,7 @@
                 holder[0] = new WindowManagerService(context, pm, dm,
                         uiHandler, haveInputMethods, showBootMsgs, onlyCore);
             }
-        });
+        }, 0);
         return holder[0];
     }
 
@@ -766,7 +766,7 @@
                         * TYPE_LAYER_MULTIPLIER
                         + TYPE_LAYER_OFFSET;
             }
-        });
+        }, 0);
     }
 
     private WindowManagerService(Context context, PowerManagerService pm,
@@ -825,8 +825,11 @@
         Watchdog.getInstance().addMonitor(this);
 
         Surface.openTransaction();
-        createWatermark();
-        Surface.closeTransaction();
+        try {
+            createWatermarkInTransaction();
+        } finally {
+            Surface.closeTransaction();
+        }
     }
 
     public InputManagerService getInputManagerService() {
@@ -5731,41 +5734,44 @@
         mWaitingForConfig = true;
         mLayoutNeeded = true;
         startFreezingDisplayLocked(inTransaction);
-        mInputManager.setDisplayOrientation(0, rotation, Surface.ROTATION_0);
+        mInputManager.setDisplayOrientation(0, rotation);
 
         // We need to update our screen size information to match the new
         // rotation.  Note that this is redundant with the later call to
         // sendNewConfiguration() that must be called after this function
         // returns...  however we need to do the screen size part of that
-        // before then so we have the correct size to use when initializiation
+        // before then so we have the correct size to use when initializing
         // the rotation animation for the new rotation.
         computeScreenConfigurationLocked(null);
 
-        if (!inTransaction) {
-            if (SHOW_TRANSACTIONS)  Slog.i(TAG,
-                    ">>> OPEN TRANSACTION setRotationUnchecked");
-            Surface.openTransaction();
-        }
         final DisplayContent displayContent = getDefaultDisplayContent();
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        if (!inTransaction) {
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked");
+            }
+            Surface.openTransaction();
+        }
         try {
             // NOTE: We disable the rotation in the emulator because
             //       it doesn't support hardware OpenGL emulation yet.
             if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null
                     && mAnimator.mScreenRotationAnimation.hasScreenshot()) {
-                if (mAnimator.mScreenRotationAnimation.setRotation(rotation, mFxSession,
+                if (mAnimator.mScreenRotationAnimation.setRotationInTransaction(
+                        rotation, mFxSession,
                         MAX_ANIMATION_DURATION, mTransitionAnimationScale,
                         displayInfo.logicalWidth, displayInfo.logicalHeight)) {
                     updateLayoutToAnimationLocked();
                 }
             }
-            mDisplayManagerService.setDisplayOrientation(
-                    displayContent.getDisplayId(), rotation);
+
+            mDisplayManagerService.performTraversalInTransactionFromWindowManager();
         } finally {
             if (!inTransaction) {
                 Surface.closeTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION setRotationUnchecked");
+                if (SHOW_LIGHT_TRANSACTIONS) {
+                    Slog.i(TAG, "<<< CLOSE TRANSACTION setRotationUnchecked");
+                }
             }
         }
 
@@ -6801,15 +6807,11 @@
             mAnimator.setDisplayDimensions(displayInfo.logicalWidth, displayInfo.logicalHeight,
                 displayInfo.appWidth, displayInfo.appHeight);
 
-            DisplayDeviceInfo info = new DisplayDeviceInfo();
-            mDisplayManagerService.getDefaultExternalDisplayDeviceInfo(info);
-
             final DisplayContent displayContent = getDefaultDisplayContent();
             mInputManager.setDisplaySize(displayContent.getDisplayId(),
-                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight,
-                    info.width, info.height);
+                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight);
             mInputManager.setDisplayOrientation(displayContent.getDisplayId(),
-                    mDefaultDisplay.getRotation(), Surface.ROTATION_0);
+                    mDefaultDisplay.getRotation());
             mPolicy.setInitialDisplaySize(mDefaultDisplay, displayContent.mInitialDisplayWidth,
                     displayContent.mInitialDisplayHeight, displayContent.mInitialDisplayDensity);
         }
@@ -8569,15 +8571,18 @@
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
 
         Surface.openTransaction();
-
-        if (mWatermark != null) {
-            mWatermark.positionSurface(dw, dh);
-        }
-        if (mStrictModeFlash != null) {
-            mStrictModeFlash.positionSurface(dw, dh);
-        }
-
         try {
+            if (mWatermark != null) {
+                mWatermark.positionSurface(dw, dh);
+            }
+            if (mStrictModeFlash != null) {
+                mStrictModeFlash.positionSurface(dw, dh);
+            }
+
+            // Give the display manager a chance to adjust properties
+            // like display rotation if it needs to.
+            mDisplayManagerService.performTraversalInTransactionFromWindowManager();
+
             int repeats = 0;
 
             do {
@@ -9077,6 +9082,12 @@
         }
     }
 
+    public void requestTraversal() {
+        synchronized (mWindowMap) {
+            requestTraversalLocked();
+        }
+    }
+
     void requestTraversalLocked() {
         if (!mTraversalScheduled) {
             mTraversalScheduled = true;
@@ -9572,7 +9583,7 @@
         return val;
     }
 
-    void createWatermark() {
+    void createWatermarkInTransaction() {
         if (mWatermark != null) {
             return;
         }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index eb1f2d6..9004e10 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -40,6 +40,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.view.CompatibilityInfoHolder;
+import android.view.Display;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -521,13 +522,18 @@
     }
 
     @Override
+    public Context createDisplayContext(Display display) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean isRestricted() {
         throw new UnsupportedOperationException();        
     }
 
     /** @hide */
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo() {
+    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 260ee3e..e629b75 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -67,6 +67,7 @@
 import android.util.TypedValue;
 import android.view.BridgeInflater;
 import android.view.CompatibilityInfoHolder;
+import android.view.Display;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
@@ -925,6 +926,12 @@
     }
 
     @Override
+    public Context createDisplayContext(Display display) {
+        // pass
+        return null;
+    }
+
+    @Override
     public String[] databaseList() {
         // pass
         return null;
@@ -1357,7 +1364,7 @@
     }
 
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo() {
+    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
         // pass
         return null;
     }