Generalize physical display management

This CL enables the framework to manage an arbitrary number of physical
displays. It also surfaces physical display IDs, which are stable across
reboots and encode (model, port) information that will be propagated
further up in a follow-up CL.

Bug: 116025192
Test: Boot with more than two displays
Test: Hotplug works with any number of displays
Test: Verify stable display IDs with "dumpsys display"
Change-Id: Idb2eaff66b2e0873be6ad27d337ff18b730d1331
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 5dcb392b..46917e4 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -252,10 +252,12 @@
 status_t BootAnimation::readyToRun() {
     mAssets.addDefaultAssets();
 
-    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
-            ISurfaceComposer::eDisplayIdMain));
+    mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
+    if (mDisplayToken == nullptr)
+        return -1;
+
     DisplayInfo dinfo;
-    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
+    status_t status = SurfaceComposerClient::getDisplayInfo(mDisplayToken, &dinfo);
     if (status)
         return -1;
 
@@ -1014,16 +1016,13 @@
         // At the end of the animation, we switch to the viewport that DisplayManager will apply
         // later. This changes the coordinate system, and means we must move the surface up by
         // the inset amount.
-        sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
-                ISurfaceComposer::eDisplayIdMain));
-
         Rect layerStackRect(0, 0, mWidth, mHeight - mTargetInset);
         Rect displayRect(0, mTargetInset, mWidth, mHeight);
 
         SurfaceComposerClient::Transaction t;
         t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
                 .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
-        t.setDisplayProjection(dtoken, 0 /* orientation */, layerStackRect, displayRect);
+        t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect);
         t.apply();
 
         mTargetInset = mCurrentInset = 0;
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 04d4f9a..19616cb 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -171,6 +171,7 @@
     EGLDisplay  mDisplay;
     EGLDisplay  mContext;
     EGLDisplay  mSurface;
+    sp<IBinder> mDisplayToken;
     sp<SurfaceControl> mFlingerSurfaceControl;
     sp<Surface> mFlingerSurface;
     bool        mClockEnabled;
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 3d74f8b..c497667 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -46,23 +46,22 @@
 
 using namespace android;
 
-static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
-
 #define COLORSPACE_UNKNOWN    0
 #define COLORSPACE_SRGB       1
 #define COLORSPACE_DISPLAY_P3 2
 
-static void usage(const char* pname)
+static void usage(const char* pname, PhysicalDisplayId displayId)
 {
     fprintf(stderr,
             "usage: %s [-hp] [-d display-id] [FILENAME]\n"
             "   -h: this message\n"
             "   -p: save the file as a png.\n"
-            "   -d: specify the display id to capture, default %d.\n"
+            "   -d: specify the physical display ID to capture (default: %"
+                    ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ")\n"
+            "       see \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"
             "If FILENAME ends with .png it will be saved as a png.\n"
             "If FILENAME is not given, the results will be printed to stdout.\n",
-            pname, DEFAULT_DISPLAY_ID
-    );
+            pname, displayId);
 }
 
 static SkColorType flinger2skia(PixelFormat f)
@@ -127,9 +126,14 @@
 
 int main(int argc, char** argv)
 {
+    std::optional<PhysicalDisplayId> displayId = SurfaceComposerClient::getInternalDisplayId();
+    if (!displayId) {
+        fprintf(stderr, "Failed to get token for internal display\n");
+        return 1;
+    }
+
     const char* pname = argv[0];
     bool png = false;
-    int32_t displayId = DEFAULT_DISPLAY_ID;
     int c;
     while ((c = getopt(argc, argv, "phd:")) != -1) {
         switch (c) {
@@ -137,11 +141,11 @@
                 png = true;
                 break;
             case 'd':
-                displayId = atoi(optarg);
+                displayId = atoll(optarg);
                 break;
             case '?':
             case 'h':
-                usage(pname);
+                usage(pname, *displayId);
                 return 1;
         }
     }
@@ -166,7 +170,7 @@
     }
 
     if (fd == -1) {
-        usage(pname);
+        usage(pname, *displayId);
         return 1;
     }
 
@@ -192,9 +196,10 @@
     ProcessState::self()->setThreadPoolMaxThreadCount(0);
     ProcessState::self()->startThreadPool();
 
-    sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
-    if (display == NULL) {
-        fprintf(stderr, "Unable to get handle for display %d\n", displayId);
+    const sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
+    if (display == nullptr) {
+        fprintf(stderr, "Failed to get token for invalid display %"
+                ANDROID_PHYSICAL_DISPLAY_ID_FORMAT "\n", *displayId);
         return 1;
     }
 
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 7f4d1d0..7f1021b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -33456,7 +33456,6 @@
 HSPLandroid/view/SurfaceControl;->finalize()V
 HSPLandroid/view/SurfaceControl;->getActiveColorMode(Landroid/os/IBinder;)I
 HSPLandroid/view/SurfaceControl;->getActiveConfig(Landroid/os/IBinder;)I
-HSPLandroid/view/SurfaceControl;->getBuiltInDisplay(I)Landroid/os/IBinder;
 HSPLandroid/view/SurfaceControl;->getDisplayColorModes(Landroid/os/IBinder;)[I
 HSPLandroid/view/SurfaceControl;->getDisplayConfigs(Landroid/os/IBinder;)[Landroid/view/SurfaceControl$PhysicalDisplayInfo;
 HSPLandroid/view/SurfaceControl;->getHandle()Landroid/os/IBinder;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index ccd0fc1..03e8a0f 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -22,7 +22,6 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.FrameInfo;
-import android.graphics.Insets;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -914,25 +913,11 @@
             super(looper, vsyncSource);
         }
 
+        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
+        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
+        // for the internal display implicitly.
         @Override
-        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
-            // Ignore vsync from secondary display.
-            // This can be problematic because the call to scheduleVsync() is a one-shot.
-            // We need to ensure that we will still receive the vsync from the primary
-            // display which is the one we really care about.  Ideally we should schedule
-            // vsync for a particular display.
-            // At this time Surface Flinger won't send us vsyncs for secondary displays
-            // but that could change in the future so let's log a message to help us remember
-            // that we need to fix this.
-            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
-                Log.d(TAG, "Received vsync from secondary display, but we don't support "
-                        + "this case yet.  Choreographer needs a way to explicitly request "
-                        + "vsync for a specific display to ensure it doesn't lose track "
-                        + "of its scheduled vsync.");
-                scheduleVsync();
-                return;
-            }
-
+        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
             // Post the vsync event to the Handler.
             // The idea is to prevent incoming vsync events from completely starving
             // the message queue.  If there are no messages in the queue with timestamps
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index edd3f1a..3e8002f 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -136,12 +136,11 @@
      *
      * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
      * timebase.
-     * @param builtInDisplayId The surface flinger built-in display id such as
-     * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param frame The frame number.  Increases by one for each vertical sync interval.
      */
     @UnsupportedAppUsage
-    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
+    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
     }
 
     /**
@@ -149,12 +148,11 @@
      *
      * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
      * timebase.
-     * @param builtInDisplayId The surface flinger built-in display id such as
-     * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_HDMI}.
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param connected True if the display is connected, false if it disconnected.
      */
     @UnsupportedAppUsage
-    public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+    public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
     }
 
     /**
@@ -174,14 +172,14 @@
     // Called from native code.
     @SuppressWarnings("unused")
     @UnsupportedAppUsage
-    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
-        onVsync(timestampNanos, builtInDisplayId, frame);
+    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
+        onVsync(timestampNanos, physicalDisplayId, frame);
     }
 
     // Called from native code.
     @SuppressWarnings("unused")
     @UnsupportedAppUsage
-    private void dispatchHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
-        onHotplug(timestampNanos, builtInDisplayId, connected);
+    private void dispatchHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
+        onHotplug(timestampNanos, physicalDisplayId, connected);
     }
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 8061cc3..a68cc66 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -131,7 +131,8 @@
     private static native boolean nativeClearAnimationFrameStats();
     private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
 
-    private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+    private static native long[] nativeGetPhysicalDisplayIds();
+    private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
     private static native IBinder nativeCreateDisplay(String name, boolean secure);
     private static native void nativeDestroyDisplay(IBinder displayToken);
     private static native void nativeSetDisplaySurface(long transactionObj,
@@ -329,24 +330,6 @@
      */
     private static final int SURFACE_OPAQUE = 0x02;
 
-
-    /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
-     * these are different from the logical display ids used elsewhere in the framework */
-
-    /**
-     * Built-in physical display id: Main display.
-     * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
-     * @hide
-     */
-    public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
-
-    /**
-     * Built-in physical display id: Attached HDMI display.
-     * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
-     * @hide
-     */
-    public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
-
     // Display power modes.
     /**
      * Display power mode off: used while blanking the screen.
@@ -1729,9 +1712,28 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
-    public static IBinder getBuiltInDisplay(int builtInDisplayId) {
-        return nativeGetBuiltInDisplay(builtInDisplayId);
+    public static long[] getPhysicalDisplayIds() {
+        return nativeGetPhysicalDisplayIds();
+    }
+
+    /**
+     * @hide
+     */
+    public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
+        return nativeGetPhysicalDisplayToken(physicalDisplayId);
+    }
+
+    /**
+     * TODO(116025192): Remove this stopgap once framework is display-agnostic.
+     *
+     * @hide
+     */
+    public static IBinder getInternalDisplayToken() {
+        final long[] physicalDisplayIds = getPhysicalDisplayIds();
+        if (physicalDisplayIds.length == 0) {
+            return null;
+        }
+        return getPhysicalDisplayToken(physicalDisplayIds[0]);
     }
 
     /**
@@ -1790,8 +1792,12 @@
     public static Bitmap screenshot(Rect sourceCrop, int width, int height,
             boolean useIdentityTransform, int rotation) {
         // TODO: should take the display as a parameter
-        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
-                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        if (displayToken == null) {
+            Log.w(TAG, "Failed to take screenshot because internal display is disconnected");
+            return null;
+        }
+
         if (rotation == ROTATION_90 || rotation == ROTATION_270) {
             rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
         }
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index c1b5aae..191472d 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -59,8 +59,8 @@
     sp<MessageQueue> mMessageQueue;
     DisplayEventReceiver mReceiver;
 
-    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
-    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
+    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
 };
 
 
@@ -84,28 +84,30 @@
     DisplayEventDispatcher::dispose();
 }
 
-void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
+void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                               uint32_t count) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
         env->CallVoidMethod(receiverObj.get(),
-                gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count);
+                gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, displayId, count);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
     mMessageQueue->raiseAndClearException(env, "dispatchVsync");
 }
 
-void NativeDisplayEventReceiver::dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected) {
+void NativeDisplayEventReceiver::dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                                 bool connected) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking hotplug handler.", this);
         env->CallVoidMethod(receiverObj.get(),
-                gDisplayEventReceiverClassInfo.dispatchHotplug, timestamp, id, connected);
+                gDisplayEventReceiverClassInfo.dispatchHotplug, timestamp, displayId, connected);
         ALOGV("receiver %p ~ Returned from hotplug handler.", this);
     }
 
@@ -175,9 +177,9 @@
     gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
     gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env,
-            gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JII)V");
+            gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
     gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
-            gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JIZ)V");
+            gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
 
     return res;
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index fad2fe0..68be005 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -483,8 +483,29 @@
     transaction->setLayerStack(ctrl, layerStack);
 }
 
-static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) {
-    sp<IBinder> token(SurfaceComposerClient::getBuiltInDisplay(id));
+static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
+    const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
+    jlongArray array = env->NewLongArray(displayIds.size());
+    if (array == nullptr) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    if (displayIds.empty()) {
+        return array;
+    }
+
+    jlong* values = env->GetLongArrayElements(array, 0);
+    for (size_t i = 0; i < displayIds.size(); ++i) {
+        values[i] = static_cast<jlong>(displayIds[i]);
+    }
+
+    env->ReleaseLongArrayElements(array, values, 0);
+    return array;
+}
+
+static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong physicalDisplayId) {
+    sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(physicalDisplayId);
     return javaObjectForIBinder(env, token);
 }
 
@@ -1145,8 +1166,10 @@
             (void*)nativeSetCornerRadius },
     {"nativeSetLayerStack", "(JJI)V",
             (void*)nativeSetLayerStack },
-    {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;",
-            (void*)nativeGetBuiltInDisplay },
+    {"nativeGetPhysicalDisplayIds", "()[J",
+            (void*)nativeGetPhysicalDisplayIds },
+    {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
+            (void*)nativeGetPhysicalDisplayToken },
     {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
@@ -1314,4 +1337,4 @@
     return err;
 }
 
-};
+} // namespace android
diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp
index 7708e43..3b9a348 100644
--- a/libs/androidfw/DisplayEventDispatcher.cpp
+++ b/libs/androidfw/DisplayEventDispatcher.cpp
@@ -68,7 +68,7 @@
 
         // Drain all pending events.
         nsecs_t vsyncTimestamp;
-        int32_t vsyncDisplayId;
+        PhysicalDisplayId vsyncDisplayId;
         uint32_t vsyncCount;
         if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
             ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",
@@ -101,10 +101,11 @@
 
     // Drain all pending events, keep the last vsync.
     nsecs_t vsyncTimestamp;
-    int32_t vsyncDisplayId;
+    PhysicalDisplayId vsyncDisplayId;
     uint32_t vsyncCount;
     if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
-        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
+        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", displayId=%"
+                ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d",
                 this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
         mWaitingForVsync = false;
         dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
@@ -114,7 +115,7 @@
 }
 
 bool DisplayEventDispatcher::processPendingEvents(
-        nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
+        nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount) {
     bool gotVsync = false;
     DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
     ssize_t n;
@@ -128,11 +129,11 @@
                 // ones. That's fine, we only care about the most recent.
                 gotVsync = true;
                 *outTimestamp = ev.header.timestamp;
-                *outId = ev.header.id;
+                *outDisplayId = ev.header.displayId;
                 *outCount = ev.vsync.count;
                 break;
             case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
-                dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
+                dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
                 break;
             default:
                 ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
diff --git a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
index bf35aa3..d2addba 100644
--- a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
+++ b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
@@ -37,10 +37,12 @@
     DisplayEventReceiver mReceiver;
     bool mWaitingForVsync;
 
-    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) = 0;
-    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected) = 0;
+    virtual void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) = 0;
+    virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                 bool connected) = 0;
 
     virtual int handleEvent(int receiveFd, int events, void* data);
-    bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
+    bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
+                              uint32_t* outCount);
 };
 }
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 4c67513..cf5d7ce 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -55,9 +55,12 @@
         return sDummyDisplay;
     }
 
+    const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
+    LOG_ALWAYS_FATAL_IF(token == nullptr,
+                        "Failed to get display info because internal display is disconnected");
+
     DisplayInfo displayInfo;
-    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &displayInfo);
+    status_t status = SurfaceComposerClient::getDisplayInfo(token, &displayInfo);
     LOG_ALWAYS_FATAL_IF(status, "Failed to get display info, error %d", status);
     return displayInfo;
 }
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 92b6cbd..0a54aca 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -37,11 +37,13 @@
         0,      // presentationDeadline
 };
 
-DisplayInfo getBuiltInDisplay() {
+DisplayInfo getInternalDisplay() {
 #if !HWUI_NULL_GPU
     DisplayInfo display;
-    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display);
+    const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
+    LOG_ALWAYS_FATAL_IF(token == nullptr,
+                        "Failed to get display info because internal display is disconnected\n");
+    status_t status = SurfaceComposerClient::getDisplayInfo(token, &display);
     LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
     return display;
 #else
diff --git a/libs/hwui/tests/common/TestContext.h b/libs/hwui/tests/common/TestContext.h
index 0996f4d..116d4de 100644
--- a/libs/hwui/tests/common/TestContext.h
+++ b/libs/hwui/tests/common/TestContext.h
@@ -36,7 +36,7 @@
 extern DisplayInfo gDisplay;
 #define dp(x) ((x)*android::uirenderer::test::gDisplay.density)
 
-DisplayInfo getBuiltInDisplay();
+DisplayInfo getInternalDisplay();
 
 class TestContext {
 public:
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 5fa008b..0e61899e 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -109,7 +109,7 @@
 void run(const TestScene::Info& info, const TestScene::Options& opts,
          benchmark::BenchmarkReporter* reporter) {
     // Switch to the real display
-    gDisplay = getBuiltInDisplay();
+    gDisplay = getInternalDisplay();
 
     Properties::forceDrawFrame = true;
     TestContext testContext;
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index c3629da..2db575b 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -24,6 +24,7 @@
 #include <android/choreographer.h>
 #include <androidfw/DisplayEventDispatcher.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
 #include <utils/Looper.h>
 #include <utils/Mutex.h>
 #include <utils/Timers.h>
@@ -67,8 +68,8 @@
     explicit Choreographer(const sp<Looper>& looper);
     Choreographer(const Choreographer&) = delete;
 
-    virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
-    virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
+    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
 
     void scheduleCallbacks();
 
@@ -139,13 +140,10 @@
     }
 }
 
-
-void Choreographer::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t) {
-    if (id != ISurfaceComposer::eDisplayIdMain) {
-        ALOGV("choreographer %p ~ ignoring vsync signal for non-main display (id=%d)", this, id);
-        scheduleVsync();
-        return;
-    }
+// TODO(b/74619554): The PhysicalDisplayId is ignored because SF only emits VSYNC events for the
+// internal display and DisplayEventReceiver::requestNextVsync only allows requesting VSYNC for
+// the internal display implicitly.
+void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t) {
     std::vector<FrameCallback> callbacks{};
     {
         AutoMutex _l{mLock};
@@ -160,9 +158,10 @@
     }
 }
 
-void Choreographer::dispatchHotplug(nsecs_t, int32_t id, bool connected) {
-    ALOGV("choreographer %p ~ received hotplug event (id=%" PRId32 ", connected=%s), ignoring.",
-            this, id, toString(connected));
+void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
+    ALOGV("choreographer %p ~ received hotplug event (displayId=%"
+            ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", connected=%s), ignoring.",
+            this, displayId, toString(connected));
 }
 
 void Choreographer::handleMessage(const Message& message) {
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 3156732..d68d349 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -46,7 +46,13 @@
 
 static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
     sp<SurfaceComposerClient> client = surfaceControl->getClient();
-    sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+    const sp<IBinder> display = client->getInternalDisplayToken();
+    if (display == nullptr) {
+        ALOGE("unable to get wide color support for disconnected internal display");
+        return false;
+    }
+
     bool isWideColorDisplay = false;
     status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
     if (err) {
@@ -58,7 +64,12 @@
 
 static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
     sp<SurfaceComposerClient> client = surfaceControl->getClient();
-    sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+    const sp<IBinder> display = client->getInternalDisplayToken();
+    if (display == nullptr) {
+        ALOGE("unable to get hdr capabilities for disconnected internal display");
+        return false;
+    }
 
     HdrCapabilities hdrCapabilities;
     status_t err = client->getHdrCapabilities(display, &hdrCapabilities);
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 591ce8d..9fad35b 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -23,6 +23,7 @@
 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
+
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
@@ -65,10 +66,9 @@
 import android.util.MathUtils;
 import android.util.Slog;
 import android.view.SurfaceControl;
+import android.view.SurfaceControl.DisplayPrimaries;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.DisplayPrimaries;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -79,6 +79,7 @@
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -315,8 +316,7 @@
         }
 
         private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
-            IBinder displayToken = SurfaceControl.getBuiltInDisplay(
-                    SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+            final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
             if (displayToken == null) {
                 return null;
             }
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index f2c539c..2f277e4 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -27,6 +27,7 @@
 import android.opengl.EGLSurface;
 import android.opengl.GLES11Ext;
 import android.opengl.GLES20;
+import android.os.IBinder;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -474,8 +475,14 @@
             final SurfaceTexture st = new SurfaceTexture(mTexNames[0]);
             final Surface s = new Surface(st);
             try {
-                SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
-                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), s);
+                final IBinder token = SurfaceControl.getInternalDisplayToken();
+                if (token == null) {
+                    Slog.e(TAG,
+                            "Failed to take screenshot because internal display is disconnected");
+                    return false;
+                }
+
+                SurfaceControl.screenshot(token, s);
                 st.updateTexImage();
                 st.getTransformMatrix(mTexMatrix);
             } finally {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 16d82df..28f21f63 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -17,12 +17,8 @@
 package com.android.server.display;
 
 import android.app.ActivityThread;
-import android.content.res.Resources;
-import com.android.server.LocalServices;
-import com.android.server.lights.Light;
-import com.android.server.lights.LightsManager;
-
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.sidekick.SidekickInternal;
 import android.os.Build;
 import android.os.Handler;
@@ -31,6 +27,7 @@
 import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -38,6 +35,11 @@
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
 import android.view.SurfaceControl;
+
+import com.android.server.LocalServices;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -58,13 +60,9 @@
 
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
 
-    private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
-            SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
-            SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
-    };
+    private final LongSparseArray<LocalDisplayDevice> mDevices =
+            new LongSparseArray<LocalDisplayDevice>();
 
-    private final SparseArray<LocalDisplayDevice> mDevices =
-            new SparseArray<LocalDisplayDevice>();
     @SuppressWarnings("unused")  // Becomes active at instantiation time.
     private HotplugDisplayEventReceiver mHotplugReceiver;
 
@@ -80,28 +78,26 @@
 
         mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper());
 
-        for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
-            tryConnectDisplayLocked(builtInDisplayId);
+        for (long physicalDisplayId : SurfaceControl.getPhysicalDisplayIds()) {
+            tryConnectDisplayLocked(physicalDisplayId);
         }
     }
 
-    private void tryConnectDisplayLocked(int builtInDisplayId) {
-        IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
+    private void tryConnectDisplayLocked(long physicalDisplayId) {
+        final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
         if (displayToken != null) {
             SurfaceControl.PhysicalDisplayInfo[] configs =
                     SurfaceControl.getDisplayConfigs(displayToken);
             if (configs == null) {
                 // There are no valid configs for this device, so we can't use it
-                Slog.w(TAG, "No valid configs found for display device " +
-                        builtInDisplayId);
+                Slog.w(TAG, "No valid configs found for display device " + physicalDisplayId);
                 return;
             }
             int activeConfig = SurfaceControl.getActiveConfig(displayToken);
             if (activeConfig < 0) {
                 // There is no active config, and for now we don't have the
                 // policy to set one.
-                Slog.w(TAG, "No active config found for display device " +
-                        builtInDisplayId);
+                Slog.w(TAG, "No active config found for display device " + physicalDisplayId);
                 return;
             }
             int activeColorMode = SurfaceControl.getActiveColorMode(displayToken);
@@ -110,16 +106,17 @@
                 // configuration pass we'll go ahead and set it to whatever it was set to last (or
                 // COLOR_MODE_NATIVE if this is the first configuration).
                 Slog.w(TAG, "Unable to get active color mode for display device " +
-                        builtInDisplayId);
+                        physicalDisplayId);
                 activeColorMode = Display.COLOR_MODE_INVALID;
             }
             int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
-            LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+            LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
-                device = new LocalDisplayDevice(displayToken, builtInDisplayId,
-                        configs, activeConfig, colorModes, activeColorMode);
-                mDevices.put(builtInDisplayId, device);
+                final boolean isInternal = mDevices.size() == 0;
+                device = new LocalDisplayDevice(displayToken, physicalDisplayId,
+                        configs, activeConfig, colorModes, activeColorMode, isInternal);
+                mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
             } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig,
                         colorModes, activeColorMode)) {
@@ -133,11 +130,11 @@
         }
     }
 
-    private void tryDisconnectDisplayLocked(int builtInDisplayId) {
-        LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+    private void tryDisconnectDisplayLocked(long physicalDisplayId) {
+        LocalDisplayDevice device = mDevices.get(physicalDisplayId);
         if (device != null) {
             // Display was removed.
-            mDevices.remove(builtInDisplayId);
+            mDevices.remove(physicalDisplayId);
             sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
         }
     }
@@ -158,10 +155,11 @@
     }
 
     private final class LocalDisplayDevice extends DisplayDevice {
-        private final int mBuiltInDisplayId;
+        private final long mPhysicalDisplayId;
         private final Light mBacklight;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
         private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
+        private final boolean mIsInternal;
 
         private DisplayDeviceInfo mInfo;
         private boolean mHavePendingChanges;
@@ -179,16 +177,17 @@
 
         private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
 
-        public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
+        LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
-                int[] colorModes, int activeColorMode) {
-            super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
-            mBuiltInDisplayId = builtInDisplayId;
+                int[] colorModes, int activeColorMode, boolean isInternal) {
+            super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
+            mPhysicalDisplayId = physicalDisplayId;
+            mIsInternal = isInternal;
             updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
                     colorModes, activeColorMode);
             updateColorModesLocked(colorModes, activeColorMode);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
-            if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
+            if (mIsInternal) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
                 mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
             } else {
@@ -392,7 +391,7 @@
                 }
 
                 final Resources res = getOverlayContext().getResources();
-                if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
+                if (mIsInternal) {
                     mInfo.name = res.getString(
                             com.android.internal.R.string.display_manager_built_in_display_name);
                     mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
@@ -455,7 +454,7 @@
             final boolean stateChanged = (mState != state);
             final boolean brightnessChanged = (mBrightness != brightness) && mBacklight != null;
             if (stateChanged || brightnessChanged) {
-                final int displayId = mBuiltInDisplayId;
+                final long physicalDisplayId = mPhysicalDisplayId;
                 final IBinder token = getDisplayTokenLocked();
                 final int oldState = mState;
 
@@ -519,7 +518,7 @@
                     private void setVrMode(boolean isVrEnabled) {
                         if (DEBUG) {
                             Slog.d(TAG, "setVrMode("
-                                    + "id=" + displayId
+                                    + "id=" + physicalDisplayId
                                     + ", state=" + Display.stateToString(state) + ")");
                         }
                         mBacklight.setVrMode(isVrEnabled);
@@ -528,7 +527,7 @@
                     private void setDisplayState(int state) {
                         if (DEBUG) {
                             Slog.d(TAG, "setDisplayState("
-                                    + "id=" + displayId
+                                    + "id=" + physicalDisplayId
                                     + ", state=" + Display.stateToString(state) + ")");
                         }
 
@@ -546,7 +545,7 @@
                         }
                         final int mode = getPowerModeForState(state);
                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
-                                + "id=" + displayId
+                                + "id=" + physicalDisplayId
                                 + ", state=" + Display.stateToString(state) + ")");
                         try {
                             SurfaceControl.setDisplayPowerMode(token, mode);
@@ -571,11 +570,12 @@
                     private void setDisplayBrightness(int brightness) {
                         if (DEBUG) {
                             Slog.d(TAG, "setDisplayBrightness("
-                                    + "id=" + displayId + ", brightness=" + brightness + ")");
+                                    + "id=" + physicalDisplayId
+                                    + ", brightness=" + brightness + ")");
                         }
 
                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
-                                + "id=" + displayId + ", brightness=" + brightness + ")");
+                                + "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
                         try {
                             mBacklight.setBrightness(brightness);
                             Trace.traceCounter(Trace.TRACE_TAG_POWER,
@@ -646,7 +646,7 @@
         @Override
         public void dumpLocked(PrintWriter pw) {
             super.dumpLocked(pw);
-            pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
+            pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
             pw.println("mActivePhysIndex=" + mActivePhysIndex);
             pw.println("mActiveModeId=" + mActiveModeId);
             pw.println("mActiveColorMode=" + mActiveColorMode);
@@ -731,12 +731,12 @@
         }
 
         @Override
-        public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+        public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
             synchronized (getSyncRoot()) {
                 if (connected) {
-                    tryConnectDisplayLocked(builtInDisplayId);
+                    tryConnectDisplayLocked(physicalDisplayId);
                 } else {
-                    tryDisconnectDisplayLocked(builtInDisplayId);
+                    tryDisconnectDisplayLocked(physicalDisplayId);
                 }
             }
         }