Merge "test-hwc2: fix build error from ui/mat4.h include"
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 34c136a..a85d16e 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -69,3 +69,20 @@
 int32_t ANativeWindow_unlockAndPost(ANativeWindow* window) {
     return window->perform(window, NATIVE_WINDOW_UNLOCK_AND_POST);
 }
+
+int32_t ANativeWindow_setBuffersTransform(ANativeWindow* window, int32_t transform) {
+    static_assert(ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL == NATIVE_WINDOW_TRANSFORM_FLIP_H);
+    static_assert(ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL == NATIVE_WINDOW_TRANSFORM_FLIP_V);
+    static_assert(ANATIVEWINDOW_TRANSFORM_ROTATE_90 == NATIVE_WINDOW_TRANSFORM_ROT_90);
+
+    constexpr int32_t kAllTransformBits =
+            ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
+            ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL |
+            ANATIVEWINDOW_TRANSFORM_ROTATE_90;
+    if (!window || !getWindowProp(window, NATIVE_WINDOW_IS_VALID))
+        return -EINVAL;
+    if ((transform & ~kAllTransformBits) != 0)
+        return -EINVAL;
+
+    return native_window_set_buffers_transform(window, transform);
+}
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 9c5f096..2668927 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -34,6 +34,10 @@
 
     clang: true,
 
+    cppflags: [
+        "-std=c++1z"
+    ],
+
     srcs: [
         "AHardwareBuffer.cpp",
         "ANativeWindow.cpp",
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 6a46d7f..6cd5df3 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -50,6 +50,25 @@
     WINDOW_FORMAT_RGB_565            = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
 };
 
+/**
+ * Transforms that can be applied to buffers as they are displayed to a window.
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical
+ * mirror, and clockwise 90 degree rotation, in that order. Rotations of 180
+ * and 270 degrees are made up of those basic transforms.
+ */
+enum ANativeWindowTransform {
+    ANATIVEWINDOW_TRANSFORM_IDENTITY            = 0x00,
+    ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL   = 0x01,
+    ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL     = 0x02,
+    ANATIVEWINDOW_TRANSFORM_ROTATE_90           = 0x04,
+
+    ANATIVEWINDOW_TRANSFORM_ROTATE_180          = ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
+                                                  ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL,
+    ANATIVEWINDOW_TRANSFORM_ROTATE_270          = ANATIVEWINDOW_TRANSFORM_ROTATE_180 |
+                                                  ANATIVEWINDOW_TRANSFORM_ROTATE_90,
+};
+
 struct ANativeWindow;
 /**
  * {@link ANativeWindow} is opaque type that provides access to a native window.
@@ -147,6 +166,19 @@
  */
 int32_t ANativeWindow_unlockAndPost(ANativeWindow* window);
 
+#if __ANDROID_API__ >= __ANDROID_API_O__
+
+/**
+ * Set a transform that will be applied to future buffers posted to the window.
+ *
+ * @param transform combination of {@link ANativeWindowTransform} flags
+ * @return 0 if successful
+ * @return -EINVAL if @param transform is invalid
+ */
+int32_t ANativeWindow_setBuffersTransform(ANativeWindow* window, int32_t transform);
+
+#endif // __ANDROID_API__ >= __ANDROID_API_O__
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index dcfabac..b29c888 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -20,6 +20,7 @@
     ANativeWindow_lock;
     ANativeWindow_release;
     ANativeWindow_setBuffersGeometry;
+    ANativeWindow_setBuffersTransform;
     ANativeWindow_unlockAndPost;
   local:
     *;
diff --git a/libs/ui/ColorSpace.cpp b/libs/ui/ColorSpace.cpp
index 6a86bb5..49390d9 100644
--- a/libs/ui/ColorSpace.cpp
+++ b/libs/ui/ColorSpace.cpp
@@ -211,8 +211,8 @@
         "Display P3",
         {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
         {0.3127f, 0.3290f},
-        std::bind(rcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
-        std::bind(response,    _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f)
+        std::bind(rcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f),
+        std::bind(response,    _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f)
     };
 }
 
diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h
index 740ead3..4c4cd4e 100644
--- a/opengl/include/EGL/eglext.h
+++ b/opengl/include/EGL/eglext.h
@@ -670,12 +670,42 @@
 #endif
 #endif
 
-#ifndef EGL_KHR_pixel_format_float
-#define EGL_KHR_pixel_format_float 1
-#define EGL_COLOR_COMPONENT_TYPE_EXT 0x3339  // eglChooseConfig and eglGetConfigAttrib attribute
-#define EGL_COLOR_COMPONENT_TYPE_FIXED_EXT 0x333A  // Attribute value for COLOR_COMPONENT_TYPE
-#define EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT 0x333B  // Attribute value for COLOR_COMPONENT_TYPE
-#endif
+#ifndef EGL_EXT_gl_colorspace_bt2020_linear
+#define EGL_EXT_gl_colorspace_bt2020_linear 1
+#define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F
+#endif /* EGL_EXT_gl_colorspace_bt2020_linear */
+
+#ifndef EGL_EXT_gl_colorspace_bt2020_pq
+#define EGL_EXT_gl_colorspace_bt2020_pq 1
+#define EGL_GL_COLORSPACE_BT2020_PQ_EXT   0x3340
+#endif /* EGL_EXT_gl_colorspace_bt2020_pq */
+
+#ifndef EGL_EXT_gl_colorspace_scrgb_linear
+#define EGL_EXT_gl_colorspace_scrgb_linear 1
+#define EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT 0x3350
+#endif /* EGL_EXT_gl_colorspace_scrgb_linear */
+
+#ifndef EGL_EXT_pixel_format_float
+#define EGL_EXT_pixel_format_float 1
+#define EGL_COLOR_COMPONENT_TYPE_EXT      0x3339
+#define EGL_COLOR_COMPONENT_TYPE_FIXED_EXT 0x333A
+#define EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT 0x333B
+#endif /* EGL_EXT_pixel_format_float */
+
+#ifndef EGL_EXT_surface_SMPTE2086_metadata
+#define EGL_EXT_surface_SMPTE2086_metadata 1
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT 0x3341
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT 0x3342
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT 0x3343
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT 0x3344
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT 0x3345
+#define EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT 0x3346
+#define EGL_SMPTE2086_WHITE_POINT_X_EXT   0x3347
+#define EGL_SMPTE2086_WHITE_POINT_Y_EXT   0x3348
+#define EGL_SMPTE2086_MAX_LUMINANCE_EXT   0x3349
+#define EGL_SMPTE2086_MIN_LUMINANCE_EXT   0x334A
+#define EGL_METADATA_SCALING_EXT          50000
+#endif /* EGL_EXT_surface_SMPTE2086_metadata */
 
 #ifdef __cplusplus
 }
diff --git a/opengl/tools/glgen2/registry/egl.xml b/opengl/tools/glgen2/registry/egl.xml
index 518c369..af13395 100755
--- a/opengl/tools/glgen2/registry/egl.xml
+++ b/opengl/tools/glgen2/registry/egl.xml
@@ -723,6 +723,23 @@
         <enum value="0x3339" name="EGL_COLOR_COMPONENT_TYPE_EXT"/>
         <enum value="0x333A" name="EGL_COLOR_COMPONENT_TYPE_FIXED_EXT"/>
         <enum value="0x333B" name="EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT"/>
+            <unused start="0x333C" end="0x333E"/>
+        <enum value="0x333F" name="EGL_GL_COLORSPACE_BT2020_LINEAR_EXT"/>
+        <enum value="0x3340" name="EGL_GL_COLORSPACE_BT2020_PQ_EXT"/>
+        <enum value="0x3341" name="EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT"/>
+        <enum value="0x3342" name="EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT"/>
+        <enum value="0x3343" name="EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT"/>
+        <enum value="0x3344" name="EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT"/>
+        <enum value="0x3345" name="EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT"/>
+        <enum value="0x3346" name="EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT"/>
+        <enum value="0x3347" name="EGL_SMPTE2086_WHITE_POINT_X_EXT"/>
+        <enum value="0x3348" name="EGL_SMPTE2086_WHITE_POINT_Y_EXT"/>
+        <enum value="0x3349" name="EGL_SMPTE2086_MAX_LUMINANCE_EXT"/>
+        <enum value="0x334A" name="EGL_SMPTE2086_MIN_LUMINANCE_EXT"/>
+        <enum value="50000"  name="EGL_METADATA_SCALING_EXT"/>
+            <unused start="0x334B" end="0x334F"/>
+        <enum value="0x3350" name="EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT"/>
+            <unused start="0x3351" end="0x339F"/>
     </enums>
 
     <enums namespace="EGL" start="0x33A0" end="0x33AF" vendor="ANGLE" comment="Reserved for Shannon Woods (Bug 13175)">
@@ -765,8 +782,8 @@
 
 <!-- Reservable for future use. To generate a new range, allocate multiples
      of 16 starting at the lowest available point in this block. -->
-    <enums namespace="EGL" start="0x3420" end="0x3FFF" vendor="KHR">
-            <unused start="0x3420" end="0x3FFF" comment="Reserved for future use"/>
+    <enums namespace="EGL" start="0x3470" end="0x3FFF" vendor="KHR">
+            <unused start="0x3470" end="0x3FFF" comment="Reserved for future use"/>
     </enums>
 
     <enums namespace="EGL" start="0x8F70" end="0x8F7F" vendor="HI" comment="For Mark Callow, Khronos bug 4055. Shared with GL.">
@@ -1859,6 +1876,21 @@
                 <command name="eglQueryDisplayAttribEXT"/>
             </require>
         </extension>
+        <extension name="EGL_EXT_gl_colorspace_bt2020_linear" supported="egl">
+            <require>
+                <enum name="EGL_GL_COLORSPACE_BT2020_LINEAR_EXT"/>
+            </require>
+        </extension>
+        <extension name="EGL_EXT_gl_colorspace_bt2020_pq" supported="egl">
+            <require>
+                <enum name="EGL_GL_COLORSPACE_BT2020_PQ_EXT"/>
+            </require>
+        </extension>
+        <extension name="EGL_EXT_gl_colorspace_scrgb_linear" supported="egl">
+            <require>
+                <enum name="EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT"/>
+            </require>
+        </extension>
         <extension name="EGL_EXT_image_dma_buf_import" supported="egl">
             <require>
                 <enum name="EGL_LINUX_DMA_BUF_EXT"/>
@@ -1955,6 +1987,22 @@
                 <command name="eglStreamConsumerOutputEXT"/>
             </require>
         </extension>
+        <extension name="EGL_EXT_surface_SMPTE2086_metadata" supported="egl">
+            <require>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT"/>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT"/>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT"/>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT"/>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT"/>
+                <enum name="EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT"/>
+                <enum name="EGL_SMPTE2086_WHITE_POINT_X_EXT"/>
+                <enum name="EGL_SMPTE2086_WHITE_POINT_Y_EXT"/>
+                <enum name="EGL_SMPTE2086_MAX_LUMINANCE_EXT"/>
+                <enum name="EGL_SMPTE2086_MIN_LUMINANCE_EXT"/>
+                <enum name="EGL_METADATA_SCALING_EXT"/>
+            </require>
+        </extension>
+
         <extension name="EGL_EXT_swap_buffers_with_damage" supported="egl">
             <require>
                 <command name="eglSwapBuffersWithDamageEXT"/>
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index 1c1db74..5ea3445 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -65,13 +65,6 @@
         DisplayHardware/HWComposer_hwc1.cpp
 endif
 
-ifeq ($(TARGET_BOARD_PLATFORM),omap4)
-    LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY
-endif
-ifeq ($(TARGET_BOARD_PLATFORM),s5pc110)
-    LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY
-endif
-
 ifeq ($(TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS),true)
     LOCAL_CFLAGS += -DFORCE_HWC_COPY_FOR_VIRTUAL_DISPLAYS
 endif
diff --git a/services/surfaceflinger/RenderEngine/RenderEngine.cpp b/services/surfaceflinger/RenderEngine/RenderEngine.cpp
index 9909bf9..e3dbecc 100644
--- a/services/surfaceflinger/RenderEngine/RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/RenderEngine.cpp
@@ -23,6 +23,9 @@
 #include "GLExtensions.h"
 #include "Mesh.h"
 
+#include <vector>
+#include <SurfaceFlinger.h>
+
 EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
 
 // ---------------------------------------------------------------------------
@@ -76,18 +79,21 @@
         LOG_ALWAYS_FATAL("no supported EGL_RENDERABLE_TYPEs");
     }
 
-    // Also create our EGLContext
-    EGLint contextAttributes[] = {
-            EGL_CONTEXT_CLIENT_VERSION, contextClientVersion,      // MUST be first
+    std::vector<EGLint> contextAttributes;
+    contextAttributes.reserve(6);
+    contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
+    contextAttributes.push_back(contextClientVersion);
 #ifdef EGL_IMG_context_priority
-#ifdef HAS_CONTEXT_PRIORITY
-#warning "using EGL_IMG_context_priority"
-            EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG,
+    if (SurfaceFlinger::useContextPriority) {
+        contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
+        contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
+    }
 #endif
-#endif
-            EGL_NONE, EGL_NONE
-    };
-    EGLContext ctxt = eglCreateContext(display, config, NULL, contextAttributes);
+    contextAttributes.push_back(EGL_NONE);
+    contextAttributes.push_back(EGL_NONE);
+
+    EGLContext ctxt = eglCreateContext(display, config, NULL,
+                                       contextAttributes.data());
 
     // if can't create a GL context, we can only abort.
     LOG_ALWAYS_FATAL_IF(ctxt==EGL_NO_CONTEXT, "EGLContext creation failed");
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 8420807..e3a3ff4 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -112,6 +112,7 @@
 // ---------------------------------------------------------------------------
 int64_t SurfaceFlinger::vsyncPhaseOffsetNs;
 int64_t SurfaceFlinger::sfVsyncPhaseOffsetNs;
+bool SurfaceFlinger::useContextPriority;
 
 SurfaceFlinger::SurfaceFlinger()
     :   BnSurfaceComposer(),
@@ -164,6 +165,9 @@
 
     ALOGI("SurfaceFlinger is starting");
 
+    useContextPriority = getBool< ISurfaceFlingerConfigs,
+            &ISurfaceFlingerConfigs::useContextPriority>(false);
+
     // debugging stuff...
     char value[PROPERTY_VALUE_MAX];
 
@@ -3224,9 +3228,8 @@
 void SurfaceFlinger::appendSfConfigString(String8& result) const
 {
     result.append(" [sf");
-#ifdef HAS_CONTEXT_PRIORITY
-    result.append(" HAS_CONTEXT_PRIORITY");
-#endif
+    result.appendFormat(" HAS_CONTEXT_PRIORITY=%d", useContextPriority);
+
 #ifdef NEVER_DEFAULT_TO_ASYNC_MODE
     result.append(" NEVER_DEFAULT_TO_ASYNC_MODE");
 #endif
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7269afe..bfe0c9c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -129,6 +129,9 @@
     static int64_t vsyncPhaseOffsetNs;
     static int64_t sfVsyncPhaseOffsetNs;
 
+    // Instruct the Render Engine to use EGL_IMG_context_priority is available.
+    static bool useContextPriority;
+
     static char const* getServiceName() ANDROID_API {
         return "SurfaceFlinger";
     }
diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
index 8c651da..2932fc9 100644
--- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
+++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
@@ -111,6 +111,7 @@
 // ---------------------------------------------------------------------------
 int64_t SurfaceFlinger::vsyncPhaseOffsetNs;
 int64_t SurfaceFlinger::sfVsyncPhaseOffsetNs;
+bool SurfaceFlinger::useContextPriority;
 
 SurfaceFlinger::SurfaceFlinger()
     :   BnSurfaceComposer(),
@@ -155,6 +156,9 @@
 
     ALOGI("SurfaceFlinger is starting");
 
+    useContextPriority = getBool< ISurfaceFlingerConfigs,
+            &ISurfaceFlingerConfigs::useContextPriority>(false);
+
     char value[PROPERTY_VALUE_MAX];
 
     property_get("ro.bq.gpu_to_cpu_unsupported", value, "0");
@@ -2996,9 +3000,8 @@
 void SurfaceFlinger::appendSfConfigString(String8& result) const
 {
     result.append(" [sf");
-#ifdef HAS_CONTEXT_PRIORITY
-    result.append(" HAS_CONTEXT_PRIORITY");
-#endif
+    result.appendFormat(" HAS_CONTEXT_PRIORITY=%d", useContextPriority);
+
 #ifdef NEVER_DEFAULT_TO_ASYNC_MODE
     result.append(" NEVER_DEFAULT_TO_ASYNC_MODE");
 #endif
diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp
index 0b84184..1d3a1d3 100644
--- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp
+++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp
@@ -21,7 +21,6 @@
 
 #include <gui/Surface.h>
 #include <gui/BufferItemConsumer.h>
-#include <gui/GraphicBufferAlloc.h>
 
 #include <ui/GraphicBuffer.h>
 #include <math/vec4.h>
@@ -395,15 +394,17 @@
  * devices */
 int Hwc2TestBuffer::generateBuffer()
 {
-    int ret;
-
     /* Create new graphic buffer with correct dimensions */
-    mGraphicBuffer = mGraphicBufferAlloc.createGraphicBuffer(
-            mBufferArea.width, mBufferArea.height, mFormat,
-            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER,
-            "hwc2_test_buffer", &ret);
-    if (ret)
+    mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height,
+            mFormat, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER,
+            "hwc2_test_buffer");
+    int ret = mGraphicBuffer->initCheck();
+    if (ret) {
         return ret;
+    }
+    if (!mGraphicBuffer->handle) {
+        return -EINVAL;
+    }
 
     /* Locks the buffer for writing */
     uint8_t* img;
@@ -466,15 +467,17 @@
         const std::set<hwc2_layer_t>* clientLayers,
         const std::set<hwc2_layer_t>* clearLayers)
 {
-    int err;
-
-    /* Create new graphic buffer with updated size */
-    mGraphicBuffer = mGraphicBufferAlloc.createGraphicBuffer(bufferArea.width,
-            bufferArea.height, mFormat,
-            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER,
-            "hwc2_test_buffer", &err);
-    if (err)
-        return err;
+    /* Create new graphic buffer with correct dimensions */
+    mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height,
+            mFormat, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER,
+            "hwc2_test_buffer");
+    int ret = mGraphicBuffer->initCheck();
+    if (ret) {
+        return ret;
+    }
+    if (!mGraphicBuffer->handle) {
+        return -EINVAL;
+    }
 
     uint8_t* img;
     mGraphicBuffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h
index ca60940..b2b3a66 100644
--- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h
+++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h
@@ -22,7 +22,6 @@
 
 #include <hardware/hwcomposer2.h>
 
-#include <gui/GraphicBufferAlloc.h>
 #include <ui/GraphicBuffer.h>
 
 #include "Hwc2TestProperties.h"
@@ -42,7 +41,6 @@
 protected:
     int generateBuffer();
 
-    android::GraphicBufferAlloc mGraphicBufferAlloc;
     android::sp<android::GraphicBuffer> mGraphicBuffer;
 
     std::unique_ptr<Hwc2TestFenceGenerator> mFenceGenerator;
@@ -66,7 +64,6 @@
             const std::set<hwc2_layer_t>* clearLayers);
 
 protected:
-    android::GraphicBufferAlloc mGraphicBufferAlloc;
     android::sp<android::GraphicBuffer> mGraphicBuffer;
 
     std::unique_ptr<Hwc2TestFenceGenerator> mFenceGenerator;
diff --git a/services/vr/vr_window_manager/Android.mk b/services/vr/vr_window_manager/Android.mk
index ddb58e9..59ef63c 100644
--- a/services/vr/vr_window_manager/Android.mk
+++ b/services/vr/vr_window_manager/Android.mk
@@ -17,6 +17,7 @@
 native_src := \
   application.cpp \
   controller_mesh.cpp \
+  display_view.cpp \
   elbow_model.cpp \
   hwc_callback.cpp \
   reticle.cpp \
diff --git a/services/vr/vr_window_manager/application.cpp b/services/vr/vr_window_manager/application.cpp
index dba797f..eb9f407 100644
--- a/services/vr/vr_window_manager/application.cpp
+++ b/services/vr/vr_window_manager/application.cpp
@@ -224,6 +224,8 @@
     DrawEye(kRightEye, fov_[kRightEye].GetProjectionMatrix(0.1f, 500.0f),
             eye_from_head_[kRightEye], head_matrix);
 
+    OnEndFrame();
+
     dvrPresent(graphics_context_);
   }
 }
diff --git a/services/vr/vr_window_manager/application.h b/services/vr/vr_window_manager/application.h
index 2c60e0a..6215561 100644
--- a/services/vr/vr_window_manager/application.h
+++ b/services/vr/vr_window_manager/application.h
@@ -57,6 +57,7 @@
   virtual void OnDrawFrame() = 0;
   virtual void DrawEye(EyeType eye, const mat4& perspective,
                        const mat4& eye_matrix, const mat4& head_matrix) = 0;
+  virtual void OnEndFrame() = 0;
 
   void SetVisibility(bool visible);
   virtual void OnVisibilityChanged(bool visible);
diff --git a/services/vr/vr_window_manager/composer/impl/vr_hwc.cpp b/services/vr/vr_window_manager/composer/impl/vr_hwc.cpp
index 6a78c98..8b50c01 100644
--- a/services/vr/vr_window_manager/composer/impl/vr_hwc.cpp
+++ b/services/vr/vr_window_manager/composer/impl/vr_hwc.cpp
@@ -102,7 +102,8 @@
 
 HwcLayer* HwcDisplay::GetLayer(Layer id) {
   for (size_t i = 0; i < layers_.size(); ++i)
-    if (layers_[i].info.id == id) return &layers_[i];
+    if (layers_[i].info.id == id)
+      return &layers_[i];
 
   return nullptr;
 }
@@ -219,7 +220,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // VrHwcClient
 
-VrHwc::VrHwc() {}
+VrHwc::VrHwc() { displays_[kDefaultDisplayId].reset(new HwcDisplay()); }
 
 VrHwc::~VrHwc() {}
 
@@ -231,7 +232,6 @@
 }
 
 void VrHwc::enableCallback(bool enable) {
-  std::lock_guard<std::mutex> guard(mutex_);
   if (enable && client_ != nullptr) {
     client_.promote()->onHotplug(kDefaultDisplayId,
                                  IComposerCallback::Connection::CONNECTED);
@@ -247,31 +247,43 @@
   return Error::NONE;
 }
 
-Error VrHwc::destroyVirtualDisplay(Display display) { return Error::NONE; }
+Error VrHwc::destroyVirtualDisplay(Display display) {
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (display == kDefaultDisplayId || displays_.erase(display) == 0)
+    return Error::BAD_DISPLAY;
+  ComposerView::Frame frame;
+  frame.display_id = display;
+  frame.removed = true;
+  if (observer_)
+    observer_->OnNewFrame(frame);
+  return Error::NONE;
+}
 
 Error VrHwc::createLayer(Display display, Layer* outLayer) {
-  if (display != kDefaultDisplayId) {
-    return Error::BAD_DISPLAY;
-  }
-
   std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* layer = display_.CreateLayer();
+  HwcLayer* layer = display_ptr->CreateLayer();
   *outLayer = layer->info.id;
   return Error::NONE;
 }
 
 Error VrHwc::destroyLayer(Display display, Layer layer) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
   std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr) {
+    return Error::BAD_DISPLAY;
+  }
 
-  return display_.DestroyLayer(layer) ? Error::NONE : Error::BAD_LAYER;
+  return display_ptr->DestroyLayer(layer) ? Error::NONE : Error::BAD_LAYER;
 }
 
 Error VrHwc::getActiveConfig(Display display, Config* outConfig) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   *outConfig = kDefaultConfigId;
   return Error::NONE;
 }
@@ -291,20 +303,19 @@
 Error VrHwc::getDisplayAttribute(Display display, Config config,
                                  IComposerClient::Attribute attribute,
                                  int32_t* outValue) {
-  if (display != kDefaultDisplayId) {
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
     return Error::BAD_DISPLAY;
-  }
-
   if (config != kDefaultConfigId) {
     return Error::BAD_CONFIG;
   }
 
   switch (attribute) {
     case IComposerClient::Attribute::WIDTH:
-      *outValue = 1920;
+      *outValue = 1080;
       break;
     case IComposerClient::Attribute::HEIGHT:
-      *outValue = 1080;
+      *outValue = 1920;
       break;
     case IComposerClient::Attribute::VSYNC_PERIOD:
       *outValue = 1000 * 1000 * 1000 / 30;  // 30fps
@@ -321,10 +332,9 @@
 }
 
 Error VrHwc::getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) {
-  if (display != kDefaultDisplayId) {
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
     return Error::BAD_DISPLAY;
-  }
-
   std::vector<Config> configs(1, kDefaultConfigId);
   *outConfigs = hidl_vec<Config>(configs);
   return Error::NONE;
@@ -337,7 +347,9 @@
 
 Error VrHwc::getDisplayType(Display display,
                             IComposerClient::DisplayType* outType) {
-  if (display != kDefaultDisplayId) {
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr) {
     *outType = IComposerClient::DisplayType::INVALID;
     return Error::BAD_DISPLAY;
   }
@@ -348,10 +360,10 @@
 
 Error VrHwc::getDozeSupport(Display display, bool* outSupport) {
   *outSupport = false;
-  if (display == kDefaultDisplayId)
-    return Error::NONE;
-  else
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
     return Error::BAD_DISPLAY;
+  return Error::NONE;
 }
 
 Error VrHwc::getHdrCapabilities(Display display, hidl_vec<Hdr>* outTypes,
@@ -365,35 +377,41 @@
 }
 
 Error VrHwc::setActiveConfig(Display display, Config config) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
-  if (config != kDefaultConfigId) return Error::BAD_CONFIG;
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
+  if (config != kDefaultConfigId)
+    return Error::BAD_CONFIG;
 
   return Error::NONE;
 }
 
 Error VrHwc::setColorMode(Display display, ColorMode mode) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setPowerMode(Display display, IComposerClient::PowerMode mode) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setColorTransform(Display display, const float* matrix,
                                int32_t hint) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
@@ -401,13 +419,15 @@
                              int32_t acquireFence, int32_t dataspace,
                              const std::vector<hwc_rect_t>& damage) {
   base::unique_fd fence(acquireFence);
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
-  if (target == nullptr) return Error::NONE;
-
   std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  if (!display_.SetClientTarget(target, std::move(fence)))
+  if (target == nullptr)
+    return Error::NONE;
+
+  if (!display_ptr->SetClientTarget(target, std::move(fence)))
     return Error::BAD_PARAMETER;
 
   return Error::NONE;
@@ -416,7 +436,10 @@
 Error VrHwc::setOutputBuffer(Display display, buffer_handle_t buffer,
                              int32_t releaseFence) {
   base::unique_fd fence(releaseFence);
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
   ALOGE("Virtual display support not implemented");
   return Error::UNSUPPORTED;
@@ -427,13 +450,13 @@
     std::vector<IComposerClient::Composition>* outCompositionTypes,
     uint32_t* outDisplayRequestMask, std::vector<Layer>* outRequestedLayers,
     std::vector<uint32_t>* outRequestMasks) {
-  if (display != kDefaultDisplayId) {
-    return Error::BAD_DISPLAY;
-  }
-
   std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  display_.GetChangedCompositionTypes(outChangedLayers, outCompositionTypes);
+  display_ptr->GetChangedCompositionTypes(outChangedLayers,
+                                          outCompositionTypes);
   return Error::NONE;
 }
 
@@ -446,18 +469,20 @@
   outLayers->clear();
   outReleaseFences->clear();
 
-  if (display != kDefaultDisplayId) {
-    return Error::BAD_DISPLAY;
-  }
-
-  std::vector<ComposerView::ComposerLayer> frame;
-  std::vector<Layer> last_frame_layers;
   std::lock_guard<std::mutex> guard(mutex_);
-  Error status = display_.GetFrame(&frame);
+  auto display_ptr = FindDisplay(display);
+
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
+
+  ComposerView::Frame frame;
+  std::vector<Layer> last_frame_layers;
+  Error status = display_ptr->GetFrame(&frame.layers);
+  frame.display_id = display;
   if (status != Error::NONE)
     return status;
 
-  last_frame_layers = display_.UpdateLastFrameAndGetLastFrameLayers();
+  last_frame_layers = display_ptr->UpdateLastFrameAndGetLastFrameLayers();
 
   base::unique_fd fence;
   if (observer_)
@@ -476,18 +501,23 @@
 
 Error VrHwc::setLayerCursorPosition(Display display, Layer layer, int32_t x,
                                     int32_t y) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerBuffer(Display display, Layer layer,
                             buffer_handle_t buffer, int32_t acquireFence) {
   base::unique_fd fence(acquireFence);
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.buffer = GetBufferFromHandle(buffer);
   hwc_layer->info.fence = new Fence(fence.release());
@@ -497,16 +527,21 @@
 
 Error VrHwc::setLayerSurfaceDamage(Display display, Layer layer,
                                    const std::vector<hwc_rect_t>& damage) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerBlendMode(Display display, Layer layer, int32_t mode) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.blend_mode =
       static_cast<ComposerView::ComposerLayer::BlendMode>(mode);
@@ -516,17 +551,22 @@
 
 Error VrHwc::setLayerColor(Display display, Layer layer,
                            IComposerClient::Color color) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerCompositionType(Display display, Layer layer,
                                      int32_t type) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->composition_type = static_cast<HwcLayer::Composition>(type);
 
@@ -535,17 +575,22 @@
 
 Error VrHwc::setLayerDataspace(Display display, Layer layer,
                                int32_t dataspace) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerDisplayFrame(Display display, Layer layer,
                                   const hwc_rect_t& frame) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.display_frame =
       {frame.left, frame.top, frame.right, frame.bottom};
@@ -554,10 +599,14 @@
 }
 
 Error VrHwc::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.alpha = alpha;
 
@@ -566,17 +615,22 @@
 
 Error VrHwc::setLayerSidebandStream(Display display, Layer layer,
                                     buffer_handle_t stream) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerSourceCrop(Display display, Layer layer,
                                 const hwc_frect_t& crop) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.crop = {crop.left, crop.top, crop.right, crop.bottom};
 
@@ -585,23 +639,29 @@
 
 Error VrHwc::setLayerTransform(Display display, Layer layer,
                                int32_t transform) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerVisibleRegion(Display display, Layer layer,
                                    const std::vector<hwc_rect_t>& visible) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
-
+  std::lock_guard<std::mutex> guard(mutex_);
+  if (!FindDisplay(display))
+    return Error::BAD_DISPLAY;
   return Error::NONE;
 }
 
 Error VrHwc::setLayerZOrder(Display display, Layer layer, uint32_t z) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->z_order = z;
 
@@ -610,10 +670,14 @@
 
 Error VrHwc::setLayerInfo(Display display, Layer layer, uint32_t type,
                           uint32_t appId) {
-  if (display != kDefaultDisplayId) return Error::BAD_DISPLAY;
+  std::lock_guard<std::mutex> guard(mutex_);
+  auto display_ptr = FindDisplay(display);
+  if (!display_ptr)
+    return Error::BAD_DISPLAY;
 
-  HwcLayer* hwc_layer = display_.GetLayer(layer);
-  if (!hwc_layer) return Error::BAD_LAYER;
+  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
+  if (!hwc_layer)
+    return Error::BAD_LAYER;
 
   hwc_layer->info.type = type;
   hwc_layer->info.app_id = appId;
@@ -665,6 +729,11 @@
     observer_ = nullptr;
 }
 
+HwcDisplay* VrHwc::FindDisplay(Display display) {
+  auto iter = displays_.find(display);
+  return iter == displays_.end() ? nullptr : iter->second.get();
+}
+
 ComposerView* GetComposerViewFromIComposer(
     hardware::graphics::composer::V2_1::IComposer* composer) {
   return static_cast<VrHwc*>(composer);
diff --git a/services/vr/vr_window_manager/composer/impl/vr_hwc.h b/services/vr/vr_window_manager/composer/impl/vr_hwc.h
index df09687..b869d3e 100644
--- a/services/vr/vr_window_manager/composer/impl/vr_hwc.h
+++ b/services/vr/vr_window_manager/composer/impl/vr_hwc.h
@@ -24,6 +24,7 @@
 #include <utils/StrongPointer.h>
 
 #include <mutex>
+#include <unordered_map>
 
 using namespace android::hardware::graphics::common::V1_0;
 using namespace android::hardware::graphics::composer::V2_1;
@@ -67,7 +68,14 @@
     uint32_t app_id;
   };
 
-  using Frame = std::vector<ComposerLayer>;
+  struct Frame {
+    Display display_id;
+    // This is set to true to notify the upper layer that the display is
+    // being removed, or left false in the case of a normal frame. The upper
+    // layer tracks display IDs and will handle new ones showing up.
+    bool removed = false;
+    std::vector<ComposerLayer> layers;
+  };
 
   class Observer {
    public:
@@ -231,13 +239,15 @@
   void UnregisterObserver(Observer* observer) override;
 
  private:
+  HwcDisplay* FindDisplay(Display display);
+
   wp<VrComposerClient> client_;
   sp<IComposerCallback> callbacks_;
 
   // Guard access to internal state from binder threads.
   std::mutex mutex_;
 
-  HwcDisplay display_;
+  std::unordered_map<Display, std::unique_ptr<HwcDisplay>> displays_;
 
   Observer* observer_ = nullptr;
 
diff --git a/services/vr/vr_window_manager/display_view.cpp b/services/vr/vr_window_manager/display_view.cpp
new file mode 100644
index 0000000..5f1e73e
--- /dev/null
+++ b/services/vr/vr_window_manager/display_view.cpp
@@ -0,0 +1,432 @@
+#include "display_view.h"
+
+#include "texture.h"
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+constexpr float kLayerScaleFactor = 3.0f;
+constexpr unsigned int kVRAppLayerCount = 2;
+constexpr unsigned int kMaximumPendingFrames = 8;
+
+// clang-format off
+const GLfloat kVertices[] = {
+  -1, -1, 0,
+   1, -1, 0,
+  -1, 1, 0,
+   1, 1, 0,
+};
+
+const GLfloat kTextureVertices[] = {
+  0, 1,
+  1, 1,
+  0, 0,
+  1, 0,
+};
+// clang-format on
+
+// Returns true if the given point is inside the given rect.
+bool IsInside(const vec2& pt, const vec2& tl, const vec2& br) {
+  return pt.x() >= tl.x() && pt.x() <= br.x() && pt.y() >= tl.y() &&
+         pt.y() <= br.y();
+}
+
+mat4 GetScalingMatrix(float width, float height) {
+  float xscale = 1, yscale = 1;
+  float ar = width / height;
+  if (ar > 1)
+    yscale = 1.0 / ar;
+  else
+    xscale = ar;
+
+  xscale *= kLayerScaleFactor;
+  yscale *= kLayerScaleFactor;
+
+  return mat4(Eigen::Scaling<float>(xscale, yscale, 1.0));
+}
+
+// Helper function that applies the crop transform to the texture layer and
+// positions (and scales) the texture layer in the appropriate location in the
+// display space.
+mat4 GetLayerTransform(const TextureLayer& texture_layer, float display_width,
+                       float display_height) {
+  // Map from vertex coordinates to [0, 1] coordinates:
+  //  1) Flip y since in vertex coordinates (-1, -1) is at the bottom left and
+  //     in texture coordinates (0, 0) is at the top left.
+  //  2) Translate by (1, 1) to map vertex coordinates to [0, 2] on x and y.
+  //  3) Scale by 1 / 2 to map coordinates to [0, 1] on x  and y.
+  mat4 unit_space(Eigen::AlignedScaling3f(0.5f, 0.5f, 1.0f) *
+                  Eigen::Translation3f(1.0f, 1.0f, 0.0f) *
+                  Eigen::AlignedScaling3f(1.0f, -1.0f, 1.0f));
+
+  mat4 texture_space(Eigen::AlignedScaling3f(
+      texture_layer.texture->width(), texture_layer.texture->height(), 1.0f));
+
+  // 1) Translate the layer to crop the left and top edge.
+  // 2) Scale the layer such that the cropped right and bottom edges map outside
+  //    the exture region.
+  float crop_width = texture_layer.crop.right - texture_layer.crop.left;
+  float crop_height = texture_layer.crop.bottom - texture_layer.crop.top;
+  mat4 texture_crop(Eigen::AlignedScaling3f(
+                        texture_layer.texture->width() / crop_width,
+                        texture_layer.texture->height() / crop_height, 1.0f) *
+                    Eigen::Translation3f(-texture_layer.crop.left,
+                                         -texture_layer.crop.top, 0.0f));
+
+  mat4 display_space(
+      Eigen::AlignedScaling3f(display_width, display_height, 1.0f));
+
+  // 1) Scale the texture to fit the display frame.
+  // 2) Translate the texture in the display frame location.
+  float display_frame_width =
+      texture_layer.display_frame.right - texture_layer.display_frame.left;
+  float display_frame_height =
+      texture_layer.display_frame.bottom - texture_layer.display_frame.top;
+  mat4 display_frame(
+      Eigen::Translation3f(texture_layer.display_frame.left,
+                           texture_layer.display_frame.top, 0.0f) *
+      Eigen::AlignedScaling3f(display_frame_width / display_width,
+                              display_frame_height / display_height, 1.0f));
+
+  mat4 layer_transform = unit_space.inverse() * display_space.inverse() *
+                         display_frame * display_space *
+                         texture_space.inverse() * texture_crop *
+                         texture_space * unit_space;
+  return layer_transform;
+}
+
+// Determine if ths frame should be shown or hidden.
+ViewMode CalculateVisibilityFromLayerConfig(const HwcCallback::Frame& frame,
+                                            uint32_t vr_app) {
+  auto& layers = frame.layers();
+
+  // TODO(achaulk): Figure out how to identify the current VR app for 2D app
+  // detection.
+
+  size_t index;
+  // Skip all layers that we don't know about.
+  for (index = 0; index < layers.size(); index++) {
+    if (layers[index].type != 0xFFFFFFFF && layers[index].type != 0)
+      break;
+  }
+
+  if (index == layers.size())
+    return ViewMode::Hidden;
+
+  if (layers[index].type != 1) {
+    // We don't have a VR app layer? Abort.
+    return ViewMode::Hidden;
+  }
+
+  // This is the VR app, ignore it.
+  index++;
+
+  // Now, find a dim layer if it exists.
+  // If it does, ignore any layers behind it for visibility determination.
+  for (size_t i = index; i < layers.size(); i++) {
+    if (layers[i].appid == HwcCallback::HwcLayer::kSurfaceFlingerLayer) {
+      index = i + 1;
+    }
+  }
+
+  // If any non-skipped layers exist now then we show, otherwise hide.
+  for (size_t i = index; i < layers.size(); i++) {
+    if (!layers[i].should_skip_layer())
+      return ViewMode::VR;
+  }
+  return ViewMode::Hidden;
+}
+
+}  // namespace
+
+DisplayView::DisplayView(uint32_t id, int touchpad_id)
+    : id_(id), touchpad_id_(touchpad_id) {
+  translate_ = Eigen::Translation3f(0, 0, -2.5f);
+  ime_translate_ = mat4(Eigen::Translation3f(0.0f, -0.5f, 0.25f));
+  ime_top_left_ = vec2(0, 0);
+  ime_size_ = vec2(0, 0);
+}
+
+DisplayView::~DisplayView() {}
+
+void DisplayView::Recenter(const mat4& initial) {
+  initial_head_matrix_ = initial;
+}
+
+void DisplayView::SetPrograms(ShaderProgram* program,
+                              ShaderProgram* overlay_program) {
+  program_ = program;
+  overlay_program_ = overlay_program;
+}
+
+void DisplayView::DrawEye(EyeType /* eye */, const mat4& perspective,
+                          const mat4& eye_matrix, const mat4& head_matrix,
+                          const vec2& size, float fade_value) {
+  size_ = size;
+  scale_ = GetScalingMatrix(size_.x(), size_.y());
+
+  DrawOverlays(perspective, eye_matrix, head_matrix, fade_value);
+}
+
+void DisplayView::AdvanceFrame() {
+  if (!pending_frames_.empty()) {
+    // Check if we should advance the frame.
+    auto& frame = pending_frames_.front();
+    if (frame.visibility == ViewMode::Hidden ||
+        frame.frame->Finish() == HwcCallback::FrameStatus::kFinished) {
+      current_frame_ = std::move(frame);
+      pending_frames_.pop_front();
+    }
+  }
+}
+
+void DisplayView::OnDrawFrame(SurfaceFlingerView* surface_flinger_view,
+                              bool debug_mode) {
+  textures_.clear();
+  has_ime_ = false;
+
+  if (!visible())
+    return;
+
+  surface_flinger_view->GetTextures(*current_frame_.frame.get(), &textures_,
+                                    &ime_texture_, debug_mode,
+                                    current_frame_.visibility == ViewMode::VR);
+  has_ime_ = ime_texture_.texture != nullptr;
+}
+
+base::unique_fd DisplayView::OnFrame(std::unique_ptr<HwcCallback::Frame> frame,
+                                     bool debug_mode, bool* showing) {
+  ViewMode visibility =
+      CalculateVisibilityFromLayerConfig(*frame.get(), current_vr_app_);
+
+  if (visibility == ViewMode::Hidden && debug_mode)
+    visibility = ViewMode::VR;
+
+  if (frame->layers().empty())
+    current_vr_app_ = 0;
+  else
+    current_vr_app_ = frame->layers().front().appid;
+
+  pending_frames_.emplace_back(std::move(frame), visibility);
+
+  if (pending_frames_.size() > kMaximumPendingFrames) {
+    pending_frames_.pop_front();
+  }
+
+  if (visibility == ViewMode::Hidden &&
+      current_frame_.visibility == ViewMode::Hidden) {
+    // Consume all frames while hidden.
+    while (!pending_frames_.empty())
+      AdvanceFrame();
+  }
+
+  // If we are showing ourselves the main thread is not processing anything,
+  // so give it a kick.
+  if (visibility != ViewMode::Hidden &&
+      current_frame_.visibility == ViewMode::Hidden) {
+    *showing = true;
+  }
+
+  return base::unique_fd(dup(release_fence_.get()));
+}
+
+bool DisplayView::IsHit(const vec3& view_location, const vec3& view_direction,
+                        vec3* hit_location, vec2* hit_location_in_window_coord,
+                        bool test_ime) {
+  mat4 m = initial_head_matrix_ * translate_;
+  if (test_ime)
+    m = m * ime_translate_;
+  mat4 inverse = (m * scale_).inverse();
+  vec4 transformed_loc =
+      inverse * vec4(view_location[0], view_location[1], view_location[2], 1);
+  vec4 transformed_dir = inverse * vec4(view_direction[0], view_direction[1],
+                                        view_direction[2], 0);
+
+  if (transformed_dir.z() >= 0 || transformed_loc.z() <= 0)
+    return false;
+
+  float distance = -transformed_loc.z() / transformed_dir.z();
+  vec4 transformed_hit_loc = transformed_loc + transformed_dir * distance;
+  if (transformed_hit_loc.x() < -1 || transformed_hit_loc.x() > 1)
+    return false;
+  if (transformed_hit_loc.y() < -1 || transformed_hit_loc.y() > 1)
+    return false;
+
+  hit_location_in_window_coord->x() =
+      (1 + transformed_hit_loc.x()) / 2 * size_.x();
+  hit_location_in_window_coord->y() =
+      (1 - transformed_hit_loc.y()) / 2 * size_.y();
+
+  *hit_location = view_location + view_direction * distance;
+  return true;
+}
+
+void DisplayView::DrawOverlays(const mat4& perspective, const mat4& eye_matrix,
+                               const mat4& head_matrix, float fade_value) {
+  if (textures_.empty())
+    return;
+
+  program_->Use();
+  mat4 mvp = perspective * eye_matrix * head_matrix;
+  GLint view_projection_location =
+      glGetUniformLocation(program_->GetProgram(), "uViewProjection");
+  glUniformMatrix4fv(view_projection_location, 1, 0, mvp.data());
+
+  GLint alpha_location = glGetUniformLocation(program_->GetProgram(), "uAlpha");
+
+  GLint tex_location = glGetUniformLocation(program_->GetProgram(), "tex");
+  glUniform1i(tex_location, 0);
+  glActiveTexture(GL_TEXTURE0);
+
+  for (const auto& texture_layer : textures_) {
+    switch (texture_layer.blending) {
+      case HWC2_BLEND_MODE_PREMULTIPLIED:
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+        break;
+      case HWC2_BLEND_MODE_COVERAGE:
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        break;
+      default:
+        break;
+    }
+
+    glUniform1f(alpha_location, fade_value * texture_layer.alpha);
+
+    glBindTexture(GL_TEXTURE_2D, texture_layer.texture->id());
+
+    mat4 layer_transform =
+        GetLayerTransform(texture_layer, size_.x(), size_.y());
+
+    mat4 transform =
+        initial_head_matrix_ * translate_ * scale_ * layer_transform;
+    DrawWithTransform(transform, *program_);
+
+    glDisable(GL_BLEND);
+  }
+
+  if (has_ime_) {
+    ime_top_left_ = vec2(static_cast<float>(ime_texture_.display_frame.left),
+                         static_cast<float>(ime_texture_.display_frame.top));
+    ime_size_ = vec2(static_cast<float>(ime_texture_.display_frame.right -
+                                        ime_texture_.display_frame.left),
+                     static_cast<float>(ime_texture_.display_frame.bottom -
+                                        ime_texture_.display_frame.top));
+
+    DrawDimOverlay(mvp, textures_[0], ime_top_left_, ime_top_left_ + ime_size_);
+
+    DrawIme();
+  }
+}
+
+void DisplayView::UpdateReleaseFence() {
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  EGLSyncKHR sync =
+      eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
+  if (sync != EGL_NO_SYNC_KHR) {
+    // Need to flush in order to get the fence FD.
+    glFlush();
+    base::unique_fd fence(eglDupNativeFenceFDANDROID(display, sync));
+    eglDestroySyncKHR(display, sync);
+    release_fence_ = std::move(fence);
+  } else {
+    ALOGE("Failed to create sync fence");
+    release_fence_ = base::unique_fd();
+  }
+}
+
+void DisplayView::DrawIme() {
+  program_->Use();
+  glBindTexture(GL_TEXTURE_2D, ime_texture_.texture->id());
+
+  mat4 layer_transform = GetLayerTransform(ime_texture_, size_.x(), size_.y());
+
+  mat4 transform = initial_head_matrix_ * translate_ * ime_translate_ * scale_ *
+                   layer_transform;
+
+  DrawWithTransform(transform, *program_);
+}
+
+void DisplayView::DrawDimOverlay(const mat4& mvp, const TextureLayer& layer,
+                                 const vec2& top_left,
+                                 const vec2& bottom_right) {
+  overlay_program_->Use();
+  glUniformMatrix4fv(
+      glGetUniformLocation(overlay_program_->GetProgram(), "uViewProjection"),
+      1, 0, mvp.data());
+  glUniform4f(glGetUniformLocation(overlay_program_->GetProgram(), "uCoords"),
+              top_left.x() / size_.x(), top_left.y() / size_.y(),
+              bottom_right.x() / size_.x(), bottom_right.y() / size_.y());
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  mat4 layer_transform = GetLayerTransform(layer, size_.x(), size_.y());
+
+  mat4 transform = initial_head_matrix_ * translate_ * scale_ * layer_transform;
+  DrawWithTransform(transform, *overlay_program_);
+  glDisable(GL_BLEND);
+}
+
+void DisplayView::DrawWithTransform(const mat4& transform,
+                                    const ShaderProgram& program) {
+  GLint transform_location =
+      glGetUniformLocation(program.GetProgram(), "uTransform");
+  glUniformMatrix4fv(transform_location, 1, 0, transform.data());
+
+  glEnableVertexAttribArray(0);
+  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, kVertices);
+  glEnableVertexAttribArray(1);
+  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, kTextureVertices);
+  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+}
+
+bool DisplayView::UpdateHitInfo(const vec3& view_location,
+                                const vec3& view_direction,
+                                vec3* hit_location) {
+  bool is_hit = false;
+  if (has_ime_) {
+    // This will set allow_input_ and hit_location_in_window_coord_.
+    is_hit = IsImeHit(view_location, view_direction, hit_location);
+  } else {
+    is_hit = IsHit(view_location, view_direction, hit_location,
+                   &hit_location_in_window_coord_, false);
+    allow_input_ = is_hit;
+  }
+  return is_hit;
+}
+
+bool DisplayView::IsImeHit(const vec3& view_location,
+                           const vec3& view_direction, vec3* hit_location) {
+  // First, check if the IME window is hit.
+  bool is_hit = IsHit(view_location, view_direction, hit_location,
+                      &hit_location_in_window_coord_, true);
+  if (is_hit) {
+    // If it is, check if the window coordinate is in the IME region;
+    // if so then we are done.
+    if (IsInside(hit_location_in_window_coord_, ime_top_left_,
+                 ime_top_left_ + ime_size_)) {
+      allow_input_ = true;
+      return true;
+    }
+  }
+
+  allow_input_ = false;
+  // Check if we have hit the main window.
+  is_hit = IsHit(view_location, view_direction, hit_location,
+                 &hit_location_in_window_coord_, false);
+  if (is_hit) {
+    // Only allow input if we are not hitting the region hidden by the IME.
+    // Allowing input here would cause clicks on the main window to actually
+    // be clicks on the IME.
+    if (!IsInside(hit_location_in_window_coord_, ime_top_left_,
+                  ime_top_left_ + ime_size_)) {
+      allow_input_ = true;
+    }
+  }
+  return is_hit;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/services/vr/vr_window_manager/display_view.h b/services/vr/vr_window_manager/display_view.h
new file mode 100644
index 0000000..0a27781
--- /dev/null
+++ b/services/vr/vr_window_manager/display_view.h
@@ -0,0 +1,107 @@
+#ifndef VR_WINDOW_MANAGER_DISPLAY_VIEW_H_
+#define VR_WINDOW_MANAGER_DISPLAY_VIEW_H_
+
+#include <private/dvr/graphics/mesh.h>
+#include <private/dvr/graphics/shader_program.h>
+
+#include "hwc_callback.h"
+#include "surface_flinger_view.h"
+
+namespace android {
+namespace dvr {
+
+enum class ViewMode {
+  Hidden,
+  VR,
+  App,
+};
+
+class DisplayView {
+ public:
+  DisplayView(uint32_t id, int touchpad_id);
+  ~DisplayView();
+
+  // Calls to these 3 functions must be synchronized.
+  base::unique_fd OnFrame(std::unique_ptr<HwcCallback::Frame> frame,
+                          bool debug_mode, bool* showing);
+  void AdvanceFrame();
+  void UpdateReleaseFence();
+
+  void OnDrawFrame(SurfaceFlingerView* surface_flinger_view, bool debug_mode);
+  void DrawEye(EyeType eye, const mat4& perspective, const mat4& eye_matrix,
+               const mat4& head_matrix, const vec2& size, float fade_value);
+
+  void Recenter(const mat4& initial);
+
+  bool UpdateHitInfo(const vec3& view_location, const vec3& view_direction,
+                     vec3* hit_location);
+
+  void SetPrograms(ShaderProgram* program, ShaderProgram* overlay_program);
+
+  bool visible() const { return current_frame_.visibility != ViewMode::Hidden; }
+  bool allow_input() const { return allow_input_; }
+  const vec2& hit_location() const { return hit_location_in_window_coord_; }
+  uint32_t id() const { return id_; }
+  int touchpad_id() const { return touchpad_id_; }
+
+ private:
+  bool IsHit(const vec3& view_location, const vec3& view_direction,
+             vec3* hit_location, vec2* hit_location_in_window_coord,
+             bool test_ime);
+  bool IsImeHit(const vec3& view_location, const vec3& view_direction,
+                vec3* hit_location);
+  void DrawOverlays(const mat4& perspective, const mat4& eye_matrix,
+                    const mat4& head_matrix, float fade_value);
+  void DrawIme();
+  void DrawDimOverlay(const mat4& mvp, const TextureLayer& layer,
+                      const vec2& top_left, const vec2& bottom_right);
+  void DrawWithTransform(const mat4& transform, const ShaderProgram& program);
+
+  uint32_t id_;
+  int touchpad_id_;
+
+  uint32_t current_vr_app_;
+
+  ShaderProgram* program_;
+  ShaderProgram* overlay_program_;
+
+  mat4 initial_head_matrix_;
+  mat4 scale_;
+  mat4 translate_;
+  mat4 ime_translate_;
+  vec2 size_;
+
+  std::vector<TextureLayer> textures_;
+  TextureLayer ime_texture_;
+
+  bool allow_input_ = false;
+  vec2 hit_location_in_window_coord_;
+  vec2 ime_top_left_;
+  vec2 ime_size_;
+  bool has_ime_ = false;
+
+  struct PendingFrame {
+    PendingFrame() = default;
+    PendingFrame(std::unique_ptr<HwcCallback::Frame>&& frame,
+                 ViewMode visibility)
+        : frame(std::move(frame)), visibility(visibility) {}
+    PendingFrame(PendingFrame&& r)
+        : frame(std::move(r.frame)), visibility(r.visibility) {}
+
+    void operator=(PendingFrame&& r) {
+      frame.reset(r.frame.release());
+      visibility = r.visibility;
+    }
+
+    std::unique_ptr<HwcCallback::Frame> frame;
+    ViewMode visibility = ViewMode::Hidden;
+  };
+  std::deque<PendingFrame> pending_frames_;
+  PendingFrame current_frame_;
+  base::unique_fd release_fence_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // VR_WINDOW_MANAGER_DISPLAY_VIEW_H_
diff --git a/services/vr/vr_window_manager/hwc_callback.cpp b/services/vr/vr_window_manager/hwc_callback.cpp
index 05ec64a..b755c60 100644
--- a/services/vr/vr_window_manager/hwc_callback.cpp
+++ b/services/vr/vr_window_manager/hwc_callback.cpp
@@ -38,7 +38,8 @@
 HwcCallback::~HwcCallback() {
 }
 
-base::unique_fd HwcCallback::OnNewFrame(const ComposerView::Frame& frame) {
+base::unique_fd HwcCallback::OnNewFrame(const ComposerView::Frame& display_frame) {
+  auto& frame = display_frame.layers;
   std::vector<HwcLayer> hwc_frame(frame.size());
   for (size_t i = 0; i < frame.size(); ++i) {
     hwc_frame[i] = HwcLayer{
@@ -53,12 +54,13 @@
     };
   }
 
-  return client_->OnFrame(
-      std::make_unique<Frame>(std::move(hwc_frame)));
+  return client_->OnFrame(std::make_unique<Frame>(
+      std::move(hwc_frame), display_frame.display_id, display_frame.removed));
 }
 
-HwcCallback::Frame::Frame(std::vector<HwcLayer>&& layers)
-    : layers_(std::move(layers)) {}
+HwcCallback::Frame::Frame(std::vector<HwcLayer>&& layers, uint32_t display_id,
+                          bool removed)
+    : display_id_(display_id), removed_(removed), layers_(std::move(layers)) {}
 
 HwcCallback::FrameStatus HwcCallback::Frame::Finish() {
   if (status_ == FrameStatus::kUnfinished)
diff --git a/services/vr/vr_window_manager/hwc_callback.h b/services/vr/vr_window_manager/hwc_callback.h
index be56856..b8aa51b 100644
--- a/services/vr/vr_window_manager/hwc_callback.h
+++ b/services/vr/vr_window_manager/hwc_callback.h
@@ -81,12 +81,16 @@
 
   class Frame {
   public:
-    Frame(std::vector<HwcLayer>&& layers);
+    Frame(std::vector<HwcLayer>&& layers, uint32_t display_id, bool removed);
 
     FrameStatus Finish();
     const std::vector<HwcLayer>& layers() const { return layers_; }
+    uint32_t display_id() const { return display_id_; }
+    bool removed() const { return removed_; }
 
   private:
+    uint32_t display_id_;
+    bool removed_;
     std::vector<HwcLayer> layers_;
     FrameStatus status_ = FrameStatus::kUnfinished;
   };
diff --git a/services/vr/vr_window_manager/shell_view.cpp b/services/vr/vr_window_manager/shell_view.cpp
index abe9c60..72a2c26 100644
--- a/services/vr/vr_window_manager/shell_view.cpp
+++ b/services/vr/vr_window_manager/shell_view.cpp
@@ -15,12 +15,6 @@
 
 namespace {
 
-constexpr float kLayerScaleFactor = 4.0f;
-
-constexpr unsigned int kVRAppLayerCount = 2;
-
-constexpr unsigned int kMaximumPendingFrames = 8;
-
 const std::string kVertexShader = SHADER0([]() {
   layout(location = 0) in vec4 aPosition;
   layout(location = 1) in vec4 aTexCoord;
@@ -79,40 +73,6 @@
   void main() { fragColor = vec4(0.8, 0.2, 0.2, 1.0); }
 });
 
-const GLfloat kVertices[] = {
-  -1, -1, 0,
-   1, -1, 0,
-  -1,  1, 0,
-   1,  1, 0,
-};
-
-const GLfloat kTextureVertices[] = {
-  0, 1,
-  1, 1,
-  0, 0,
-  1, 0,
-};
-
-// Returns true if the given point is inside the given rect.
-bool IsInside(const vec2& pt, const vec2& tl, const vec2& br) {
-  return pt.x() >= tl.x() && pt.x() <= br.x() &&
-    pt.y() >= tl.y() && pt.y() <= br.y();
-}
-
-mat4 GetScalingMatrix(float width, float height) {
-  float xscale = 1, yscale = 1;
-  float ar = width / height;
-  if (ar > 1)
-    yscale = 1.0 / ar;
-  else
-    xscale = ar;
-
-  xscale *= kLayerScaleFactor;
-  yscale *= kLayerScaleFactor;
-
-  return mat4(Eigen::Scaling<float>(xscale, yscale, 1.0));
-}
-
 mat4 GetHorizontallyAlignedMatrixFromPose(const Posef& pose) {
   vec3 position = pose.GetPosition();
   quat view_quaternion = pose.GetRotation();
@@ -131,115 +91,16 @@
   m(3, 0) = 0.0f; m(3, 1) = 0.0f; m(3, 2) = 0.0f; m(3, 3) = 1.0f;
   // clang-format on
 
-  return m * Eigen::AngleAxisf(M_PI * 0.5f, vec3::UnitZ());
+  return m;
 }
 
-// Helper function that applies the crop transform to the texture layer and
-// positions (and scales) the texture layer in the appropriate location in the
-// display space.
-mat4 GetLayerTransform(const TextureLayer& texture_layer, float display_width,
-                       float display_height) {
-  // Map from vertex coordinates to [0, 1] coordinates:
-  //  1) Flip y since in vertex coordinates (-1, -1) is at the bottom left and
-  //     in texture coordinates (0, 0) is at the top left.
-  //  2) Translate by (1, 1) to map vertex coordinates to [0, 2] on x and y.
-  //  3) Scale by 1 / 2 to map coordinates to [0, 1] on x  and y.
-  mat4 unit_space(
-      Eigen::AlignedScaling3f(0.5f, 0.5f, 1.0f) *
-      Eigen::Translation3f(1.0f, 1.0f, 0.0f) *
-      Eigen::AlignedScaling3f(1.0f, -1.0f, 1.0f));
-
-  mat4 texture_space(Eigen::AlignedScaling3f(
-      texture_layer.texture->width(), texture_layer.texture->height(), 1.0f));
-
-  // 1) Translate the layer to crop the left and top edge.
-  // 2) Scale the layer such that the cropped right and bottom edges map outside
-  //    the exture region.
-  float crop_width = texture_layer.crop.right - texture_layer.crop.left;
-  float crop_height = texture_layer.crop.bottom - texture_layer.crop.top;
-  mat4 texture_crop(
-      Eigen::AlignedScaling3f(
-          texture_layer.texture->width() / crop_width,
-          texture_layer.texture->height() / crop_height,
-          1.0f) *
-      Eigen::Translation3f(
-          -texture_layer.crop.left, -texture_layer.crop.top, 0.0f));
-
-  mat4 display_space(
-      Eigen::AlignedScaling3f(display_width, display_height, 1.0f));
-
-  // 1) Scale the texture to fit the display frame.
-  // 2) Translate the texture in the display frame location.
-  float display_frame_width = texture_layer.display_frame.right -
-      texture_layer.display_frame.left;
-  float display_frame_height = texture_layer.display_frame.bottom -
-      texture_layer.display_frame.top;
-  mat4 display_frame(
-      Eigen::Translation3f(
-          texture_layer.display_frame.left,
-          texture_layer.display_frame.top,
-          0.0f) *
-      Eigen::AlignedScaling3f(
-          display_frame_width / display_width,
-          display_frame_height / display_height,
-          1.0f));
-
-  mat4 layer_transform = unit_space.inverse() * display_space.inverse() *
-      display_frame * display_space * texture_space.inverse() * texture_crop *
-      texture_space * unit_space;
-  return layer_transform;
+int GetTouchIdForDisplay(uint32_t display) {
+  return display == 1 ? VirtualTouchpad::PRIMARY : VirtualTouchpad::VIRTUAL;
 }
 
-// Determine if ths frame should be shown or hidden.
-ViewMode CalculateVisibilityFromLayerConfig(const HwcCallback::Frame& frame,
-                                            uint32_t vr_app) {
-  auto& layers = frame.layers();
-
-  // TODO(achaulk): Figure out how to identify the current VR app for 2D app
-  // detection.
-
-  size_t index;
-  // Skip all layers that we don't know about.
-  for (index = 0; index < layers.size(); index++) {
-    if (layers[index].type != 0xFFFFFFFF && layers[index].type != 0)
-      break;
-  }
-
-  if (index == layers.size())
-    return ViewMode::Hidden;
-
-  if (layers[index].type != 1) {
-    // We don't have a VR app layer? Abort.
-    return ViewMode::Hidden;
-  }
-
-  // This is the VR app, ignore it.
-  index++;
-
-  // Now, find a dim layer if it exists.
-  // If it does, ignore any layers behind it for visibility determination.
-  for (size_t i = index; i < layers.size(); i++) {
-    if (layers[i].appid == HwcCallback::HwcLayer::kSurfaceFlingerLayer) {
-      index = i + 1;
-    }
-  }
-
-  // If any non-skipped layers exist now then we show, otherwise hide.
-  for (size_t i = index; i < layers.size(); i++) {
-    if (!layers[i].should_skip_layer())
-      return ViewMode::VR;
-  }
-  return ViewMode::Hidden;
-}
-
-
 }  // namespace
 
-ShellView::ShellView() {
-  ime_translate_ = mat4(Eigen::Translation3f(0.0f, -0.5f, 0.25f));
-  ime_top_left_ = vec2(0, 0);
-  ime_size_ = vec2(0, 0);
-}
+ShellView::ShellView() {}
 
 ShellView::~ShellView() {}
 
@@ -248,8 +109,6 @@
   if (ret)
     return ret;
 
-  translate_ = Eigen::Translation3f(0, 0, -2.5f);
-
   virtual_touchpad_ = VirtualTouchpadClient::Create();
   const status_t touchpad_status = virtual_touchpad_->Attach();
   if (touchpad_status != OK) {
@@ -286,6 +145,9 @@
   controller_mesh_->SetVertices(kNumControllerMeshVertices,
                                 kControllerMeshVertices);
 
+  for (auto& display : displays_)
+    display->SetPrograms(program_.get(), overlay_program_.get());
+
   initialized_ = true;
 
   return 0;
@@ -302,13 +164,13 @@
 }
 
 void ShellView::EnableDebug(bool debug) {
-  ALOGI("EnableDebug(%d)", (int)debug); // XXX TODO delete
+  ALOGI("EnableDebug(%d)", (int)debug);  // XXX TODO delete
   QueueTask(debug ? MainThreadTask::EnableDebugMode
                   : MainThreadTask::DisableDebugMode);
 }
 
 void ShellView::VrMode(bool mode) {
-  ALOGI("VrMode(%d)", (int)mode); // XXX TODO delete
+  ALOGI("VrMode(%d)", (int)mode);  // XXX TODO delete
   QueueTask(mode ? MainThreadTask::EnteringVrMode
                  : MainThreadTask::ExitingVrMode);
 }
@@ -320,63 +182,114 @@
   result.appendFormat("debug_mode = %s\n\n", debug_mode_ ? "true" : "false");
 }
 
-void ShellView::AdvanceFrame() {
-  if (!pending_frames_.empty()) {
-    // Check if we should advance the frame.
-    auto& frame = pending_frames_.front();
-    if (frame.visibility == ViewMode::Hidden ||
-        frame.frame->Finish() == HwcCallback::FrameStatus::kFinished) {
-      current_frame_ = std::move(frame);
-      pending_frames_.pop_front();
-    }
-  }
-}
-
 void ShellView::OnDrawFrame() {
-  textures_.clear();
-  has_ime_ = false;
+  bool visible = false;
 
   {
-    std::unique_lock<std::mutex> l(pending_frame_mutex_);
-    AdvanceFrame();
-  }
+    std::unique_lock<std::mutex> l(display_frame_mutex_);
 
-  bool visible = current_frame_.visibility != ViewMode::Hidden;
+    // Move any new displays into the list.
+    if (!new_displays_.empty()) {
+      for (auto& display : new_displays_) {
+        display->Recenter(GetHorizontallyAlignedMatrixFromPose(last_pose_));
+        display->SetPrograms(program_.get(), overlay_program_.get());
+        displays_.emplace_back(display.release());
+      }
+      new_displays_.clear();
+    }
+
+    // Remove any old displays from the list now.
+    if (!removed_displays_.empty()) {
+      for (auto& display : removed_displays_) {
+        displays_.erase(std::find_if(
+            displays_.begin(), displays_.end(),
+            [display](auto& ptr) { return display == ptr.get(); }));
+      }
+      removed_displays_.clear();
+    }
+
+    for (auto& display : displays_) {
+      display->AdvanceFrame();
+      visible = visible || display->visible();
+    }
+  }
 
   if (!debug_mode_ && visible != is_visible_) {
-    SetVisibility(current_frame_.visibility != ViewMode::Hidden);
+    SetVisibility(visible);
   }
 
-  if (!debug_mode_ && !visible)
-    return;
-
-  ime_texture_ = TextureLayer();
-
-  surface_flinger_view_->GetTextures(*current_frame_.frame.get(), &textures_,
-                                     &ime_texture_, debug_mode_,
-                                     current_frame_.visibility == ViewMode::VR);
-  has_ime_ = ime_texture_.texture != nullptr;
+  for (auto& display : displays_) {
+    display->OnDrawFrame(surface_flinger_view_.get(), debug_mode_);
+  }
 }
 
-void ShellView::DrawEye(EyeType /* eye */, const mat4& perspective,
+void ShellView::OnEndFrame() {
+  std::unique_lock<std::mutex> l(display_frame_mutex_);
+  for (auto& display : displays_) {
+    display->UpdateReleaseFence();
+  }
+}
+
+DisplayView* ShellView::FindOrCreateDisplay(uint32_t id) {
+  for (auto& display : displays_) {
+    if (display->id() == id) {
+      return display.get();
+    }
+  }
+
+  // It might be pending addition.
+  for (auto& display : new_displays_) {
+    if (display->id() == id) {
+      return display.get();
+    }
+  }
+
+  auto display = new DisplayView(id, GetTouchIdForDisplay(id));
+  new_displays_.emplace_back(display);
+  return display;
+}
+
+base::unique_fd ShellView::OnFrame(std::unique_ptr<HwcCallback::Frame> frame) {
+  std::unique_lock<std::mutex> l(display_frame_mutex_);
+  DisplayView* display = FindOrCreateDisplay(frame->display_id());
+
+  if (frame->removed()) {
+    removed_displays_.push_back(display);
+    return base::unique_fd();
+  }
+
+  bool showing = false;
+
+  base::unique_fd fd(display->OnFrame(std::move(frame), debug_mode_, &showing));
+
+  if (showing)
+    QueueTask(MainThreadTask::Show);
+
+  return fd;
+}
+
+void ShellView::DrawEye(EyeType eye, const mat4& perspective,
                         const mat4& eye_matrix, const mat4& head_matrix) {
-  if (should_recenter_) {
+  if (should_recenter_ && !displays_.empty()) {
     // Position the quad horizontally aligned in the direction the user
     // is facing, effectively taking out head roll.
-    initial_head_matrix_ = GetHorizontallyAlignedMatrixFromPose(last_pose_);
+    displays_[0]->Recenter(GetHorizontallyAlignedMatrixFromPose(last_pose_));
     should_recenter_ = false;
   }
 
   size_ = vec2(surface_flinger_view_->width(), surface_flinger_view_->height());
-  scale_ = GetScalingMatrix(size_.x(), size_.y());
-
-  DrawOverlays(perspective, eye_matrix, head_matrix);
 
   // TODO(alexst): Replicate controller rendering from VR Home.
   // Current approach in the function below is a quick visualization.
   DrawController(perspective, eye_matrix, head_matrix);
 
-  // TODO: Make sure reticle is shown only over visible overlays.
+  for (auto& display : displays_) {
+    if (display->visible()) {
+      display->DrawEye(eye, perspective, eye_matrix, head_matrix, size_,
+                       fade_value_);
+    }
+  }
+
   DrawReticle(perspective, eye_matrix, head_matrix);
 }
 
@@ -387,7 +300,7 @@
 
 bool ShellView::OnClick(bool down) {
   if (down) {
-    if (!is_touching_ && allow_input_) {
+    if (!is_touching_ && active_display_ && active_display_->allow_input()) {
       is_touching_ = true;
     }
   } else {
@@ -397,224 +310,6 @@
   return true;
 }
 
-base::unique_fd ShellView::OnFrame(std::unique_ptr<HwcCallback::Frame> frame) {
-  ViewMode visibility =
-      CalculateVisibilityFromLayerConfig(*frame.get(), current_vr_app_);
-
-  if (visibility == ViewMode::Hidden && debug_mode_)
-    visibility = ViewMode::VR;
-
-  if (frame->layers().empty())
-    current_vr_app_ = 0;
-  else
-    current_vr_app_ = frame->layers().front().appid;
-
-  std::unique_lock<std::mutex> l(pending_frame_mutex_);
-
-  pending_frames_.emplace_back(std::move(frame), visibility);
-
-  if (pending_frames_.size() > kMaximumPendingFrames) {
-    pending_frames_.pop_front();
-  }
-
-  if (visibility == ViewMode::Hidden &&
-      current_frame_.visibility == ViewMode::Hidden) {
-    // Consume all frames while hidden.
-    while (!pending_frames_.empty())
-      AdvanceFrame();
-  }
-
-  // If we are showing ourselves the main thread is not processing anything,
-  // so give it a kick.
-  if (visibility != ViewMode::Hidden &&
-      current_frame_.visibility == ViewMode::Hidden) {
-    QueueTask(MainThreadTask::Show);
-  }
-
-  return base::unique_fd(dup(release_fence_.get()));
-}
-
-bool ShellView::IsHit(const vec3& view_location, const vec3& view_direction,
-                      vec3* hit_location, vec2* hit_location_in_window_coord,
-                      bool test_ime) {
-  mat4 m = initial_head_matrix_ * translate_;
-  if (test_ime)
-    m = m * ime_translate_;
-  mat4 inverse = (m * scale_).inverse();
-  vec4 transformed_loc =
-      inverse * vec4(view_location[0], view_location[1], view_location[2], 1);
-  vec4 transformed_dir = inverse * vec4(view_direction[0], view_direction[1],
-                                        view_direction[2], 0);
-
-  if (transformed_dir.z() >= 0 || transformed_loc.z() <= 0)
-    return false;
-
-  float distance = -transformed_loc.z() / transformed_dir.z();
-  vec4 transformed_hit_loc = transformed_loc + transformed_dir * distance;
-  if (transformed_hit_loc.x() < -1 || transformed_hit_loc.x() > 1)
-    return false;
-  if (transformed_hit_loc.y() < -1 || transformed_hit_loc.y() > 1)
-    return false;
-
-  hit_location_in_window_coord->x() =
-      (1 + transformed_hit_loc.x()) / 2 * size_.x();
-  hit_location_in_window_coord->y() =
-      (1 - transformed_hit_loc.y()) / 2 * size_.y();
-
-  *hit_location = view_location + view_direction * distance;
-  return true;
-}
-
-void ShellView::DrawOverlays(const mat4& perspective, const mat4& eye_matrix,
-                             const mat4& head_matrix) {
-  if (textures_.empty())
-    return;
-
-  program_->Use();
-  mat4 mvp = perspective * eye_matrix * head_matrix;
-  GLint view_projection_location =
-      glGetUniformLocation(program_->GetProgram(), "uViewProjection");
-  glUniformMatrix4fv(view_projection_location, 1, 0, mvp.data());
-
-  GLint alpha_location =
-      glGetUniformLocation(program_->GetProgram(), "uAlpha");
-
-  GLint tex_location = glGetUniformLocation(program_->GetProgram(), "tex");
-  glUniform1i(tex_location, 0);
-  glActiveTexture(GL_TEXTURE0);
-
-  for (const auto& texture_layer : textures_) {
-    switch (texture_layer.blending) {
-      case HWC2_BLEND_MODE_PREMULTIPLIED:
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-        break;
-      case HWC2_BLEND_MODE_COVERAGE:
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        break;
-      default:
-        break;
-    }
-
-    glUniform1f(alpha_location, fade_value_ * texture_layer.alpha);
-
-    glBindTexture(GL_TEXTURE_2D, texture_layer.texture->id());
-
-    mat4 layer_transform = GetLayerTransform(texture_layer, size_.x(),
-                                             size_.y());
-
-    mat4 transform = initial_head_matrix_ * translate_ * scale_ *
-        layer_transform;
-    DrawWithTransform(transform, *program_);
-
-    glDisable(GL_BLEND);
-  }
-
-  if (has_ime_) {
-    ime_top_left_ = vec2(static_cast<float>(ime_texture_.display_frame.left),
-                         static_cast<float>(ime_texture_.display_frame.top));
-    ime_size_ = vec2(static_cast<float>(ime_texture_.display_frame.right -
-                                        ime_texture_.display_frame.left),
-                     static_cast<float>(ime_texture_.display_frame.bottom -
-                                        ime_texture_.display_frame.top));
-
-    DrawDimOverlay(mvp, textures_[0], ime_top_left_, ime_top_left_ + ime_size_);
-
-    DrawIme();
-  }
-
-  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-  EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID,
-                                     nullptr);
-  if (sync != EGL_NO_SYNC_KHR) {
-    // Need to flush in order to get the fence FD.
-    glFlush();
-    base::unique_fd fence(eglDupNativeFenceFDANDROID(display, sync));
-    eglDestroySyncKHR(display, sync);
-    UpdateReleaseFence(std::move(fence));
-  } else {
-    ALOGE("Failed to create sync fence");
-    UpdateReleaseFence(base::unique_fd());
-  }
-}
-
-void ShellView::DrawIme() {
-  program_->Use();
-  glBindTexture(GL_TEXTURE_2D, ime_texture_.texture->id());
-
-  mat4 layer_transform = GetLayerTransform(ime_texture_, size_.x(), size_.y());
-
-  mat4 transform = initial_head_matrix_ * translate_ * ime_translate_ * scale_ *
-              layer_transform;
-
-  DrawWithTransform(transform, *program_);
-}
-
-void ShellView::DrawDimOverlay(const mat4& mvp, const TextureLayer& layer, const vec2& top_left,
-                    const vec2& bottom_right) {
-  overlay_program_->Use();
-  glUniformMatrix4fv(
-      glGetUniformLocation(overlay_program_->GetProgram(), "uViewProjection"),
-      1, 0, mvp.data());
-  glUniform4f(glGetUniformLocation(overlay_program_->GetProgram(), "uCoords"),
-              top_left.x() / size_.x(), top_left.y() / size_.y(),
-              bottom_right.x() / size_.x(), bottom_right.y() / size_.y());
-  glEnable(GL_BLEND);
-  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-  mat4 layer_transform =
-      GetLayerTransform(layer, size_.x(), size_.y());
-
-  mat4 transform =
-      initial_head_matrix_ * translate_ * scale_ * layer_transform;
-  DrawWithTransform(transform, *overlay_program_);
-  glDisable(GL_BLEND);
-}
-
-void ShellView::DrawWithTransform(const mat4& transform,
-                                  const ShaderProgram& program) {
-  GLint transform_location =
-      glGetUniformLocation(program.GetProgram(), "uTransform");
-  glUniformMatrix4fv(transform_location, 1, 0, transform.data());
-
-  glEnableVertexAttribArray(0);
-  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, kVertices);
-  glEnableVertexAttribArray(1);
-  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, kTextureVertices);
-  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-}
-
-bool ShellView::IsImeHit(const vec3& view_location, const vec3& view_direction,
-                vec3 *hit_location) {
-  // First, check if the IME window is hit.
-  bool is_hit = IsHit(view_location, view_direction, hit_location,
-                      &hit_location_in_window_coord_, true);
-  if (is_hit) {
-    // If it is, check if the window coordinate is in the IME region;
-    // if so then we are done.
-    if (IsInside(hit_location_in_window_coord_, ime_top_left_,
-                 ime_top_left_ + ime_size_)) {
-      allow_input_ = true;
-      return true;
-    }
-  }
-
-  allow_input_ = false;
-  // Check if we have hit the main window.
-  is_hit = IsHit(view_location, view_direction, hit_location,
-                 &hit_location_in_window_coord_, false);
-  if (is_hit) {
-    // Only allow input if we are not hitting the region hidden by the IME.
-    // Allowing input here would cause clicks on the main window to actually
-    // be clicks on the IME.
-    if (!IsInside(hit_location_in_window_coord_, ime_top_left_,
-                  ime_top_left_ + ime_size_)) {
-      allow_input_ = true;
-    }
-  }
-  return is_hit;
-}
-
 void ShellView::DrawReticle(const mat4& perspective, const mat4& eye_matrix,
                             const mat4& head_matrix) {
   reticle_->Hide();
@@ -653,30 +348,47 @@
     }
   }
 
-  vec3 view_direction = vec3(view_quaternion * vec3(0, 0, -1));
-
   vec3 hit_location;
+  active_display_ =
+      FindActiveDisplay(pointer_location, view_quaternion, &hit_location);
 
-  bool is_hit;
-  if(has_ime_) {
-    // This will set allow_input_ and hit_location_in_window_coord_.
-    is_hit = IsImeHit(pointer_location, view_direction, &hit_location);
-  } else {
-    is_hit = IsHit(pointer_location, view_direction, &hit_location,
-                   &hit_location_in_window_coord_, false);
-    allow_input_ = is_hit;
-  }
-
-  if (is_hit) {
+  if (active_display_) {
     reticle_->ShowAt(
         Eigen::Translation3f(hit_location) * view_quaternion.matrix(),
-        allow_input_ ? vec3(1, 0, 0) : vec3(0, 0, 0));
+        active_display_->allow_input() ? vec3(1, 0, 0) : vec3(0, 0, 0));
     Touch();
   }
 
   reticle_->Draw(perspective, eye_matrix, head_matrix);
 }
 
+DisplayView* ShellView::FindActiveDisplay(const vec3& position,
+                                          const quat& quaternion,
+                                          vec3* hit_location) {
+  vec3 direction = vec3(quaternion * vec3(0, 0, -1));
+  vec3 temp_hit;
+
+  DisplayView* best_display = nullptr;
+  vec3 best_hit;
+
+  auto is_better = [&best_hit, &position](DisplayView*, const vec3& hit) {
+    return (hit - position).squaredNorm() < (best_hit - position).squaredNorm();
+  };
+
+  for (auto& display : displays_) {
+    if (display->UpdateHitInfo(position, direction, &temp_hit)) {
+      if (!best_display || is_better(display.get(), temp_hit)) {
+        best_display = display.get();
+        best_hit = temp_hit;
+      }
+    }
+  }
+
+  if (best_display)
+    *hit_location = best_hit;
+  return best_display;
+}
+
 void ShellView::DrawController(const mat4& perspective, const mat4& eye_matrix,
                                const mat4& head_matrix) {
   if (!shmem_controller_active_)
@@ -712,10 +424,16 @@
     return;
   }
 
+  if (!active_display_)
+    return;
+
+  const vec2& hit_location = active_display_->hit_location();
+
+  // Device is portrait, but in landscape when in VR.
+  // Rotate touch input appropriately.
   const android::status_t status = virtual_touchpad_->Touch(
-      VirtualTouchpad::PRIMARY,
-      hit_location_in_window_coord_.x() / size_.x(),
-      hit_location_in_window_coord_.y() / size_.y(),
+      active_display_->touchpad_id(),
+      1.0f - hit_location.y() / size_.y(), hit_location.x() / size_.x(),
       is_touching_ ? 1.0f : 0.0f);
   if (status != OK) {
     ALOGE("touch failed: %d", status);
@@ -725,7 +443,7 @@
 bool ShellView::OnTouchpadButton(bool down, int button) {
   int buttons = touchpad_buttons_;
   if (down) {
-    if (allow_input_) {
+    if (active_display_ && active_display_->allow_input()) {
       buttons |= button;
     }
   } else {
@@ -740,18 +458,16 @@
     return false;
   }
 
+  if (!active_display_)
+    return true;
+
   const android::status_t status = virtual_touchpad_->ButtonState(
-      VirtualTouchpad::PRIMARY, touchpad_buttons_);
+      active_display_->touchpad_id(), touchpad_buttons_);
   if (status != OK) {
     ALOGE("touchpad button failed: %d %d", touchpad_buttons_, status);
   }
   return true;
 }
 
-void ShellView::UpdateReleaseFence(base::unique_fd fence) {
-  std::lock_guard<std::mutex> guard(pending_frame_mutex_);
-  release_fence_ = std::move(fence);
-}
-
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/vr_window_manager/shell_view.h b/services/vr/vr_window_manager/shell_view.h
index 49456c6..c10bd27 100644
--- a/services/vr/vr_window_manager/shell_view.h
+++ b/services/vr/vr_window_manager/shell_view.h
@@ -8,6 +8,7 @@
 
 #include "VirtualTouchpadClient.h"
 #include "application.h"
+#include "display_view.h"
 #include "reticle.h"
 #include "shell_view_binder_interface.h"
 #include "surface_flinger_view.h"
@@ -15,12 +16,6 @@
 namespace android {
 namespace dvr {
 
-enum class ViewMode {
-  Hidden,
-  VR,
-  App,
-};
-
 class ShellView : public Application,
                   public android::dvr::ShellViewBinderInterface,
                   public HwcCallback::Client {
@@ -41,91 +36,54 @@
  protected:
   void DrawEye(EyeType eye, const mat4& perspective, const mat4& eye_matrix,
                const mat4& head_matrix) override;
+  void OnDrawFrame() override;
+  void OnEndFrame() override;
   void OnVisibilityChanged(bool visible) override;
 
-  void DrawOverlays(const mat4& perspective, const mat4& eye_matrix,
-                    const mat4& head_matrix);
   void DrawReticle(const mat4& perspective, const mat4& eye_matrix,
                    const mat4& head_matrix);
-  void DrawIme();
-  void DrawDimOverlay(const mat4& mvp, const TextureLayer& layer,
-                      const vec2& top_left, const vec2& bottom_right);
   void DrawController(const mat4& perspective, const mat4& eye_matrix,
                       const mat4& head_matrix);
 
-  bool IsHit(const vec3& view_location, const vec3& view_direction,
-             vec3* hit_location, vec2* hit_location_in_window_coord,
-             bool test_ime);
-  bool IsImeHit(const vec3& view_location, const vec3& view_direction,
-                vec3 *hit_location);
   void Touch();
   bool OnTouchpadButton(bool down, int button);
 
-  void OnDrawFrame() override;
-  void DrawWithTransform(const mat4& transform, const ShaderProgram& program);
-
   bool OnClick(bool down);
 
-  void AdvanceFrame();
+  DisplayView* FindActiveDisplay(const vec3& position, const quat& quaternion,
+                                 vec3* hit_location);
 
-  void UpdateReleaseFence(base::unique_fd fence);
 
   // HwcCallback::Client:
   base::unique_fd OnFrame(std::unique_ptr<HwcCallback::Frame> frame) override;
+  DisplayView* FindOrCreateDisplay(uint32_t id);
 
   std::unique_ptr<ShaderProgram> program_;
   std::unique_ptr<ShaderProgram> overlay_program_;
   std::unique_ptr<ShaderProgram> controller_program_;
 
-  uint32_t current_vr_app_;
-
-  // Used to center the scene when the shell becomes visible.
-  bool should_recenter_ = true;
-  mat4 initial_head_matrix_;
-  mat4 scale_;
-  mat4 translate_;
-  mat4 ime_translate_;
-  vec2 size_;
-
   std::unique_ptr<SurfaceFlingerView> surface_flinger_view_;
   std::unique_ptr<Reticle> reticle_;
   sp<VirtualTouchpad> virtual_touchpad_;
-  std::vector<TextureLayer> textures_;
-  TextureLayer ime_texture_;
-
-  bool is_touching_ = false;
-  bool allow_input_ = false;
-  int touchpad_buttons_ = 0;
-  vec2 hit_location_in_window_coord_;
-  vec2 ime_top_left_;
-  vec2 ime_size_;
-  bool has_ime_ = false;
 
   std::unique_ptr<Mesh<vec3, vec3, vec2>> controller_mesh_;
 
-  struct PendingFrame {
-    PendingFrame() = default;
-    PendingFrame(std::unique_ptr<HwcCallback::Frame>&& frame, ViewMode visibility)
-        : frame(std::move(frame)), visibility(visibility) {}
-    PendingFrame(PendingFrame&& r)
-        : frame(std::move(r.frame)), visibility(r.visibility) {}
+  bool is_touching_ = false;
+  int touchpad_buttons_ = 0;
+  vec2 size_;
 
-    void operator=(PendingFrame&& r) {
-      frame.reset(r.frame.release());
-      visibility = r.visibility;
-    }
+  // Used to center the scene when the shell becomes visible.
+  bool should_recenter_ = true;
 
-    std::unique_ptr<HwcCallback::Frame> frame;
-    ViewMode visibility = ViewMode::Hidden;
-  };
-  std::deque<PendingFrame> pending_frames_;
-  std::mutex pending_frame_mutex_;
-  PendingFrame current_frame_;
+  std::mutex display_frame_mutex_;
+
+  std::vector<std::unique_ptr<DisplayView>> displays_;
+  std::vector<std::unique_ptr<DisplayView>> new_displays_;
+  std::vector<DisplayView*> removed_displays_;
+  DisplayView* active_display_ = nullptr;
 
   mat4 controller_translate_;
 
-  base::unique_fd release_fence_;
-
   ShellView(const ShellView&) = delete;
   void operator=(const ShellView&) = delete;
 };