Add type to DisplayViewport

Refactor DisplayViewport to treat internal, external, and virtual
display viewports more equally. Now stored in single array, and mLocked
contains the most up-to-date copy of these. Still enforce the
requirement that there can only be 1 internal and 1 external viewport.
We should remove this requirement in the future.

Bug: 111108021
Test: atest DisplayManagerServiceTest
Change-Id: I6fca9725f6e4180063c86f3c2bc34906ac1d0024
diff --git a/core/java/android/hardware/display/DisplayViewport.java b/core/java/android/hardware/display/DisplayViewport.java
index 496f34c..df0d46b 100644
--- a/core/java/android/hardware/display/DisplayViewport.java
+++ b/core/java/android/hardware/display/DisplayViewport.java
@@ -16,9 +16,14 @@
 
 package android.hardware.display;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.graphics.Rect;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+
 /**
  * Describes how the pixels of physical display device reflects the content of
  * a logical display.
@@ -35,6 +40,10 @@
     public static final int VIEWPORT_INTERNAL = 1;
     public static final int VIEWPORT_EXTERNAL = 2;
     public static final int VIEWPORT_VIRTUAL = 3;
+    @IntDef(prefix = { "VIEWPORT_" }, value = {
+            VIEWPORT_INTERNAL, VIEWPORT_EXTERNAL, VIEWPORT_VIRTUAL})
+    @Retention(SOURCE)
+    public @interface ViewportType {};
 
     // True if this viewport is valid.
     public boolean valid;
@@ -62,6 +71,8 @@
     // The ID used to uniquely identify this display.
     public String uniqueId;
 
+    public @ViewportType int type;
+
     public void copyFrom(DisplayViewport viewport) {
         valid = viewport.valid;
         displayId = viewport.displayId;
@@ -71,6 +82,7 @@
         deviceWidth = viewport.deviceWidth;
         deviceHeight = viewport.deviceHeight;
         uniqueId = viewport.uniqueId;
+        type = viewport.type;
     }
 
     /**
@@ -100,7 +112,8 @@
               && physicalFrame.equals(other.physicalFrame)
               && deviceWidth == other.deviceWidth
               && deviceHeight == other.deviceHeight
-              && TextUtils.equals(uniqueId, other.uniqueId);
+              && TextUtils.equals(uniqueId, other.uniqueId)
+              && type == other.type;
     }
 
     @Override
@@ -115,13 +128,15 @@
         result += prime * result + deviceWidth;
         result += prime * result + deviceHeight;
         result += prime * result + uniqueId.hashCode();
+        result += prime * result + type;
         return result;
     }
 
     // For debugging purposes.
     @Override
     public String toString() {
-        return "DisplayViewport{valid=" + valid
+        return "DisplayViewport{type=" + typeToString(type)
+                + ", valid=" + valid
                 + ", displayId=" + displayId
                 + ", uniqueId='" + uniqueId + "'"
                 + ", orientation=" + orientation
@@ -131,4 +146,20 @@
                 + ", deviceHeight=" + deviceHeight
                 + "}";
     }
+
+    /**
+     * Human-readable viewport type.
+     */
+    public static String typeToString(@ViewportType int viewportType) {
+        switch (viewportType) {
+            case VIEWPORT_INTERNAL:
+                return "INTERNAL";
+            case VIEWPORT_EXTERNAL:
+                return "EXTERNAL";
+            case VIEWPORT_VIRTUAL:
+                return "VIRTUAL";
+            default:
+                return "UNKNOWN (" + viewportType + ")";
+        }
+    }
 }
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index c4d7e40..d8da548 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -40,8 +40,7 @@
      * Called by the display manager to set information about the displays as needed
      * by the input system.  The input system must copy this information to retain it.
      */
-    public abstract void setDisplayViewports(DisplayViewport defaultViewport,
-            DisplayViewport externalTouchViewport, List<DisplayViewport> virtualTouchViewports);
+    public abstract void setDisplayViewports(List<DisplayViewport> viewports);
 
     /**
      * Called by the power manager to tell the input manager whether it should start
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index ab8e685..05f6556 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -40,6 +40,7 @@
     jfieldID deviceWidth;
     jfieldID deviceHeight;
     jfieldID uniqueId;
+    jfieldID type;
 } gDisplayViewportClassInfo;
 
 static struct {
@@ -64,6 +65,9 @@
         viewport->uniqueId = ScopedUtfChars(env, uniqueId).c_str();
     }
 
+    viewport->type = static_cast<ViewportType>(env->GetIntField(viewportObj,
+                gDisplayViewportClassInfo.type));
+
     jobject logicalFrameObj =
             env->GetObjectField(viewportObj, gDisplayViewportClassInfo.logicalFrame);
     viewport->logicalLeft = env->GetIntField(logicalFrameObj, gRectClassInfo.left);
@@ -108,6 +112,9 @@
     gDisplayViewportClassInfo.uniqueId = GetFieldIDOrDie(env,
             gDisplayViewportClassInfo.clazz, "uniqueId", "Ljava/lang/String;");
 
+    gDisplayViewportClassInfo.type = GetFieldIDOrDie(env,
+            gDisplayViewportClassInfo.clazz, "type", "I");
+
     clazz = FindClassOrDie(env, "android/graphics/Rect");
     gRectClassInfo.left = GetFieldIDOrDie(env, clazz, "left", "I");
     gRectClassInfo.top = GetFieldIDOrDie(env, clazz, "top", "I");
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 2405925..7bfe9ce 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -19,7 +19,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
-import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -224,6 +223,8 @@
         DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
         viewport.deviceWidth = isRotated ? info.height : info.width;
         viewport.deviceHeight = isRotated ? info.width : info.height;
+
+        viewport.uniqueId = info.uniqueId;
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0eff7f5..e70460a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -17,15 +17,14 @@
 package com.android.server.display;
 
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
-import static android.hardware.display.DisplayManager
-        .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.IndentingPrintWriter;
+import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
+import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
+import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -45,8 +44,8 @@
 import android.hardware.display.Curve;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayViewport;
 import android.hardware.display.IDisplayManager;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -83,14 +82,17 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.internal.util.Preconditions;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
-import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.SurfaceAnimationThread;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -256,9 +258,8 @@
 
     // Viewports of the default display and the display that should receive touch
     // input from an external source.  Used by the input system.
-    private final DisplayViewport mDefaultViewport = new DisplayViewport();
-    private final DisplayViewport mExternalTouchViewport = new DisplayViewport();
-    private final ArrayList<DisplayViewport> mVirtualTouchViewports = new ArrayList<>();
+    @GuardedBy("mSyncRoot")
+    private final ArrayList<DisplayViewport> mViewports = new ArrayList<>();
 
     // Persistent data store for all internal settings maintained by the display manager service.
     private final PersistentDataStore mPersistentDataStore = new PersistentDataStore();
@@ -272,9 +273,7 @@
 
     // Temporary viewports, used when sending new viewport information to the
     // input system.  May be used outside of the lock but only on the handler thread.
-    private final DisplayViewport mTempDefaultViewport = new DisplayViewport();
-    private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport();
-    private final ArrayList<DisplayViewport> mTempVirtualTouchViewports = new ArrayList<>();
+    private final ArrayList<DisplayViewport> mTempViewports = new ArrayList<>();
 
     // The default color mode for default displays. Overrides the usual
     // Display.Display.COLOR_MODE_DEFAULT for displays with the
@@ -1255,9 +1254,7 @@
     }
 
     private void clearViewportsLocked() {
-        mDefaultViewport.valid = false;
-        mExternalTouchViewport.valid = false;
-        mVirtualTouchViewports.clear();
+        mViewports.clear();
     }
 
     private void configureDisplayLocked(SurfaceControl.Transaction t, DisplayDevice device) {
@@ -1287,40 +1284,89 @@
         }
         display.configureDisplayLocked(t, device, info.state == Display.STATE_OFF);
 
-        // Update the viewports if needed.
-        if (!mDefaultViewport.valid
-                && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
-            setViewportLocked(mDefaultViewport, display, device);
+        // Update the corresponding viewport.
+        DisplayViewport internalViewport = getInternalViewportLocked();
+        if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
+            populateViewportLocked(internalViewport, display, device);
         }
-        if (!mExternalTouchViewport.valid
-                && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
-            setViewportLocked(mExternalTouchViewport, display, device);
+        DisplayViewport externalViewport = getExternalViewportLocked();
+        if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
+            populateViewportLocked(externalViewport, display, device);
+        } else if (!externalViewport.valid) {
+            // TODO (b/116850516) move this logic into InputReader
+            externalViewport.copyFrom(internalViewport);
+            externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL;
         }
 
         if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL && !TextUtils.isEmpty(info.uniqueId)) {
-            final DisplayViewport viewport = getVirtualTouchViewportLocked(info.uniqueId);
-            setViewportLocked(viewport, display, device);
+            final DisplayViewport viewport = getVirtualViewportLocked(info.uniqueId);
+            populateViewportLocked(viewport, display, device);
         }
     }
 
-    /** Gets the virtual device viewport or creates it if not yet created. */
-    private DisplayViewport getVirtualTouchViewportLocked(@NonNull String uniqueId) {
+    /** Get the virtual device viewport that has the specified uniqueId.
+     * If such viewport does not exist, create it. */
+    private DisplayViewport getVirtualViewportLocked(@NonNull String uniqueId) {
         DisplayViewport viewport;
-        final int count = mVirtualTouchViewports.size();
+        final int count = mViewports.size();
         for (int i = 0; i < count; i++) {
-            viewport = mVirtualTouchViewports.get(i);
+            viewport = mViewports.get(i);
             if (uniqueId.equals(viewport.uniqueId)) {
+                if (viewport.type != VIEWPORT_VIRTUAL) {
+                    Slog.wtf(TAG, "Found a viewport with uniqueId '"  + uniqueId
+                            + "' but it has type " + DisplayViewport.typeToString(viewport.type)
+                            + " (expected VIRTUAL)");
+                    continue;
+                }
                 return viewport;
             }
         }
 
         viewport = new DisplayViewport();
         viewport.uniqueId = uniqueId;
-        mVirtualTouchViewports.add(viewport);
+        viewport.type = VIEWPORT_VIRTUAL;
+        mViewports.add(viewport);
         return viewport;
     }
 
-    private static void setViewportLocked(DisplayViewport viewport,
+    private DisplayViewport getInternalViewportLocked() {
+        return getViewportByTypeLocked(VIEWPORT_INTERNAL);
+    }
+
+    private DisplayViewport getExternalViewportLocked() {
+        return getViewportByTypeLocked(VIEWPORT_EXTERNAL);
+    }
+
+    /**
+     * Get internal or external viewport. Create it if does not currently exist.
+     * @param viewportType - either INTERNAL or EXTERNAL
+     * @return the viewport with the requested type
+     */
+    private DisplayViewport getViewportByTypeLocked(int viewportType) {
+        // Only allow a single INTERNAL or EXTERNAL viewport, which makes this function possible.
+        // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function.
+        // Creates the viewport if none exists.
+        if (viewportType != VIEWPORT_INTERNAL && viewportType != VIEWPORT_EXTERNAL) {
+            Slog.wtf(TAG, "Cannot call getViewportByTypeLocked for type "
+                    + DisplayViewport.typeToString(viewportType));
+            return null;
+        }
+        DisplayViewport viewport;
+        final int count = mViewports.size();
+        for (int i = 0; i < count; i++) {
+            viewport = mViewports.get(i);
+            if (viewport.type == viewportType) {
+                return viewport;
+            }
+        }
+
+        viewport = new DisplayViewport();
+        viewport.type = viewportType;
+        mViewports.add(viewport);
+        return viewport;
+    }
+
+    private static void populateViewportLocked(DisplayViewport viewport,
             LogicalDisplay display, DisplayDevice device) {
         viewport.valid = true;
         viewport.displayId = display.getDisplayIdLocked();
@@ -1400,9 +1446,7 @@
             pw.println("  mPendingTraversal=" + mPendingTraversal);
             pw.println("  mGlobalDisplayState=" + Display.stateToString(mGlobalDisplayState));
             pw.println("  mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
-            pw.println("  mDefaultViewport=" + mDefaultViewport);
-            pw.println("  mExternalTouchViewport=" + mExternalTouchViewport);
-            pw.println("  mVirtualTouchViewports=" + mVirtualTouchViewports);
+            pw.println("  mViewports=" + mViewports);
             pw.println("  mDefaultDisplayDefaultColorMode=" + mDefaultDisplayDefaultColorMode);
             pw.println("  mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
             pw.println("  mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
@@ -1522,18 +1566,19 @@
                     break;
 
                 case MSG_UPDATE_VIEWPORT: {
+                    final boolean changed;
                     synchronized (mSyncRoot) {
-                        mTempDefaultViewport.copyFrom(mDefaultViewport);
-                        mTempExternalTouchViewport.copyFrom(mExternalTouchViewport);
-                        if (!mTempVirtualTouchViewports.equals(mVirtualTouchViewports)) {
-                          mTempVirtualTouchViewports.clear();
-                          for (DisplayViewport d : mVirtualTouchViewports) {
-                              mTempVirtualTouchViewports.add(d.makeCopy());
-                          }
+                        changed = !mTempViewports.equals(mViewports);
+                        if (changed) {
+                            mTempViewports.clear();
+                            for (DisplayViewport d : mViewports) {
+                                mTempViewports.add(d.makeCopy());
+                            }
                         }
                     }
-                    mInputManagerInternal.setDisplayViewports(mTempDefaultViewport,
-                            mTempExternalTouchViewport, mTempVirtualTouchViewports);
+                    if (changed) {
+                        mInputManagerInternal.setDisplayViewports(mTempViewports);
+                    }
                     break;
                 }
 
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6111c23..244c764 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -19,13 +19,13 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager
         .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
 import static android.hardware.display.DisplayManager
         .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 
 import android.content.Context;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -33,10 +33,10 @@
 import android.media.projection.IMediaProjectionCallback;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.SystemProperties;
 import android.os.IBinder.DeathRecipient;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.Display;
@@ -60,7 +60,8 @@
     static final boolean DEBUG = false;
 
     // Unique id prefix for virtual displays
-    private static final String UNIQUE_ID_PREFIX = "virtual:";
+    @VisibleForTesting
+    static final String UNIQUE_ID_PREFIX = "virtual:";
 
     private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
             new ArrayMap<IBinder, VirtualDisplayDevice>();
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0f28439..5bd095d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,9 +16,6 @@
 
 package com.android.server.input;
 
-import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
-import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
-
 import android.annotation.NonNull;
 import android.app.IInputForwarder;
 import android.app.Notification;
@@ -188,13 +185,8 @@
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
     private static native void nativeStart(long ptr);
-    private static native void nativeSetVirtualDisplayViewports(long ptr,
+    private static native void nativeSetDisplayViewports(long ptr,
             DisplayViewport[] viewports);
-    private static native void nativeSetDisplayViewport(long ptr, int viewportType,
-            int displayId, int rotation,
-            int logicalLeft, int logicalTop, int logicalRight, int logicalBottom,
-            int physicalLeft, int physicalTop, int physicalRight, int physicalBottom,
-            int deviceWidth, int deviceHeight, String uniqueId);
 
     private static native int nativeGetScanCodeState(long ptr,
             int deviceId, int sourceMask, int scanCode);
@@ -409,31 +401,8 @@
         nativeReloadDeviceAliases(mPtr);
     }
 
-    private void setDisplayViewportsInternal(DisplayViewport defaultViewport,
-            DisplayViewport externalTouchViewport,
-            List<DisplayViewport> virtualTouchViewports) {
-        if (defaultViewport.valid) {
-            setDisplayViewport(VIEWPORT_INTERNAL, defaultViewport);
-        }
-
-        if (externalTouchViewport.valid) {
-            setDisplayViewport(VIEWPORT_EXTERNAL, externalTouchViewport);
-        } else if (defaultViewport.valid) {
-            setDisplayViewport(VIEWPORT_EXTERNAL, defaultViewport);
-        }
-
-        nativeSetVirtualDisplayViewports(mPtr,
-                virtualTouchViewports.toArray(new DisplayViewport[0]));
-    }
-
-    private void setDisplayViewport(int viewportType, DisplayViewport viewport) {
-        nativeSetDisplayViewport(mPtr, viewportType,
-                viewport.displayId, viewport.orientation,
-                viewport.logicalFrame.left, viewport.logicalFrame.top,
-                viewport.logicalFrame.right, viewport.logicalFrame.bottom,
-                viewport.physicalFrame.left, viewport.physicalFrame.top,
-                viewport.physicalFrame.right, viewport.physicalFrame.bottom,
-                viewport.deviceWidth, viewport.deviceHeight, viewport.uniqueId);
+    private void setDisplayViewportsInternal(List<DisplayViewport> viewports) {
+        nativeSetDisplayViewports(mPtr, viewports.toArray(new DisplayViewport[0]));
     }
 
     /**
@@ -2203,11 +2172,8 @@
 
     private final class LocalService extends InputManagerInternal {
         @Override
-        public void setDisplayViewports(DisplayViewport defaultViewport,
-                DisplayViewport externalTouchViewport,
-                List<DisplayViewport> virtualTouchViewports) {
-            setDisplayViewportsInternal(defaultViewport, externalTouchViewport,
-                    virtualTouchViewports);
+        public void setDisplayViewports(List<DisplayViewport> viewports) {
+            setDisplayViewportsInternal(viewports);
         }
 
         @Override
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index becde73..2c01d1d 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -8,6 +8,7 @@
         "-Wall",
         "-Werror",
         "-Wno-unused-parameter",
+        "-Wthread-safety",
 
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 42ade38..a754d2a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -64,6 +64,8 @@
 #include "com_android_server_input_InputWindowHandle.h"
 #include "android_hardware_display_DisplayViewport.h"
 
+#include <vector>
+
 #define INDENT "  "
 
 using android::base::StringPrintf;
@@ -144,8 +146,8 @@
 
 static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env,
         const sp<InputApplicationHandle>& inputApplicationHandle) {
-    if (inputApplicationHandle == NULL) {
-        return NULL;
+    if (inputApplicationHandle == nullptr) {
+        return nullptr;
     }
     return static_cast<NativeInputApplicationHandle*>(inputApplicationHandle.get())->
             getInputApplicationHandleObjLocalRef(env);
@@ -153,8 +155,8 @@
 
 static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
         const sp<InputWindowHandle>& inputWindowHandle) {
-    if (inputWindowHandle == NULL) {
-        return NULL;
+    if (inputWindowHandle == nullptr) {
+        return nullptr;
     }
     return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())->
             getInputWindowHandleObjLocalRef(env);
@@ -182,6 +184,15 @@
     loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
 }
 
+static void updatePointerControllerFromViewport(
+        sp<PointerController> controller, const DisplayViewport* const viewport) {
+    if (controller != nullptr && viewport != nullptr) {
+        const int32_t width = viewport->logicalRight - viewport->logicalLeft;
+        const int32_t height = viewport->logicalBottom - viewport->logicalTop;
+        controller->setDisplayViewport(width, height, viewport->orientation);
+    }
+}
+
 enum {
     WM_ACTION_PASS_TO_USER = 1,
 };
@@ -203,8 +214,7 @@
 
     void dump(std::string& dump);
 
-    void setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
-    void setDisplayViewport(int32_t viewportType, const DisplayViewport& viewport);
+    void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
     status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
             const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
@@ -277,9 +287,7 @@
     Mutex mLock;
     struct Locked {
         // Display size information.
-        DisplayViewport internalViewport;
-        DisplayViewport externalViewport;
-        Vector<DisplayViewport> virtualViewports;
+        std::vector<DisplayViewport> viewports;
 
         // System UI visibility.
         int32_t systemUiVisibility;
@@ -304,7 +312,7 @@
 
         // Input devices to be disabled
         SortedVector<int32_t> disabledInputDevices;
-    } mLocked;
+    } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
 
@@ -384,8 +392,17 @@
     return false;
 }
 
-void NativeInputManager::setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) {
-    Vector<DisplayViewport> viewports;
+static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) {
+    for (const DisplayViewport& v : viewports) {
+        if (v.type == ViewportType::VIEWPORT_INTERNAL) {
+            return &v;
+        }
+    }
+    return nullptr;
+}
+
+void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) {
+    std::vector<DisplayViewport> viewports;
 
     if (viewportObjArray) {
         jsize length = env->GetArrayLength(viewportObjArray);
@@ -397,57 +414,32 @@
 
             DisplayViewport viewport;
             android_hardware_display_DisplayViewport_toNative(env, viewportObj, &viewport);
-            ALOGI("Viewport [%d] to add: %s", (int) length, viewport.uniqueId.c_str());
-            viewports.push(viewport);
+            ALOGI("Viewport [%d] to add: %s", (int) i, viewport.uniqueId.c_str());
+            viewports.push_back(viewport);
 
             env->DeleteLocalRef(viewportObj);
         }
     }
 
+    const DisplayViewport* newInternalViewport = findInternalViewport(viewports);
     {
         AutoMutex _l(mLock);
-        mLocked.virtualViewports = viewports;
+        const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports);
+        // Internal viewport has changed if there wasn't one earlier, and there is one now, or,
+        // if they are different.
+        const bool internalViewportChanged = (newInternalViewport != nullptr) &&
+                (oldInternalViewport == nullptr || (*newInternalViewport != *newInternalViewport));
+        if (internalViewportChanged) {
+            sp<PointerController> controller = mLocked.pointerController.promote();
+            updatePointerControllerFromViewport(controller, newInternalViewport);
+        }
+        mLocked.viewports = viewports;
     }
 
     mInputManager->getReader()->requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
-void NativeInputManager::setDisplayViewport(int32_t type, const DisplayViewport& viewport) {
-    bool changed = false;
-    {
-        AutoMutex _l(mLock);
-
-        ViewportType viewportType = static_cast<ViewportType>(type);
-        DisplayViewport* v = NULL;
-        if (viewportType == ViewportType::VIEWPORT_EXTERNAL) {
-            v = &mLocked.externalViewport;
-        } else if (viewportType == ViewportType::VIEWPORT_INTERNAL) {
-            v = &mLocked.internalViewport;
-        }
-
-        if (v != NULL && *v != viewport) {
-            changed = true;
-            *v = viewport;
-
-            if (viewportType == ViewportType::VIEWPORT_INTERNAL) {
-                sp<PointerController> controller = mLocked.pointerController.promote();
-                if (controller != NULL) {
-                    controller->setDisplayViewport(
-                            viewport.logicalRight - viewport.logicalLeft,
-                            viewport.logicalBottom - viewport.logicalTop,
-                            viewport.orientation);
-                }
-            }
-        }
-    }
-
-    if (changed) {
-        mInputManager->getReader()->requestRefreshConfiguration(
-                InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-    }
-}
-
 status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
         const sp<InputChannel>& inputChannel,
         const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
@@ -479,7 +471,7 @@
         jsize length = env->GetArrayLength(excludedDeviceNames);
         for (jsize i = 0; i < length; i++) {
             jstring item = jstring(env->GetObjectArrayElement(excludedDeviceNames, i));
-            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);
+            const char* deviceNameChars = env->GetStringUTFChars(item, nullptr);
             outConfig->excludedDeviceNames.push_back(deviceNameChars);
             env->ReleaseStringUTFChars(item, deviceNameChars);
             env->DeleteLocalRef(item);
@@ -526,11 +518,7 @@
 
         outConfig->pointerCapture = mLocked.pointerCapture;
 
-        outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_INTERNAL,
-                mLocked.internalViewport);
-        outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_EXTERNAL,
-                mLocked.externalViewport);
-        outConfig->setVirtualDisplayViewports(mLocked.virtualViewports);
+        outConfig->setDisplayViewports(mLocked.viewports);
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
     } // release lock
@@ -541,25 +529,22 @@
     AutoMutex _l(mLock);
 
     sp<PointerController> controller = mLocked.pointerController.promote();
-    if (controller == NULL) {
+    if (controller == nullptr) {
         ensureSpriteControllerLocked();
 
         controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
 
-        DisplayViewport& v = mLocked.internalViewport;
-        controller->setDisplayViewport(
-                v.logicalRight - v.logicalLeft,
-                v.logicalBottom - v.logicalTop,
-                v.orientation);
+        const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports);
+        updatePointerControllerFromViewport(controller, internalViewport);
 
         updateInactivityTimeoutLocked(controller);
     }
     return controller;
 }
 
-void NativeInputManager::ensureSpriteControllerLocked() {
-    if (mLocked.spriteController == NULL) {
+void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) {
+    if (mLocked.spriteController == nullptr) {
         JNIEnv* env = jniEnv();
         jint layer = env->CallIntMethod(mServiceObj, gServiceClassInfo.getPointerLayer);
         if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
@@ -575,7 +560,7 @@
 
     size_t count = inputDevices.size();
     jobjectArray inputDevicesObjArray = env->NewObjectArray(
-            count, gInputDeviceClassInfo.clazz, NULL);
+            count, gInputDeviceClassInfo.clazz, nullptr);
     if (inputDevicesObjArray) {
         bool error = false;
         for (size_t i = 0; i < count; i++) {
@@ -750,7 +735,7 @@
 
             sp<InputWindowHandle> windowHandle =
                     android_server_InputWindowHandle_getHandle(env, windowHandleObj);
-            if (windowHandle != NULL) {
+            if (windowHandle != nullptr) {
                 windowHandles.push(windowHandle);
             }
             env->DeleteLocalRef(windowHandleObj);
@@ -803,13 +788,14 @@
         mLocked.systemUiVisibility = visibility;
 
         sp<PointerController> controller = mLocked.pointerController.promote();
-        if (controller != NULL) {
+        if (controller != nullptr) {
             updateInactivityTimeoutLocked(controller);
         }
     }
 }
 
-void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller)
+        REQUIRES(mLock) {
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
     controller->setInactivityTimeout(lightsOut
             ? PointerController::INACTIVITY_TIMEOUT_SHORT
@@ -894,7 +880,7 @@
 void NativeInputManager::setPointerIconType(int32_t iconId) {
     AutoMutex _l(mLock);
     sp<PointerController> controller = mLocked.pointerController.promote();
-    if (controller != NULL) {
+    if (controller != nullptr) {
         controller->updatePointerIcon(iconId);
     }
 }
@@ -902,7 +888,7 @@
 void NativeInputManager::reloadPointerIcons() {
     AutoMutex _l(mLock);
     sp<PointerController> controller = mLocked.pointerController.promote();
-    if (controller != NULL) {
+    if (controller != nullptr) {
         controller->reloadPointerResources();
     }
 }
@@ -910,7 +896,7 @@
 void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) {
     AutoMutex _l(mLock);
     sp<PointerController> controller = mLocked.pointerController.promote();
-    if (controller != NULL) {
+    if (controller != nullptr) {
         controller->setCustomPointerIcon(icon);
     }
 }
@@ -1122,7 +1108,7 @@
                     gServiceClassInfo.dispatchUnhandledKey,
                     inputWindowHandleObj, keyEventObj, policyFlags);
             if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
-                fallbackKeyEventObj = NULL;
+                fallbackKeyEventObj = nullptr;
             }
             android_view_KeyEvent_recycle(env, keyEventObj);
             env->DeleteLocalRef(keyEventObj);
@@ -1235,7 +1221,7 @@
 static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
         jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
     sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
-    if (messageQueue == NULL) {
+    if (messageQueue == nullptr) {
         jniThrowRuntimeException(env, "MessageQueue is not initialized.");
         return 0;
     }
@@ -1255,37 +1241,10 @@
     }
 }
 
-static void nativeSetVirtualDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static void nativeSetDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr,
         jobjectArray viewportObjArray) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-    im->setVirtualDisplayViewports(env, viewportObjArray);
-}
-
-static void nativeSetDisplayViewport(JNIEnv* env, jclass /* clazz */, jlong ptr,
-        jint viewportType, jint displayId, jint orientation,
-        jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom,
-        jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom,
-        jint deviceWidth, jint deviceHeight, jstring uniqueId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    DisplayViewport v;
-    v.displayId = displayId;
-    v.orientation = orientation;
-    v.logicalLeft = logicalLeft;
-    v.logicalTop = logicalTop;
-    v.logicalRight = logicalRight;
-    v.logicalBottom = logicalBottom;
-    v.physicalLeft = physicalLeft;
-    v.physicalTop = physicalTop;
-    v.physicalRight = physicalRight;
-    v.physicalBottom = physicalBottom;
-    v.deviceWidth = deviceWidth;
-    v.deviceHeight = deviceHeight;
-    if (uniqueId != nullptr) {
-        v.uniqueId = ScopedUtfChars(env, uniqueId).c_str();
-    }
-
-    im->setDisplayViewport(viewportType, v);
+    im->setDisplayViewports(env, viewportObjArray);
 }
 
 static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */,
@@ -1316,8 +1275,8 @@
         jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
-    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
-    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
+    int32_t* codes = env->GetIntArrayElements(keyCodes, nullptr);
+    uint8_t* flags = env->GetBooleanArrayElements(outFlags, nullptr);
     jsize numCodes = env->GetArrayLength(keyCodes);
     jboolean result;
     if (numCodes == env->GetArrayLength(keyCodes)) {
@@ -1356,7 +1315,7 @@
 
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
             inputChannelObj);
-    if (inputChannel == NULL) {
+    if (inputChannel == nullptr) {
         throwInputChannelNotInitialized(env);
         return;
     }
@@ -1385,12 +1344,12 @@
 
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
             inputChannelObj);
-    if (inputChannel == NULL) {
+    if (inputChannel == nullptr) {
         throwInputChannelNotInitialized(env);
         return;
     }
 
-    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, nullptr, nullptr);
 
     status_t status = im->unregisterInputChannel(env, inputChannel);
     if (status && status != BAD_VALUE) { // ignore already unregistered channel
@@ -1490,7 +1449,7 @@
     sp<InputChannel> toChannel =
             android_view_InputChannel_getInputChannel(env, toChannelObj);
 
-    if (fromChannel == NULL || toChannel == NULL) {
+    if (fromChannel == nullptr || toChannel == nullptr) {
         return JNI_FALSE;
     }
 
@@ -1543,7 +1502,7 @@
     }
 
     jlong* patternMillis = static_cast<jlong*>(env->GetPrimitiveArrayCritical(
-            patternObj, NULL));
+            patternObj, nullptr));
     nsecs_t pattern[patternSize];
     for (size_t i = 0; i < patternSize; i++) {
         pattern[i] = max(jlong(0), min(patternMillis[i],
@@ -1656,10 +1615,8 @@
             (void*) nativeInit },
     { "nativeStart", "(J)V",
             (void*) nativeStart },
-    { "nativeSetVirtualDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V",
-            (void*) nativeSetVirtualDisplayViewports },
-    { "nativeSetDisplayViewport", "(JIIIIIIIIIIIIILjava/lang/String;)V",
-            (void*) nativeSetDisplayViewport },
+    { "nativeSetDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V",
+            (void*) nativeSetDisplayViewports },
     { "nativeGetScanCodeState", "(JIII)I",
             (void*) nativeGetScanCodeState },
     { "nativeGetKeyCodeState", "(JIII)I",
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index ceee60c..b421280 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+
 import android.content.Context;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.Curve;
@@ -25,35 +27,48 @@
 import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.display.DisplayDeviceInfo;
 import com.android.server.display.DisplayManagerService.SyncRoot;
-import com.android.server.display.VirtualDisplayAdapter.SurfaceControlDisplayFactory;
 import com.android.server.lights.LightsManager;
 import com.android.server.wm.WindowManagerInternal;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
 import java.util.List;
 
-import static org.mockito.Matchers.any;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.mock;
 
 @SmallTest
-public class DisplayManagerServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
     private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
 
+    private Context mContext;
+
     private final DisplayManagerService.Injector mShortMockedInjector =
             new DisplayManagerService.Injector() {
                 @Override
@@ -86,8 +101,8 @@
     @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
     @Mock IBinder mMockDisplayToken;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -96,15 +111,12 @@
         LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
         LocalServices.removeServiceForTest(LightsManager.class);
         LocalServices.addService(LightsManager.class, mMockLightsManager);
-        super.setUp();
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    public void testCreateVirtualDisplay_sentToInputManager() throws Exception {
+    @Test
+    public void testCreateVirtualDisplay_sentToInputManager() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
@@ -115,7 +127,7 @@
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
 
         String uniqueId = "uniqueId --- Test";
-        String uniqueIdPrefix = "virtual:" + mContext.getPackageName() + ":";
+        String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":";
         int width = 600;
         int height = 800;
         int dpi = 320;
@@ -132,19 +144,113 @@
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
 
-        ArgumentCaptor<List<DisplayViewport>> virtualViewportCaptor =
-                ArgumentCaptor.forClass(List.class);
-        verify(mMockInputManagerInternal).setDisplayViewports(
-                any(), any(), virtualViewportCaptor.capture());
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
 
-        assertEquals(1, virtualViewportCaptor.getValue().size());
-        DisplayViewport dv = virtualViewportCaptor.getValue().get(0);
-        assertEquals(height, dv.deviceHeight);
-        assertEquals(width, dv.deviceWidth);
-        assertEquals(uniqueIdPrefix + uniqueId, dv.uniqueId);
-        assertEquals(displayId, dv.displayId);
+        // Expect to receive 3 viewports: internal, external, and virtual
+        assertEquals(3, viewports.size());
+
+        DisplayViewport virtualViewport = null;
+        DisplayViewport internalViewport = null;
+        DisplayViewport externalViewport = null;
+        for (int i = 0; i < viewports.size(); i++) {
+            DisplayViewport v = viewports.get(i);
+            switch (v.type) {
+                case DisplayViewport.VIEWPORT_INTERNAL: {
+                    internalViewport = v;
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_EXTERNAL: {
+                    externalViewport = v;
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_VIRTUAL: {
+                    virtualViewport = v;
+                    break;
+                }
+            }
+        }
+        // INTERNAL and EXTERNAL viewports get created upon access
+        assertNotNull(internalViewport);
+        assertNotNull(externalViewport);
+        assertNotNull(virtualViewport);
+
+        // INTERNAL and EXTERNAL
+        assertTrue(internalViewport.valid);
+        assertTrue(externalViewport.valid);
+
+        // VIRTUAL
+        assertEquals(height, virtualViewport.deviceHeight);
+        assertEquals(width, virtualViewport.deviceWidth);
+        assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId);
+        assertEquals(displayId, virtualViewport.displayId);
     }
 
+    @Test
+    public void testPhysicalViewports() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.systemReady(false /* safeMode */, true /* onlyCore */);
+        displayManager.windowManagerAndInputReady();
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        final int displayIds[] = bs.getDisplayIds();
+        assertEquals(1, displayIds.length);
+        final int displayId = displayIds[0];
+        DisplayInfo info = bs.getDisplayInfo(displayId);
+        assertEquals(info.type, Display.TYPE_BUILT_IN);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
+
+        // Expect to receive 2 viewports: 1 internal, 1 external
+        assertEquals(2, viewports.size());
+
+        DisplayViewport internalViewport = null;
+        DisplayViewport externalViewport = null;
+        for (int i = 0; i < viewports.size(); i++) {
+            DisplayViewport v = viewports.get(i);
+            switch (v.type) {
+                case DisplayViewport.VIEWPORT_INTERNAL: {
+                    internalViewport = v;
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_EXTERNAL: {
+                    externalViewport = v;
+                    break;
+                }
+                default: {
+                    fail("Unexpected viewport type: " + DisplayViewport.typeToString(v.type));
+                    break;
+                }
+            }
+        }
+        // INTERNAL and EXTERNAL viewports get created upon access
+        assertNotNull(internalViewport);
+        assertNotNull(externalViewport);
+        assertTrue(internalViewport.valid);
+        assertEquals(displayId, internalViewport.displayId);
+
+        // To simplify comparison, override the type for external Viewport
+        // TODO (b/116850516) remove this
+        externalViewport.type = internalViewport.type;
+        assertEquals(internalViewport, externalViewport);
+        externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL; // undo the changes above
+    }
+
+    @Test
     public void testCreateVirtualDisplayRotatesWithContent() throws Exception {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
@@ -178,6 +284,7 @@
     /**
      * Tests that the virtual display is created along-side the default display.
      */
+    @Test
     public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -188,6 +295,7 @@
     /**
      * Tests that we get a Runtime exception when we cannot initialize the default display.
      */
+    @Test
     public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -206,6 +314,7 @@
     /**
      * Tests that we get a Runtime exception when we cannot initialize the virtual display.
      */
+    @Test
     public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
         DisplayManagerService displayManager = new DisplayManagerService(mContext,
                 new DisplayManagerService.Injector() {
@@ -232,6 +341,7 @@
     /**
      * Tests that an exception is raised for too dark a brightness configuration.
      */
+    @Test
     public void testTooDarkBrightnessConfigurationThrowException() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -266,6 +376,7 @@
     /**
      * Tests that no exception is raised for not too dark a brightness configuration.
      */
+    @Test
     public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -279,6 +390,7 @@
     /**
      * Tests that null brightness configurations are alright.
      */
+    @Test
     public void testNullBrightnessConfiguration() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);