Merge "[GPU Service] Separate GPU Service out of SurfaceFlinger."
diff --git a/cmds/surfacereplayer/proto/src/trace.proto b/cmds/surfacereplayer/proto/src/trace.proto
index 68ddeb0..c169b76 100644
--- a/cmds/surfacereplayer/proto/src/trace.proto
+++ b/cmds/surfacereplayer/proto/src/trace.proto
@@ -160,7 +160,7 @@
 message DisplayCreation {
     required int32     id                = 1;
     required string    name              = 2;
-    required int32     type              = 3;
+    optional uint64    display_id        = 3;
     required bool      is_secure         = 4;
 }
 
diff --git a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
index a892e46..d63d97f 100644
--- a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
+++ b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
@@ -172,7 +172,7 @@
 def display_create(increment):
     increment.display_creation.id = int(input("Enter id: "))
     increment.display_creation.name = str(raw_input("Enter name: "))
-    increment.display_creation.type = int(input("Enter type: "))
+    increment.display_creation.display_id = int(input("Enter display ID: "))
     increment.display_creation.is_secure = bool(input("Enter if secure: "))
 
 def display_delete(increment):
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 5222bda..d711ad8 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -287,6 +287,9 @@
  * For debugging only!
  *
  * \param binder the binder object to retrieve the refcount of.
+ *
+ * \return the number of strong-refs on this binder in this process. If binder is null, this will be
+ * -1.
  */
 int32_t AIBinder_debugGetRefCount(AIBinder* binder) __INTRODUCED_IN(29);
 
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 97b4828..afa32b6 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -68,8 +68,7 @@
 }
 
 void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName,
-                               const std::string appPref, bool developerOptIn,
-                               const int rulesFd, const long rulesOffset,
+                               bool developerOptIn, const int rulesFd, const long rulesOffset,
                                const long rulesLength) {
     if (!mAnglePath.empty()) {
         ALOGV("ignoring attempt to change ANGLE path from '%s' to '%s'", mAnglePath.c_str(),
@@ -87,14 +86,6 @@
         mAngleAppName = appName;
     }
 
-    if (!mAngleAppPref.empty()) {
-        ALOGV("ignoring attempt to change ANGLE application opt-in from '%s' to '%s'",
-              mAngleAppPref.c_str(), appPref.c_str());
-    } else {
-        ALOGV("setting ANGLE application opt-in to '%s'", appPref.c_str());
-        mAngleAppPref = appPref;
-    }
-
     mAngleDeveloperOptIn = developerOptIn;
 
     ALOGV("setting ANGLE rules file descriptor to '%i'", rulesFd);
@@ -128,11 +119,6 @@
     return mAngleDeveloperOptIn;
 }
 
-const char* GraphicsEnv::getAngleAppPref() {
-    if (mAngleAppPref.empty()) return nullptr;
-    return mAngleAppPref.c_str();
-}
-
 int GraphicsEnv::getAngleRulesFd() {
     return mAngleRulesFd;
 }
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 528c260..20e4d66 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -44,9 +44,8 @@
     // (libraries must be stored uncompressed and page aligned); such elements
     // in the search path must have a '!' after the zip filename, e.g.
     //     /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a
-    void setAngleInfo(const std::string path, const std::string appName, const std::string appPref,
-                      bool devOptIn, const int rulesFd, const long rulesOffset,
-                      const long rulesLength);
+    void setAngleInfo(const std::string path, const std::string appName, bool devOptIn,
+                      const int rulesFd, const long rulesOffset, const long rulesLength);
     android_namespace_t* getAngleNamespace();
     const char* getAngleAppName();
     const char* getAngleAppPref();
@@ -70,7 +69,6 @@
     std::string mDriverPath;
     std::string mAnglePath;
     std::string mAngleAppName;
-    std::string mAngleAppPref;
     bool mAngleDeveloperOptIn;
     int mAngleRulesFd;
     long mAngleRulesOffset;
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index a150db1..0c43b83 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -42,18 +42,6 @@
   android_namespace_t* android_get_exported_namespace(const char*);
 
   // TODO(ianelliott@): Get this from an ANGLE header:
-  typedef enum ANGLEPreference {
-      ANGLE_NO_PREFERENCE = 0,
-      ANGLE_PREFER_NATIVE = 1,
-      ANGLE_PREFER_ANGLE = 2,
-  } ANGLEPreference;
-
-  // TODO(ianelliott@): Get this from an ANGLE header:
-  typedef bool (*fpANGLEUseForApplication)(const char* appName, const char* deviceMfr,
-                                           const char* deviceModel, ANGLEPreference developerOption,
-                                           ANGLEPreference appPreference);
-
-  // TODO(ianelliott@): Get this from an ANGLE header:
   typedef bool (*fpANGLEGetUtilityAPI)(unsigned int* versionToUse);
 
   // TODO(ianelliott@): Get this from an ANGLE header:
@@ -522,16 +510,56 @@
     return nullptr;
 }
 
-static ANGLEPreference getAnglePref(const char* app_pref) {
-    if (app_pref == nullptr)
-        return ANGLE_NO_PREFERENCE;
 
-    if (strcmp(app_pref, "angle") == 0) {
-        return ANGLE_PREFER_ANGLE;
-    } else if (strcmp(app_pref, "native") == 0) {
-        return ANGLE_PREFER_NATIVE;
+static bool check_angle_rules(void* so, const char* app_name) {
+    bool use_angle = false;
+    const int rules_fd = android::GraphicsEnv::getInstance().getAngleRulesFd();
+    const long rules_offset = android::GraphicsEnv::getInstance().getAngleRulesOffset();
+    const long rules_length = android::GraphicsEnv::getInstance().getAngleRulesLength();
+
+    std::string app_name_str = app_name ? app_name : "";
+    char manufacturer[PROPERTY_VALUE_MAX];
+    char model[PROPERTY_VALUE_MAX];
+    property_get("ro.product.manufacturer", manufacturer, "UNSET");
+    property_get("ro.product.model", model, "UNSET");
+
+    fpANGLEGetUtilityAPI ANGLEGetUtilityAPI =
+            (fpANGLEGetUtilityAPI)dlsym(so, "ANGLEGetUtilityAPI");
+
+    if (ANGLEGetUtilityAPI) {
+
+        // Negotiate the interface version by requesting most recent known to the platform
+        unsigned int versionToUse = 1;
+        if ((ANGLEGetUtilityAPI)(&versionToUse)) {
+
+            // Add and remove versions below as needed
+            switch(versionToUse) {
+            case 1: {
+                ALOGV("Using version 1 of ANGLE opt-in/out logic interface");
+                fpAndroidUseANGLEForApplication AndroidUseANGLEForApplication =
+                        (fpAndroidUseANGLEForApplication)dlsym(so, "AndroidUseANGLEForApplication");
+
+                if (AndroidUseANGLEForApplication) {
+                    use_angle = (AndroidUseANGLEForApplication)(rules_fd, rules_offset,
+                                                                rules_length, app_name_str.c_str(),
+                                                                manufacturer, model);
+                } else {
+                    ALOGW("Cannot find AndroidUseANGLEForApplication in ANGLE feature-support library");
+                }
+            }
+            break;
+            default:
+                ALOGW("Cannot find supported version of ANGLE feature-support library, found version %u", versionToUse);
+            }
+        } else {
+            ALOGW("Cannot use ANGLE feature-support library, it is older than supported by EGL, requested version %u", versionToUse);
+        }
+    } else {
+        ALOGW("Cannot find ANGLEGetUtilityAPI function");
     }
-    return ANGLE_NO_PREFERENCE;
+
+    ALOGV("Close temporarily-loaded ANGLE opt-in/out logic");
+    return use_angle;
 }
 
 static void* load_angle(const char* kind, android_namespace_t* ns, egl_connection_t* cnx) {
@@ -543,15 +571,10 @@
     char prop[PROPERTY_VALUE_MAX];
 
     const char* app_name = android::GraphicsEnv::getInstance().getAngleAppName();
-    const char* app_pref = android::GraphicsEnv::getInstance().getAngleAppPref();
     bool developer_opt_in = android::GraphicsEnv::getInstance().getAngleDeveloperOptIn();
-    const int rules_fd = android::GraphicsEnv::getInstance().getAngleRulesFd();
-    const long rules_offset = android::GraphicsEnv::getInstance().getAngleRulesOffset();
-    const long rules_length = android::GraphicsEnv::getInstance().getAngleRulesLength();
 
     // Determine whether or not to use ANGLE:
-    ANGLEPreference developer_option = developer_opt_in ? ANGLE_PREFER_ANGLE : ANGLE_NO_PREFERENCE;
-    bool use_angle = (developer_option == ANGLE_PREFER_ANGLE);
+    bool use_angle = developer_opt_in;
 
     if (use_angle) {
         ALOGV("User set \"Developer Options\" to force the use of ANGLE");
@@ -560,59 +583,11 @@
     } else {
         // The "Developer Options" value wasn't set to force the use of ANGLE.  Need to temporarily
         // load ANGLE and call the updatable opt-in/out logic:
-        std::string app_name_str = app_name ? app_name : "";
-        char manufacturer[PROPERTY_VALUE_MAX];
-        char model[PROPERTY_VALUE_MAX];
-        property_get("ro.product.manufacturer", manufacturer, "UNSET");
-        property_get("ro.product.model", model, "UNSET");
 
         cnx->featureSo = load_angle_from_namespace("feature_support", ns);
         if (cnx->featureSo) {
             ALOGV("loaded ANGLE's opt-in/out logic from namespace");
-            bool use_version0_API = false;
-            bool use_version1_API = false;
-            fpANGLEGetUtilityAPI ANGLEGetUtilityAPI =
-                    (fpANGLEGetUtilityAPI)dlsym(cnx->featureSo, "ANGLEGetUtilityAPI");
-            if (ANGLEGetUtilityAPI) {
-                unsigned int versionToUse = 1;
-                if ((ANGLEGetUtilityAPI)(&versionToUse)) {
-                    if (versionToUse == 1) {
-                        use_version1_API = true;
-                    } else {
-                        use_version0_API = true;
-                    }
-                }
-            } else {
-                use_version0_API = true;
-                ALOGV("Cannot find ANGLEGetUtilityAPI in library");
-            }
-            if (use_version1_API) {
-                // Use the new version 1 API to determine if the
-                // application should use the ANGLE or the native driver.
-                fpAndroidUseANGLEForApplication AndroidUseANGLEForApplication =
-                        (fpAndroidUseANGLEForApplication)dlsym(cnx->featureSo, "AndroidUseANGLEForApplication");
-                if (AndroidUseANGLEForApplication) {
-                    use_angle = (AndroidUseANGLEForApplication)(rules_fd, rules_offset,
-                                                                rules_length, app_name_str.c_str(),
-                                                                manufacturer, model);
-                } else {
-                    ALOGW("Cannot find AndroidUseANGLEForApplication in library");
-                }
-            } else if (use_version0_API) {
-                // Use the old version 0 API to determine if the
-                // application should use the ANGLE or the native driver.
-                fpANGLEUseForApplication ANGLEUseForApplication =
-                        (fpANGLEUseForApplication)dlsym(cnx->featureSo, "ANGLEUseForApplication");
-                if (ANGLEUseForApplication) {
-                    ANGLEPreference app_preference =
-                            getAnglePref(android::GraphicsEnv::getInstance().getAngleAppPref());
-                    use_angle = (ANGLEUseForApplication)(app_name_str.c_str(), manufacturer, model,
-                                                         developer_option, app_preference);
-                    ALOGV("Result of opt-in/out logic is %s", use_angle ? "true" : "false");
-                } else {
-                    ALOGW("Cannot find ANGLEUseForApplication in library");
-                }
-            }
+            use_angle = check_angle_rules(cnx->featureSo, app_name);
         } else {
             // We weren't able to load and call the updateable opt-in/out logic.
             // If we can't load the library, there is no ANGLE available.
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 440f1e2..641bd8d 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -229,15 +229,14 @@
             getBE().compositionInfo.mBuffer->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102);
 }
 
-void BufferLayer::setPerFrameData(const sp<const DisplayDevice>& display) {
+void BufferLayer::setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                  const Rect& viewport, int32_t supportedPerFrameMetadata) {
     // Apply this display's projection's viewport to the visible region
     // before giving it to the HWC HAL.
-    const ui::Transform& tr = display->getTransform();
-    const auto& viewport = display->getViewport();
-    Region visible = tr.transform(visibleRegion.intersect(viewport));
-    const auto displayId = display->getId();
+    Region visible = transform.transform(visibleRegion.intersect(viewport));
+
     if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setPerFrameData: no HWC layer found (%d)",
+        ALOGE("[%s] failed to setPerFrameData: no HWC layer found for display %" PRIu64,
               mName.string(), displayId);
         return;
     }
@@ -290,7 +289,7 @@
     }
 
     const HdrMetadata& metadata = getDrawingHdrMetadata();
-    error = hwcLayer->setPerFrameMetadata(display->getSupportedPerFrameMetadata(), metadata);
+    error = hwcLayer->setPerFrameMetadata(supportedPerFrameMetadata, metadata);
     if (error != HWC2::Error::None && error != HWC2::Error::Unsupported) {
         ALOGE("[%s] Failed to set hdrMetadata: %s (%d)", mName.string(),
               to_string(error).c_str(), static_cast<int32_t>(error));
@@ -303,10 +302,10 @@
     }
     getBE().compositionInfo.hwc.dataspace = mCurrentDataSpace;
     getBE().compositionInfo.hwc.hdrMetadata = getDrawingHdrMetadata();
-    getBE().compositionInfo.hwc.supportedPerFrameMetadata = display->getSupportedPerFrameMetadata();
+    getBE().compositionInfo.hwc.supportedPerFrameMetadata = supportedPerFrameMetadata;
     getBE().compositionInfo.hwc.colorTransform = getColorTransform();
 
-    setHwcLayerBuffer(display);
+    setHwcLayerBuffer(displayId);
 }
 
 bool BufferLayer::onPreComposition(nsecs_t refreshStartTime) {
@@ -318,10 +317,10 @@
     return hasReadyFrame();
 }
 
-bool BufferLayer::onPostComposition(const std::shared_ptr<FenceTime>& glDoneFence,
+bool BufferLayer::onPostComposition(const std::optional<DisplayId>& displayId,
+                                    const std::shared_ptr<FenceTime>& glDoneFence,
                                     const std::shared_ptr<FenceTime>& presentFence,
                                     const CompositorTiming& compositorTiming) {
-
     // mFrameLatencyNeeded is true when a new frame was latched for the
     // composition.
     if (!mFrameLatencyNeeded) return false;
@@ -352,11 +351,10 @@
     if (presentFence->isValid()) {
         mTimeStats.setPresentFence(layerID, mCurrentFrameNumber, presentFence);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
-    } else if (mFlinger->getHwComposer().isConnected(HWC_DISPLAY_PRIMARY)) {
+    } else if (displayId && mFlinger->getHwComposer().isConnected(*displayId)) {
         // The HWC doesn't support present fences, so use the refresh
         // timestamp instead.
-        const nsecs_t actualPresentTime =
-                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY);
+        const nsecs_t actualPresentTime = mFlinger->getHwComposer().getRefreshTimestamp(*displayId);
         mTimeStats.setPresentTime(layerID, mCurrentFrameNumber, actualPresentTime);
         mFrameTracker.setActualPresentTime(actualPresentTime);
     }
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index d000d85..b3ea7e6 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -80,10 +80,12 @@
 
     bool isHdrY410() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& displayDevice) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool onPreComposition(nsecs_t refreshStartTime) override;
-    bool onPostComposition(const std::shared_ptr<FenceTime>& glDoneFence,
+    bool onPostComposition(const std::optional<DisplayId>& displayId,
+                           const std::shared_ptr<FenceTime>& glDoneFence,
                            const std::shared_ptr<FenceTime>& presentFence,
                            const CompositorTiming& compositorTiming) override;
 
@@ -145,7 +147,7 @@
     virtual status_t updateActiveBuffer() = 0;
     virtual status_t updateFrameNumber(nsecs_t latchTime) = 0;
 
-    virtual void setHwcLayerBuffer(const sp<const DisplayDevice>& display) = 0;
+    virtual void setHwcLayerBuffer(DisplayId displayId) = 0;
 
     // -----------------------------------------------------------------------
 
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index c130bc5..e10548f 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -325,8 +325,7 @@
     return NO_ERROR;
 }
 
-void BufferQueueLayer::setHwcLayerBuffer(const sp<const DisplayDevice>& display) {
-    const auto displayId = display->getId();
+void BufferQueueLayer::setHwcLayerBuffer(DisplayId displayId) {
     auto& hwcInfo = getBE().mHwcLayers[displayId];
     auto& hwcLayer = hwcInfo.layer;
 
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index c9ebe04..74dd21e 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -94,8 +94,7 @@
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
 
-    void setHwcLayerBuffer(const sp<const DisplayDevice>& display) override;
-    // -----------------------------------------------------------------------
+    void setHwcLayerBuffer(DisplayId displayId) override;
 
     // -----------------------------------------------------------------------
     // Interface implementation for BufferLayerConsumer::ContentsChangedListener
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 73098bf..8d59841 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -497,8 +497,7 @@
     return NO_ERROR;
 }
 
-void BufferStateLayer::setHwcLayerBuffer(const sp<const DisplayDevice>& display) {
-    const auto displayId = display->getId();
+void BufferStateLayer::setHwcLayerBuffer(DisplayId displayId) {
     auto& hwcInfo = getBE().mHwcLayers[displayId];
     auto& hwcLayer = hwcInfo.layer;
 
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 0c6eaf5..381cd28 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -121,8 +121,8 @@
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
 
-    void setHwcLayerBuffer(const sp<const DisplayDevice>& display) override;
-    // -----------------------------------------------------------------------
+    void setHwcLayerBuffer(DisplayId displayId) override;
+
 private:
     void onFirstRef() override;
 
diff --git a/services/surfaceflinger/ColorLayer.cpp b/services/surfaceflinger/ColorLayer.cpp
index 263f872..ad716ef 100644
--- a/services/surfaceflinger/ColorLayer.cpp
+++ b/services/surfaceflinger/ColorLayer.cpp
@@ -57,13 +57,12 @@
     return !isHiddenByPolicy() && getAlpha() > 0.0f;
 }
 
-void ColorLayer::setPerFrameData(const sp<const DisplayDevice>& display) {
-    const ui::Transform& tr = display->getTransform();
-    const auto& viewport = display->getViewport();
-    Region visible = tr.transform(visibleRegion.intersect(viewport));
-    const auto displayId = display->getId();
+void ColorLayer::setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                 const Rect& viewport, int32_t /* supportedPerFrameMetadata */) {
+    Region visible = transform.transform(visibleRegion.intersect(viewport));
+
     if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setPerFrameData: no HWC layer found (%d)",
+        ALOGE("[%s] failed to setPerFrameData: no HWC layer found for display %" PRIu64,
               mName.string(), displayId);
         return;
     }
diff --git a/services/surfaceflinger/ColorLayer.h b/services/surfaceflinger/ColorLayer.h
index 2febe9a..d1b1697 100644
--- a/services/surfaceflinger/ColorLayer.h
+++ b/services/surfaceflinger/ColorLayer.h
@@ -33,7 +33,8 @@
                         bool useIdentityTransform);
     bool isVisible() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& display) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool onPreComposition(nsecs_t /*refreshStartTime*/) override { return false; }
 
diff --git a/services/surfaceflinger/ContainerLayer.cpp b/services/surfaceflinger/ContainerLayer.cpp
index 44e843e..ca49f6c 100644
--- a/services/surfaceflinger/ContainerLayer.cpp
+++ b/services/surfaceflinger/ContainerLayer.cpp
@@ -32,6 +32,6 @@
     return !isHiddenByPolicy();
 }
 
-void ContainerLayer::setPerFrameData(const sp<const DisplayDevice>&) {}
+void ContainerLayer::setPerFrameData(DisplayId, const ui::Transform&, const Rect&, int32_t) {}
 
 } // namespace android
diff --git a/services/surfaceflinger/ContainerLayer.h b/services/surfaceflinger/ContainerLayer.h
index 316db83..413844b 100644
--- a/services/surfaceflinger/ContainerLayer.h
+++ b/services/surfaceflinger/ContainerLayer.h
@@ -33,7 +33,8 @@
                 bool useIdentityTransform) override;
     bool isVisible() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& display) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool isCreatedFromMainThread() const override { return true; }
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 91b18c9..7c5c1bc 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -212,15 +212,14 @@
 
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger,
                                                      const wp<IBinder>& displayToken,
-                                                     DisplayDevice::DisplayType type, int32_t id)
-      : flinger(flinger), displayToken(displayToken), type(type), id(id) {}
+                                                     const std::optional<DisplayId>& displayId)
+      : flinger(flinger), displayToken(displayToken), displayId(displayId) {}
 
 DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs&& args)
       : lastCompositionHadVisibleLayers(false),
         mFlinger(args.flinger),
-        mType(args.type),
-        mId(args.id),
         mDisplayToken(args.displayToken),
+        mId(args.displayId),
         mNativeWindow(args.nativeWindow),
         mDisplaySurface(args.displaySurface),
         mSurface{std::move(args.renderSurface)},
@@ -228,6 +227,7 @@
         mDisplayHeight(args.displayHeight),
         mDisplayInstallOrientation(args.displayInstallOrientation),
         mPageFlipCount(0),
+        mIsVirtual(args.isVirtual),
         mIsSecure(args.isSecure),
         mLayerStack(NO_LAYER_STACK),
         mOrientation(),
@@ -240,7 +240,8 @@
         mHasHdr10(false),
         mHasHLG(false),
         mHasDolbyVision(false),
-        mSupportedPerFrameMetadata(args.supportedPerFrameMetadata) {
+        mSupportedPerFrameMetadata(args.supportedPerFrameMetadata),
+        mIsPrimary(args.isPrimary) {
     populateColorModes(args.hwcColorModes);
 
     ALOGE_IF(!mNativeWindow, "No native window was set for display");
@@ -293,16 +294,12 @@
 DisplayDevice::~DisplayDevice() = default;
 
 void DisplayDevice::disconnect(HWComposer& hwc) {
-    if (mId >= 0) {
-        hwc.disconnectDisplay(mId);
-        mId = -1;
+    if (mId) {
+        hwc.disconnectDisplay(*mId);
+        mId.reset();
     }
 }
 
-bool DisplayDevice::isValid() const {
-    return mFlinger != nullptr;
-}
-
 int DisplayDevice::getWidth() const {
     return mDisplayWidth;
 }
@@ -334,9 +331,11 @@
 
 status_t DisplayDevice::prepareFrame(HWComposer& hwc,
         std::vector<CompositionInfo>& compositionData) {
-    status_t error = hwc.prepare(*this, compositionData);
-    if (error != NO_ERROR) {
-        return error;
+    if (mId) {
+        status_t error = hwc.prepare(*mId, compositionData);
+        if (error != NO_ERROR) {
+            return error;
+        }
     }
 
     DisplaySurface::CompositionType compositionType;
@@ -600,7 +599,7 @@
 
     // need to take care of primary display rotation for mGlobalTransform
     // for case if the panel is not installed aligned with device orientation
-    if (mType == DisplayType::DISPLAY_PRIMARY) {
+    if (isPrimary()) {
         DisplayDevice::orientationToTransfrom(
                 (orientation + mDisplayInstallOrientation) % (DisplayState::eOrientation270 + 1),
                 w, h, &R);
@@ -648,7 +647,7 @@
 }
 
 std::string DisplayDevice::getDebugName() const {
-    const auto id = mId >= 0 ? base::StringPrintf("%d, ", mId) : std::string();
+    const auto id = mId ? base::StringPrintf("%" PRIu64 ", ", *mId) : std::string();
     return base::StringPrintf("DisplayDevice{%s%s%s\"%s\"}", id.c_str(),
                               isPrimary() ? "primary, " : "", isVirtual() ? "virtual, " : "",
                               mDisplayName.c_str());
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 152d0ec..9ec7666 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <unordered_map>
 
@@ -37,6 +38,7 @@
 #include <utils/String8.h>
 #include <utils/Timers.h>
 
+#include "DisplayHardware/DisplayIdentification.h"
 #include "RenderArea.h"
 
 struct ANativeWindow;
@@ -66,25 +68,15 @@
     Region undefinedRegion;
     bool lastCompositionHadVisibleLayers;
 
-    enum DisplayType {
-        DISPLAY_ID_INVALID = -1,
-        DISPLAY_PRIMARY     = HWC_DISPLAY_PRIMARY,
-        DISPLAY_EXTERNAL    = HWC_DISPLAY_EXTERNAL,
-        DISPLAY_VIRTUAL     = HWC_DISPLAY_VIRTUAL,
-        NUM_BUILTIN_DISPLAY_TYPES = HWC_NUM_PHYSICAL_DISPLAY_TYPES,
-    };
-
     enum {
         NO_LAYER_STACK = 0xFFFFFFFF,
     };
 
     explicit DisplayDevice(DisplayDeviceCreationArgs&& args);
-
     ~DisplayDevice();
 
-    // whether this is a valid object. An invalid DisplayDevice is returned
-    // when an non existing id is requested
-    bool isValid() const;
+    bool isVirtual() const { return mIsVirtual; }
+    bool isPrimary() const { return mIsPrimary; }
 
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
@@ -118,11 +110,9 @@
     bool                    needsFiltering() const { return mNeedsFiltering; }
 
     uint32_t                getLayerStack() const { return mLayerStack; }
-    int32_t                 getDisplayType() const { return mType; }
-    bool                    isPrimary() const { return mType == DISPLAY_PRIMARY; }
-    bool                    isVirtual() const { return mType == DISPLAY_VIRTUAL; }
-    int32_t                 getId() const { return mId; }
-    const wp<IBinder>&      getDisplayToken() const { return mDisplayToken; }
+
+    const std::optional<DisplayId>& getId() const { return mId; }
+    const wp<IBinder>& getDisplayToken() const { return mDisplayToken; }
 
     int32_t getSupportedPerFrameMetadata() const { return mSupportedPerFrameMetadata; }
 
@@ -207,13 +197,10 @@
     void dump(String8& result) const;
 
 private:
-    /*
-     *  Constants, set during initialization
-     */
-    sp<SurfaceFlinger> mFlinger;
-    DisplayType mType;
-    int32_t mId;
-    wp<IBinder> mDisplayToken;
+    const sp<SurfaceFlinger> mFlinger;
+    const wp<IBinder> mDisplayToken;
+
+    std::optional<DisplayId> mId;
 
     // ANativeWindow this display is rendering into
     sp<ANativeWindow> mNativeWindow;
@@ -225,7 +212,9 @@
     const int       mDisplayInstallOrientation;
     mutable uint32_t mPageFlipCount;
     std::string     mDisplayName;
-    bool            mIsSecure;
+
+    const bool mIsVirtual;
+    const bool mIsSecure;
 
     /*
      * Can only accessed from the main thread, these members
@@ -299,13 +288,16 @@
             const ui::ColorMode mode, const ui::RenderIntent intent);
 
     std::unordered_map<ColorModeKey, ColorModeValue> mColorModes;
+
+    // TODO(b/74619554): Remove special cases for primary display.
+    const bool mIsPrimary;
 };
 
 struct DisplayDeviceState {
-    bool isVirtual() const { return type >= DisplayDevice::DISPLAY_VIRTUAL; }
+    bool isVirtual() const { return !displayId.has_value(); }
 
     int32_t sequenceId = sNextSequenceId++;
-    DisplayDevice::DisplayType type = DisplayDevice::DISPLAY_ID_INVALID;
+    std::optional<DisplayId> displayId;
     sp<IGraphicBufferProducer> surface;
     uint32_t layerStack = DisplayDevice::NO_LAYER_STACK;
     Rect viewport;
@@ -324,13 +316,13 @@
     // We use a constructor to ensure some of the values are set, without
     // assuming a default value.
     DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger, const wp<IBinder>& displayToken,
-                              DisplayDevice::DisplayType type, int32_t id);
+                              const std::optional<DisplayId>& displayId);
 
     const sp<SurfaceFlinger> flinger;
     const wp<IBinder> displayToken;
-    const DisplayDevice::DisplayType type;
-    const int32_t id;
+    const std::optional<DisplayId> displayId;
 
+    bool isVirtual{false};
     bool isSecure{false};
     sp<ANativeWindow> nativeWindow;
     sp<DisplaySurface> displaySurface;
@@ -343,6 +335,7 @@
     int32_t supportedPerFrameMetadata{0};
     std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
     int initialPowerMode{HWC_POWER_MODE_NORMAL};
+    bool isPrimary{false};
 };
 
 class DisplayRenderArea : public RenderArea {
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
index dcc4138..ec240f3 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
@@ -33,6 +33,9 @@
 
 constexpr size_t kEdidHeaderLength = 5;
 
+constexpr uint16_t kFallbackEdidManufacturerId = 0;
+constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
+
 std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
     if (view.size() < kEdidHeaderLength || view[0] || view[1] || view[2] || view[4]) {
         return {};
@@ -165,7 +168,8 @@
     return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
 }
 
-std::optional<DisplayId> generateDisplayId(uint8_t port, const DisplayIdentificationData& data) {
+std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
+        uint8_t port, const DisplayIdentificationData& data) {
     if (!isEdid(data)) {
         ALOGE("Display identification data has unknown format.");
         return {};
@@ -179,7 +183,16 @@
     // Hash display name instead of using product code or serial number, since the latter have been
     // observed to change on some displays with multiple inputs.
     const auto hash = static_cast<uint32_t>(std::hash<std::string_view>()(edid->displayName));
-    return getEdidDisplayId(port, edid->manufacturerId, hash);
+    return DisplayIdentificationInfo{getEdidDisplayId(port, edid->manufacturerId, hash),
+                                     std::string(edid->displayName)};
+}
+
+DisplayId getFallbackDisplayId(uint8_t port) {
+    return getEdidDisplayId(port, kFallbackEdidManufacturerId, 0);
+}
+
+DisplayId getVirtualDisplayId(uint32_t id) {
+    return getEdidDisplayId(0, kVirtualEdidManufacturerId, id);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
index 379f2d3..1f2e789 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
@@ -19,6 +19,7 @@
 #include <array>
 #include <cstdint>
 #include <optional>
+#include <string>
 #include <string_view>
 #include <vector>
 
@@ -27,6 +28,11 @@
 using DisplayId = uint64_t;
 using DisplayIdentificationData = std::vector<uint8_t>;
 
+struct DisplayIdentificationInfo {
+    DisplayId id;
+    std::string name;
+};
+
 // NUL-terminated plug and play ID.
 using PnpId = std::array<char, 4>;
 
@@ -40,6 +46,10 @@
 std::optional<Edid> parseEdid(const DisplayIdentificationData&);
 std::optional<PnpId> getPnpId(uint16_t manufacturerId);
 
-std::optional<DisplayId> generateDisplayId(uint8_t port, const DisplayIdentificationData&);
+std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
+        uint8_t port, const DisplayIdentificationData&);
+
+DisplayId getFallbackDisplayId(uint8_t port);
+DisplayId getVirtualDisplayId(uint32_t id);
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index e6d7834..f3f8d9d 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -52,26 +52,25 @@
  *
  */
 
-FramebufferSurface::FramebufferSurface(HWComposer& hwc, int disp,
-        const sp<IGraphicBufferConsumer>& consumer) :
-    ConsumerBase(consumer),
-    mDisplayType(disp),
-    mCurrentBufferSlot(-1),
-    mCurrentBuffer(),
-    mCurrentFence(Fence::NO_FENCE),
-    mHwc(hwc),
-    mHasPendingRelease(false),
-    mPreviousBufferSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mPreviousBuffer()
-{
-    ALOGV("Creating for display %d", disp);
+FramebufferSurface::FramebufferSurface(HWComposer& hwc, DisplayId displayId,
+                                       const sp<IGraphicBufferConsumer>& consumer)
+      : ConsumerBase(consumer),
+        mDisplayId(displayId),
+        mCurrentBufferSlot(-1),
+        mCurrentBuffer(),
+        mCurrentFence(Fence::NO_FENCE),
+        mHwc(hwc),
+        mHasPendingRelease(false),
+        mPreviousBufferSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mPreviousBuffer() {
+    ALOGV("Creating for display %" PRIu64, displayId);
 
     mName = "FramebufferSurface";
     mConsumer->setConsumerName(mName);
     mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_FB |
                                        GRALLOC_USAGE_HW_RENDER |
                                        GRALLOC_USAGE_HW_COMPOSER);
-    const auto& activeConfig = mHwc.getActiveConfig(disp);
+    const auto& activeConfig = mHwc.getActiveConfig(displayId);
     mConsumer->setDefaultBufferSize(activeConfig->getWidth(),
             activeConfig->getHeight());
     mConsumer->setMaxAcquiredBufferCount(
@@ -142,8 +141,7 @@
     mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer,
             &outSlot, &outBuffer);
     outDataspace = static_cast<Dataspace>(item.mDataSpace);
-    status_t result =
-            mHwc.setClientTarget(mDisplayType, outSlot, outFence, outBuffer, outDataspace);
+    status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
     if (result != NO_ERROR) {
         ALOGE("error posting framebuffer: %d", result);
         return result;
@@ -161,7 +159,7 @@
 
 void FramebufferSurface::onFrameCommitted() {
     if (mHasPendingRelease) {
-        sp<Fence> fence = mHwc.getPresentFence(mDisplayType);
+        sp<Fence> fence = mHwc.getPresentFence(mDisplayId);
         if (fence->isValid()) {
             status_t result = addReleaseFence(mPreviousBufferSlot,
                     mPreviousBuffer, fence);
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 0fd8e9e..2431dfd 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SF_FRAMEBUFFER_SURFACE_H
 #define ANDROID_SF_FRAMEBUFFER_SURFACE_H
 
+#include "DisplayIdentification.h"
 #include "DisplaySurface.h"
 #include "HWComposerBufferCache.h"
 
@@ -38,7 +39,8 @@
 class FramebufferSurface : public ConsumerBase,
                            public DisplaySurface {
 public:
-    FramebufferSurface(HWComposer& hwc, int disp, const sp<IGraphicBufferConsumer>& consumer);
+    FramebufferSurface(HWComposer& hwc, DisplayId displayId,
+                       const sp<IGraphicBufferConsumer>& consumer);
 
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType compositionType);
@@ -63,8 +65,7 @@
     status_t nextBuffer(uint32_t& outSlot, sp<GraphicBuffer>& outBuffer,
             sp<Fence>& outFence, ui::Dataspace& outDataspace);
 
-    // mDisplayType must match one of the HWC display types
-    int mDisplayType;
+    const DisplayId mDisplayId;
 
     // mCurrentBufferIndex is the slot index of the current buffer or
     // INVALID_BUFFER_SLOT to indicate that either there is no current buffer
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index d827fd2..4c472b8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -58,15 +58,15 @@
     ALOGE("%s failed for HWC display %" PRIu64 ": %s", __FUNCTION__, hwcDisplayId, msg)
 
 #define LOG_DISPLAY_ERROR(displayId, msg) \
-    ALOGE("%s failed for display %d: %s", __FUNCTION__, displayId, msg)
+    ALOGE("%s failed for display %" PRIu64 ": %s", __FUNCTION__, displayId, msg)
 
-#define LOG_HWC_ERROR(what, error, displayId)                                     \
-    ALOGE("%s: %s failed for display %d: %s (%d)", __FUNCTION__, what, displayId, \
+#define LOG_HWC_ERROR(what, error, displayId)                                              \
+    ALOGE("%s: %s failed for display %" PRIu64 ": %s (%d)", __FUNCTION__, what, displayId, \
           to_string(error).c_str(), static_cast<int32_t>(error))
 
 #define RETURN_IF_INVALID_DISPLAY(displayId, ...)            \
     do {                                                     \
-        if (!isValidDisplay(displayId)) {                    \
+        if (mDisplayData.count(displayId) == 0) {            \
             LOG_DISPLAY_ERROR(displayId, "Invalid display"); \
             return __VA_ARGS__;                              \
         }                                                    \
@@ -92,7 +92,9 @@
 HWComposer::HWComposer(std::unique_ptr<android::Hwc2::Composer> composer)
       : mHwcDevice(std::make_unique<HWC2::Device>(std::move(composer))) {}
 
-HWComposer::~HWComposer() = default;
+HWComposer::~HWComposer() {
+    mDisplayData.clear();
+}
 
 void HWComposer::registerCallback(HWC2::ComposerCallback* callback,
                                   int32_t sequenceId) {
@@ -116,11 +118,6 @@
     return mHwcDevice->getCapabilities().count(capability) > 0;
 }
 
-bool HWComposer::isValidDisplay(int32_t displayId) const {
-    return static_cast<size_t>(displayId) < mDisplayData.size() &&
-            mDisplayData[displayId].hwcDisplay;
-}
-
 void HWComposer::validateChange(HWC2::Composition from, HWC2::Composition to) {
     bool valid = true;
     switch (from) {
@@ -146,52 +143,50 @@
     }
 }
 
-std::optional<DisplayId> HWComposer::onHotplug(hwc2_display_t hwcDisplayId, int32_t displayType,
-                                               HWC2::Connection connection) {
-    if (displayType >= HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
-        ALOGE("Invalid display type of %d", displayType);
-        return {};
-    }
+std::optional<DisplayIdentificationInfo> HWComposer::onHotplug(hwc2_display_t hwcDisplayId,
+                                                               HWC2::Connection connection) {
+    std::optional<DisplayIdentificationInfo> info;
 
-    ALOGV("hotplug: %" PRIu64 ", %s %s", hwcDisplayId,
-          displayType == DisplayDevice::DISPLAY_PRIMARY ? "primary" : "external",
-          to_string(connection).c_str());
-    mHwcDevice->onHotplug(hwcDisplayId, connection);
-
-    std::optional<DisplayId> displayId;
-
-    if (connection == HWC2::Connection::Connected) {
-        uint8_t port;
-        DisplayIdentificationData data;
-        if (getDisplayIdentificationData(hwcDisplayId, &port, &data)) {
-            displayId = generateDisplayId(port, data);
-            ALOGE_IF(!displayId, "Failed to generate stable ID for display %" PRIu64, hwcDisplayId);
+    if (const auto displayId = toPhysicalDisplayId(hwcDisplayId)) {
+        info = DisplayIdentificationInfo{*displayId, std::string()};
+    } else {
+        if (connection == HWC2::Connection::Disconnected) {
+            ALOGE("Ignoring disconnection of invalid HWC display %" PRIu64, hwcDisplayId);
+            return {};
         }
+
+        info = onHotplugConnect(hwcDisplayId);
+        if (!info) return {};
     }
 
+    ALOGV("%s: %s %s display %" PRIu64 " with HWC ID %" PRIu64, __FUNCTION__,
+          to_string(connection).c_str(),
+          hwcDisplayId == mInternalHwcDisplayId ? "internal" : "external", info->id, hwcDisplayId);
+
+    mHwcDevice->onHotplug(hwcDisplayId, connection);
+
     // Disconnect is handled through HWComposer::disconnectDisplay via
     // SurfaceFlinger's onHotplugReceived callback handling
     if (connection == HWC2::Connection::Connected) {
-        mDisplayData[displayType].hwcDisplay = mHwcDevice->getDisplayById(hwcDisplayId);
-        mHwcDisplaySlots[hwcDisplayId] = displayType;
+        mDisplayData[info->id].hwcDisplay = mHwcDevice->getDisplayById(hwcDisplayId);
+        mPhysicalDisplayIdMap[hwcDisplayId] = info->id;
     }
 
-    return displayId;
+    return info;
 }
 
-bool HWComposer::onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp, int32_t* outDisplayId) {
-    const auto it = mHwcDisplaySlots.find(hwcDisplayId);
-    if (it == mHwcDisplaySlots.end()) {
-        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid display");
+bool HWComposer::onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp) {
+    const auto displayId = toPhysicalDisplayId(hwcDisplayId);
+    if (!displayId) {
+        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display");
         return false;
     }
 
-    const int32_t displayId = it->second;
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
 
-    const auto& displayData = mDisplayData[displayId];
+    const auto& displayData = mDisplayData[*displayId];
     if (displayData.isVirtual) {
-        LOG_DISPLAY_ERROR(displayId, "Invalid operation on virtual display");
+        LOG_DISPLAY_ERROR(*displayId, "Invalid operation on virtual display");
         return false;
     }
 
@@ -202,31 +197,26 @@
         // with the same timestamp when turning the display off and on. This
         // is a bug in the HWC implementation, but filter the extra events
         // out here so they don't cause havoc downstream.
-        if (timestamp == mLastHwVSync[displayId]) {
-            ALOGW("Ignoring duplicate VSYNC event from HWC (t=%" PRId64 ")",
-                    timestamp);
+        if (timestamp == mLastHwVSync[*displayId]) {
+            ALOGW("Ignoring duplicate VSYNC event from HWC for display %" PRIu64 " (t=%" PRId64 ")",
+                  *displayId, timestamp);
             return false;
         }
 
-        mLastHwVSync[displayId] = timestamp;
+        mLastHwVSync[*displayId] = timestamp;
     }
 
-    if (outDisplayId) {
-        *outDisplayId = displayId;
-    }
-
-    char tag[16];
-    snprintf(tag, sizeof(tag), "HW_VSYNC_%1u", displayId);
-    ATRACE_INT(tag, ++mVSyncCounts[displayId] & 1);
+    const auto tag = "HW_VSYNC_" + std::to_string(*displayId);
+    ATRACE_INT(tag.c_str(), ++mVSyncCounts[*displayId] & 1);
 
     return true;
 }
 
-status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height,
-        ui::PixelFormat* format, int32_t *outId) {
+std::optional<DisplayId> HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height,
+                                                            ui::PixelFormat* format) {
     if (mRemainingHwcVirtualDisplays == 0) {
         ALOGE("%s: No remaining virtual displays", __FUNCTION__);
-        return NO_MEMORY;
+        return {};
     }
 
     if (SurfaceFlinger::maxVirtualDisplaySize != 0 &&
@@ -234,41 +224,33 @@
          height > SurfaceFlinger::maxVirtualDisplaySize)) {
         ALOGE("%s: Display size %ux%u exceeds maximum dimension of %" PRIu64, __FUNCTION__, width,
               height, SurfaceFlinger::maxVirtualDisplaySize);
-        return INVALID_OPERATION;
+        return {};
     }
-
     HWC2::Display* display;
     auto error = mHwcDevice->createVirtualDisplay(width, height, format,
             &display);
     if (error != HWC2::Error::None) {
         ALOGE("%s: Failed to create HWC virtual display", __FUNCTION__);
-        return NO_MEMORY;
+        return {};
     }
 
-    size_t displaySlot = 0;
-    if (!mFreeDisplaySlots.empty()) {
-        displaySlot = *mFreeDisplaySlots.begin();
-        mFreeDisplaySlots.erase(displaySlot);
-    } else if (mDisplayData.size() < INT32_MAX) {
-        // Don't bother allocating a slot larger than we can return
-        displaySlot = mDisplayData.size();
-        mDisplayData.resize(displaySlot + 1);
+    DisplayId displayId;
+    if (mFreeVirtualDisplayIds.empty()) {
+        displayId = getVirtualDisplayId(mNextVirtualDisplayId++);
     } else {
-        ALOGE("%s: Unable to allocate a display slot", __FUNCTION__);
-        return NO_MEMORY;
+        displayId = *mFreeVirtualDisplayIds.begin();
+        mFreeVirtualDisplayIds.erase(displayId);
     }
 
-    auto& displayData = mDisplayData[displaySlot];
+    auto& displayData = mDisplayData[displayId];
     displayData.hwcDisplay = display;
     displayData.isVirtual = true;
 
     --mRemainingHwcVirtualDisplays;
-    *outId = static_cast<int32_t>(displaySlot);
-
-    return NO_ERROR;
+    return displayId;
 }
 
-HWC2::Layer* HWComposer::createLayer(int32_t displayId) {
+HWC2::Layer* HWComposer::createLayer(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId, nullptr);
 
     auto display = mDisplayData[displayId].hwcDisplay;
@@ -278,7 +260,7 @@
     return layer;
 }
 
-void HWComposer::destroyLayer(int32_t displayId, HWC2::Layer* layer) {
+void HWComposer::destroyLayer(DisplayId displayId, HWC2::Layer* layer) {
     RETURN_IF_INVALID_DISPLAY(displayId);
 
     auto display = mDisplayData[displayId].hwcDisplay;
@@ -286,7 +268,8 @@
     RETURN_IF_HWC_ERROR(error, displayId);
 }
 
-nsecs_t HWComposer::getRefreshTimestamp(int32_t displayId) const {
+nsecs_t HWComposer::getRefreshTimestamp(DisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, 0);
     // this returns the last refresh timestamp.
     // if the last one is not available, we estimate it based on
     // the refresh period and whatever closest timestamp we have.
@@ -296,17 +279,17 @@
     return now - ((now - mLastHwVSync[displayId]) % vsyncPeriod);
 }
 
-bool HWComposer::isConnected(int32_t displayId) const {
+bool HWComposer::isConnected(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return mDisplayData[displayId].hwcDisplay->isConnected();
+    return mDisplayData.at(displayId).hwcDisplay->isConnected();
 }
 
-std::vector<std::shared_ptr<const HWC2::Display::Config>>
-        HWComposer::getConfigs(int32_t displayId) const {
+std::vector<std::shared_ptr<const HWC2::Display::Config>> HWComposer::getConfigs(
+        DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
-    auto& displayData = mDisplayData[displayId];
-    auto configs = mDisplayData[displayId].hwcDisplay->getConfigs();
+    const auto& displayData = mDisplayData.at(displayId);
+    auto configs = displayData.hwcDisplay->getConfigs();
     if (displayData.configMap.empty()) {
         for (size_t i = 0; i < configs.size(); ++i) {
             displayData.configMap[i] = configs[i];
@@ -315,12 +298,12 @@
     return configs;
 }
 
-std::shared_ptr<const HWC2::Display::Config>
-        HWComposer::getActiveConfig(int32_t displayId) const {
+std::shared_ptr<const HWC2::Display::Config> HWComposer::getActiveConfig(
+        DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, nullptr);
 
     std::shared_ptr<const HWC2::Display::Config> config;
-    auto error = mDisplayData[displayId].hwcDisplay->getActiveConfig(&config);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getActiveConfig(&config);
     if (error == HWC2::Error::BadConfig) {
         LOG_DISPLAY_ERROR(displayId, "No active config");
         return nullptr;
@@ -336,11 +319,11 @@
     return config;
 }
 
-int HWComposer::getActiveConfigIndex(int32_t displayId) const {
+int HWComposer::getActiveConfigIndex(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, -1);
 
     int index;
-    auto error = mDisplayData[displayId].hwcDisplay->getActiveConfigIndex(&index);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getActiveConfigIndex(&index);
     if (error == HWC2::Error::BadConfig) {
         LOG_DISPLAY_ERROR(displayId, "No active config");
         return -1;
@@ -356,17 +339,17 @@
     return index;
 }
 
-std::vector<ui::ColorMode> HWComposer::getColorModes(int32_t displayId) const {
+std::vector<ui::ColorMode> HWComposer::getColorModes(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     std::vector<ui::ColorMode> modes;
-    auto error = mDisplayData[displayId].hwcDisplay->getColorModes(&modes);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getColorModes(&modes);
     RETURN_IF_HWC_ERROR(error, displayId, {});
     return modes;
 }
 
-status_t HWComposer::setActiveColorMode(int32_t displayId, ui::ColorMode mode,
-        ui::RenderIntent renderIntent) {
+status_t HWComposer::setActiveColorMode(DisplayId displayId, ui::ColorMode mode,
+                                        ui::RenderIntent renderIntent) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -379,8 +362,7 @@
     return NO_ERROR;
 }
 
-
-void HWComposer::setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled) {
+void HWComposer::setVsyncEnabled(DisplayId displayId, HWC2::Vsync enabled) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     auto& displayData = mDisplayData[displayId];
 
@@ -401,37 +383,30 @@
 
         displayData.vsyncEnabled = enabled;
 
-        char tag[16];
-        snprintf(tag, sizeof(tag), "HW_VSYNC_ON_%1u", displayId);
-        ATRACE_INT(tag, enabled == HWC2::Vsync::Enable ? 1 : 0);
+        const auto tag = "HW_VSYNC_ON_" + std::to_string(displayId);
+        ATRACE_INT(tag.c_str(), enabled == HWC2::Vsync::Enable ? 1 : 0);
     }
 }
 
-status_t HWComposer::setClientTarget(int32_t displayId, uint32_t slot,
-        const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
-        ui::Dataspace dataspace) {
+status_t HWComposer::setClientTarget(DisplayId displayId, uint32_t slot,
+                                     const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
+                                     ui::Dataspace dataspace) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
-    ALOGV("setClientTarget for display %d", displayId);
+    ALOGV("setClientTarget for display %" PRIu64, displayId);
     auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
     auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace);
     RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
     return NO_ERROR;
 }
 
-status_t HWComposer::prepare(DisplayDevice& display,
-        std::vector<CompositionInfo>& compositionData) {
+status_t HWComposer::prepare(DisplayId displayId, std::vector<CompositionInfo>& compositionData) {
     ATRACE_CALL();
 
-    Mutex::Autolock _l(mDisplayLock);
-    const auto displayId = display.getId();
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
-        ALOGV("Skipping HWComposer prepare for non-HWC display");
-        return NO_ERROR;
-    }
-
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
+    Mutex::Autolock _l(mDisplayLock);
+
     auto& displayData = mDisplayData[displayId];
     auto& hwcDisplay = displayData.hwcDisplay;
     if (!hwcDisplay->isConnected()) {
@@ -501,7 +476,8 @@
                     changedTypes[&*hwcLayer]);
             compositionInfo.compositionType = changedTypes[&*hwcLayer];
             compositionInfo.layer->mLayer->setCompositionType(displayId,
-                    compositionInfo.compositionType, false);
+                                                              compositionInfo.compositionType,
+                                                              false);
         }
 
         switch (compositionInfo.compositionType) {
@@ -540,49 +516,48 @@
     return NO_ERROR;
 }
 
-bool HWComposer::hasDeviceComposition(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasDeviceComposition(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are never composed by
         // the device
         return false;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return mDisplayData[displayId].hasDeviceComposition;
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
+    return mDisplayData.at(*displayId).hasDeviceComposition;
 }
 
-bool HWComposer::hasFlipClientTargetRequest(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasFlipClientTargetRequest(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are never composed by
         // the device
         return false;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return ((static_cast<uint32_t>(mDisplayData[displayId].displayRequests) &
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
+    return ((static_cast<uint32_t>(mDisplayData.at(*displayId).displayRequests) &
              static_cast<uint32_t>(HWC2::DisplayRequest::FlipClientTarget)) != 0);
 }
 
-bool HWComposer::hasClientComposition(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasClientComposition(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are always composed by
         // the client
         return true;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, true);
-    return mDisplayData[displayId].hasClientComposition;
+    RETURN_IF_INVALID_DISPLAY(*displayId, true);
+    return mDisplayData.at(*displayId).hasClientComposition;
 }
 
-sp<Fence> HWComposer::getPresentFence(int32_t displayId) const {
+sp<Fence> HWComposer::getPresentFence(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE);
-    return mDisplayData[displayId].lastPresentFence;
+    return mDisplayData.at(displayId).lastPresentFence;
 }
 
-sp<Fence> HWComposer::getLayerReleaseFence(int32_t displayId,
-        HWC2::Layer* layer) const {
+sp<Fence> HWComposer::getLayerReleaseFence(DisplayId displayId, HWC2::Layer* layer) const {
     RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE);
-    auto displayFences = mDisplayData[displayId].releaseFences;
+    auto displayFences = mDisplayData.at(displayId).releaseFences;
     if (displayFences.count(layer) == 0) {
         ALOGV("getLayerReleaseFence: Release fence not found");
         return Fence::NO_FENCE;
@@ -590,7 +565,7 @@
     return displayFences[layer];
 }
 
-status_t HWComposer::presentAndGetReleaseFences(int32_t displayId) {
+status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
     ATRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
@@ -618,8 +593,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setPowerMode(int32_t displayId, int32_t intMode) {
-    ALOGV("setPowerMode(%d, %d)", displayId, intMode);
+status_t HWComposer::setPowerMode(DisplayId displayId, int32_t intMode) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     const auto& displayData = mDisplayData[displayId];
@@ -675,7 +649,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setActiveConfig(int32_t displayId, size_t configId) {
+status_t HWComposer::setActiveConfig(DisplayId displayId, size_t configId) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -689,8 +663,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setColorTransform(int32_t displayId,
-        const mat4& transform) {
+status_t HWComposer::setColorTransform(DisplayId displayId, const mat4& transform) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -702,26 +675,34 @@
     return NO_ERROR;
 }
 
-void HWComposer::disconnectDisplay(int32_t displayId) {
+void HWComposer::disconnectDisplay(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     auto& displayData = mDisplayData[displayId];
 
     // If this was a virtual display, add its slot back for reuse by future
     // virtual displays
     if (displayData.isVirtual) {
-        mFreeDisplaySlots.insert(displayId);
+        mFreeVirtualDisplayIds.insert(displayId);
         ++mRemainingHwcVirtualDisplays;
     }
 
     const auto hwcDisplayId = displayData.hwcDisplay->getId();
-    mHwcDisplaySlots.erase(hwcDisplayId);
-    displayData = DisplayData();
+    mPhysicalDisplayIdMap.erase(hwcDisplayId);
+    mDisplayData.erase(displayId);
+    mVSyncCounts.erase(displayId);
+
+    // TODO(b/74619554): Select internal/external display from remaining displays.
+    if (hwcDisplayId == mInternalHwcDisplayId) {
+        mInternalHwcDisplayId.reset();
+    } else if (hwcDisplayId == mExternalHwcDisplayId) {
+        mExternalHwcDisplayId.reset();
+    }
 
     mHwcDevice->destroyDisplay(hwcDisplayId);
 }
 
-status_t HWComposer::setOutputBuffer(int32_t displayId,
-        const sp<Fence>& acquireFence, const sp<GraphicBuffer>& buffer) {
+status_t HWComposer::setOutputBuffer(DisplayId displayId, const sp<Fence>& acquireFence,
+                                     const sp<GraphicBuffer>& buffer) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto& displayData = mDisplayData[displayId];
 
@@ -735,13 +716,12 @@
     return NO_ERROR;
 }
 
-void HWComposer::clearReleaseFences(int32_t displayId) {
+void HWComposer::clearReleaseFences(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     mDisplayData[displayId].releaseFences.clear();
 }
 
-status_t HWComposer::getHdrCapabilities(
-        int32_t displayId, HdrCapabilities* outCapabilities) {
+status_t HWComposer::getHdrCapabilities(DisplayId displayId, HdrCapabilities* outCapabilities) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
@@ -750,22 +730,22 @@
     return NO_ERROR;
 }
 
-int32_t HWComposer::getSupportedPerFrameMetadata(int32_t displayId) const {
+int32_t HWComposer::getSupportedPerFrameMetadata(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, 0);
-    return mDisplayData[displayId].hwcDisplay->getSupportedPerFrameMetadata();
+    return mDisplayData.at(displayId).hwcDisplay->getSupportedPerFrameMetadata();
 }
 
-std::vector<ui::RenderIntent> HWComposer::getRenderIntents(int32_t displayId,
-        ui::ColorMode colorMode) const {
+std::vector<ui::RenderIntent> HWComposer::getRenderIntents(DisplayId displayId,
+                                                           ui::ColorMode colorMode) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     std::vector<ui::RenderIntent> renderIntents;
-    auto error = mDisplayData[displayId].hwcDisplay->getRenderIntents(colorMode, &renderIntents);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getRenderIntents(colorMode, &renderIntents);
     RETURN_IF_HWC_ERROR(error, displayId, {});
     return renderIntents;
 }
 
-mat4 HWComposer::getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace) {
+mat4 HWComposer::getDataspaceSaturationMatrix(DisplayId displayId, ui::Dataspace dataspace) {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     mat4 matrix;
@@ -806,12 +786,67 @@
     result.append(mHwcDevice->dump().c_str());
 }
 
-std::optional<hwc2_display_t>
-HWComposer::getHwcDisplayId(int32_t displayId) const {
-    if (!isValidDisplay(displayId)) {
+std::optional<DisplayId> HWComposer::toPhysicalDisplayId(hwc2_display_t hwcDisplayId) const {
+    if (const auto it = mPhysicalDisplayIdMap.find(hwcDisplayId);
+        it != mPhysicalDisplayIdMap.end()) {
+        return it->second;
+    }
+    return {};
+}
+
+std::optional<hwc2_display_t> HWComposer::fromPhysicalDisplayId(DisplayId displayId) const {
+    if (const auto it = mDisplayData.find(displayId);
+        it != mDisplayData.end() && !it->second.isVirtual) {
+        return it->second.hwcDisplay->getId();
+    }
+    return {};
+}
+
+std::optional<DisplayIdentificationInfo> HWComposer::onHotplugConnect(hwc2_display_t hwcDisplayId) {
+    if (isUsingVrComposer() && mInternalHwcDisplayId) {
+        ALOGE("Ignoring connection of external display %" PRIu64 " in VR mode", hwcDisplayId);
         return {};
     }
-    return mDisplayData[displayId].hwcDisplay->getId();
+
+    uint8_t port;
+    DisplayIdentificationData data;
+    const bool hasMultiDisplaySupport = getDisplayIdentificationData(hwcDisplayId, &port, &data);
+
+    if (mPhysicalDisplayIdMap.empty()) {
+        mHasMultiDisplaySupport = hasMultiDisplaySupport;
+        ALOGI("Switching to %s multi-display mode",
+              hasMultiDisplaySupport ? "generalized" : "legacy");
+    } else if (mHasMultiDisplaySupport && !hasMultiDisplaySupport) {
+        ALOGE("Ignoring connection of display %" PRIu64 " without identification data",
+              hwcDisplayId);
+        return {};
+    }
+
+    std::optional<DisplayIdentificationInfo> info;
+
+    if (mHasMultiDisplaySupport) {
+        info = parseDisplayIdentificationData(port, data);
+        ALOGE_IF(!info, "Failed to parse identification data for display %" PRIu64, hwcDisplayId);
+    } else if (mInternalHwcDisplayId && mExternalHwcDisplayId) {
+        ALOGE("Ignoring connection of tertiary display %" PRIu64, hwcDisplayId);
+        return {};
+    } else {
+        ALOGW_IF(hasMultiDisplaySupport, "Ignoring identification data for display %" PRIu64,
+                 hwcDisplayId);
+        port = mInternalHwcDisplayId ? HWC_DISPLAY_EXTERNAL : HWC_DISPLAY_PRIMARY;
+    }
+
+    if (!mInternalHwcDisplayId) {
+        mInternalHwcDisplayId = hwcDisplayId;
+    } else if (!mExternalHwcDisplayId) {
+        mExternalHwcDisplayId = hwcDisplayId;
+    }
+
+    if (info) return info;
+
+    return DisplayIdentificationInfo{getFallbackDisplayId(port),
+                                     hwcDisplayId == mInternalHwcDisplayId ? "Internal display"
+                                                                           : "External display"};
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 4777ca9..5074c2c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -34,7 +34,8 @@
 
 #include <memory>
 #include <optional>
-#include <set>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "DisplayIdentification.h"
@@ -82,101 +83,94 @@
 
     bool hasCapability(HWC2::Capability capability) const;
 
-    // Attempts to allocate a virtual display. If the virtual display is created
-    // on the HWC device, outId will contain its HWC ID.
-    status_t allocateVirtualDisplay(uint32_t width, uint32_t height,
-            ui::PixelFormat* format, int32_t* outId);
+    // Attempts to allocate a virtual display and returns its ID if created on the HWC device.
+    std::optional<DisplayId> allocateVirtualDisplay(uint32_t width, uint32_t height,
+                                                    ui::PixelFormat* format);
 
     // Attempts to create a new layer on this display
-    HWC2::Layer* createLayer(int32_t displayId);
+    HWC2::Layer* createLayer(DisplayId displayId);
     // Destroy a previously created layer
-    void destroyLayer(int32_t displayId, HWC2::Layer* layer);
+    void destroyLayer(DisplayId displayId, HWC2::Layer* layer);
 
     // Asks the HAL what it can do
-    status_t prepare(DisplayDevice& display,
-            std::vector<CompositionInfo>& compositionData);
+    status_t prepare(DisplayId displayId, std::vector<CompositionInfo>& compositionData);
 
-    status_t setClientTarget(int32_t displayId, uint32_t slot,
-            const sp<Fence>& acquireFence,
-            const sp<GraphicBuffer>& target, ui::Dataspace dataspace);
+    status_t setClientTarget(DisplayId displayId, uint32_t slot, const sp<Fence>& acquireFence,
+                             const sp<GraphicBuffer>& target, ui::Dataspace dataspace);
 
     // Present layers to the display and read releaseFences.
-    status_t presentAndGetReleaseFences(int32_t displayId);
+    status_t presentAndGetReleaseFences(DisplayId displayId);
 
     // set power mode
-    status_t setPowerMode(int32_t displayId, int mode);
+    status_t setPowerMode(DisplayId displayId, int mode);
 
     // set active config
-    status_t setActiveConfig(int32_t displayId, size_t configId);
+    status_t setActiveConfig(DisplayId displayId, size_t configId);
 
     // Sets a color transform to be applied to the result of composition
-    status_t setColorTransform(int32_t displayId, const mat4& transform);
+    status_t setColorTransform(DisplayId displayId, const mat4& transform);
 
     // reset state when an external, non-virtual display is disconnected
-    void disconnectDisplay(int32_t displayId);
+    void disconnectDisplay(DisplayId displayId);
 
     // does this display have layers handled by HWC
-    bool hasDeviceComposition(int32_t displayId) const;
+    bool hasDeviceComposition(const std::optional<DisplayId>& displayId) const;
 
     // does this display have pending request to flip client target
-    bool hasFlipClientTargetRequest(int32_t displayId) const;
+    bool hasFlipClientTargetRequest(const std::optional<DisplayId>& displayId) const;
 
     // does this display have layers handled by GLES
-    bool hasClientComposition(int32_t displayId) const;
+    bool hasClientComposition(const std::optional<DisplayId>& displayId) const;
 
     // get the present fence received from the last call to present.
-    sp<Fence> getPresentFence(int32_t displayId) const;
+    sp<Fence> getPresentFence(DisplayId displayId) const;
 
     // Get last release fence for the given layer
-    sp<Fence> getLayerReleaseFence(int32_t displayId,
-            HWC2::Layer* layer) const;
+    sp<Fence> getLayerReleaseFence(DisplayId displayId, HWC2::Layer* layer) const;
 
     // Set the output buffer and acquire fence for a virtual display.
     // Returns INVALID_OPERATION if displayId is not a virtual display.
-    status_t setOutputBuffer(int32_t displayId, const sp<Fence>& acquireFence,
-            const sp<GraphicBuffer>& buf);
+    status_t setOutputBuffer(DisplayId displayId, const sp<Fence>& acquireFence,
+                             const sp<GraphicBuffer>& buffer);
 
     // After SurfaceFlinger has retrieved the release fences for all the frames,
     // it can call this to clear the shared pointers in the release fence map
-    void clearReleaseFences(int32_t displayId);
+    void clearReleaseFences(DisplayId displayId);
 
     // Fetches the HDR capabilities of the given display
-    status_t getHdrCapabilities(int32_t displayId, HdrCapabilities* outCapabilities);
+    status_t getHdrCapabilities(DisplayId displayId, HdrCapabilities* outCapabilities);
 
-    int32_t getSupportedPerFrameMetadata(int32_t displayId) const;
+    int32_t getSupportedPerFrameMetadata(DisplayId displayId) const;
 
     // Returns the available RenderIntent of the given display.
-    std::vector<ui::RenderIntent> getRenderIntents(int32_t displayId, ui::ColorMode colorMode) const;
+    std::vector<ui::RenderIntent> getRenderIntents(DisplayId displayId,
+                                                   ui::ColorMode colorMode) const;
 
-    mat4 getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace);
+    mat4 getDataspaceSaturationMatrix(DisplayId displayId, ui::Dataspace dataspace);
 
     // Events handling ---------------------------------------------------------
 
-    // Returns true if successful, false otherwise. The
-    // DisplayDevice::DisplayType of the display is returned as an output param.
-    bool onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp, int32_t* outDisplay);
-    std::optional<DisplayId> onHotplug(hwc2_display_t hwcDisplayId, int32_t displayType,
-                                       HWC2::Connection connection);
+    // Returns stable display ID (and display name on connection of new or previously disconnected
+    // display), or std::nullopt if hotplug event was ignored.
+    std::optional<DisplayIdentificationInfo> onHotplug(hwc2_display_t hwcDisplayId,
+                                                       HWC2::Connection connection);
 
-    void setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled);
+    bool onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp);
+    void setVsyncEnabled(DisplayId displayId, HWC2::Vsync enabled);
 
-    // Query display parameters.  Pass in a display index (e.g.
-    // HWC_DISPLAY_PRIMARY).
-    nsecs_t getRefreshTimestamp(int32_t displayId) const;
-    bool isConnected(int32_t displayId) const;
+    nsecs_t getRefreshTimestamp(DisplayId displayId) const;
+    bool isConnected(DisplayId displayId) const;
 
     // Non-const because it can update configMap inside of mDisplayData
-    std::vector<std::shared_ptr<const HWC2::Display::Config>>
-            getConfigs(int32_t displayId) const;
+    std::vector<std::shared_ptr<const HWC2::Display::Config>> getConfigs(DisplayId displayId) const;
 
-    std::shared_ptr<const HWC2::Display::Config>
-            getActiveConfig(int32_t displayId) const;
-    int getActiveConfigIndex(int32_t displayId) const;
+    std::shared_ptr<const HWC2::Display::Config> getActiveConfig(DisplayId displayId) const;
+    int getActiveConfigIndex(DisplayId displayId) const;
 
-    std::vector<ui::ColorMode> getColorModes(int32_t displayId) const;
+    std::vector<ui::ColorMode> getColorModes(DisplayId displayId) const;
 
-    status_t setActiveColorMode(int32_t displayId, ui::ColorMode mode,
-            ui::RenderIntent renderIntent);
+    status_t setActiveColorMode(DisplayId displayId, ui::ColorMode mode,
+                                ui::RenderIntent renderIntent);
 
     bool isUsingVrComposer() const;
 
@@ -185,12 +179,19 @@
 
     android::Hwc2::Composer* getComposer() const { return mHwcDevice->getComposer(); }
 
-    std::optional<hwc2_display_t> getHwcDisplayId(int32_t displayId) const;
+    // TODO(b/74619554): Remove special cases for internal/external display.
+    std::optional<hwc2_display_t> getInternalHwcDisplayId() const { return mInternalHwcDisplayId; }
+    std::optional<hwc2_display_t> getExternalHwcDisplayId() const { return mExternalHwcDisplayId; }
+
+    std::optional<DisplayId> toPhysicalDisplayId(hwc2_display_t hwcDisplayId) const;
+    std::optional<hwc2_display_t> fromPhysicalDisplayId(DisplayId displayId) const;
+
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
 
-    bool isValidDisplay(int32_t displayId) const;
+    std::optional<DisplayIdentificationInfo> onHotplugConnect(hwc2_display_t hwcDisplayId);
+
     static void validateChange(HWC2::Composition from, HWC2::Composition to);
 
     struct cb_context;
@@ -215,21 +216,30 @@
         HWC2::Error presentError;
     };
 
-    std::unique_ptr<HWC2::Device>   mHwcDevice;
-    std::vector<DisplayData> mDisplayData{HWC_NUM_PHYSICAL_DISPLAY_TYPES};
-    std::set<size_t>                mFreeDisplaySlots;
-    std::unordered_map<hwc2_display_t, int32_t> mHwcDisplaySlots;
+    std::unordered_map<DisplayId, DisplayData> mDisplayData;
+
+    // This must be destroyed before mDisplayData, because destructor may call back into HWComposer
+    // and look up DisplayData.
+    std::unique_ptr<HWC2::Device> mHwcDevice;
+
+    std::unordered_map<hwc2_display_t, DisplayId> mPhysicalDisplayIdMap;
+    std::optional<hwc2_display_t> mInternalHwcDisplayId;
+    std::optional<hwc2_display_t> mExternalHwcDisplayId;
+    bool mHasMultiDisplaySupport = false;
+
     // protect mDisplayData from races between prepare and dump
     mutable Mutex mDisplayLock;
 
     cb_context* mCBContext = nullptr;
-    size_t mVSyncCounts[HWC_NUM_PHYSICAL_DISPLAY_TYPES]{0, 0};
+    std::unordered_map<DisplayId, size_t> mVSyncCounts;
+
+    std::unordered_set<uint32_t> mFreeVirtualDisplayIds;
+    uint32_t mNextVirtualDisplayId = 0;
     uint32_t mRemainingHwcVirtualDisplays{mHwcDevice->getMaxVirtualDisplayCount()};
 
     // protected by mLock
     mutable Mutex mLock;
-    mutable std::unordered_map<int32_t, nsecs_t> mLastHwVSync{
-            {{HWC_DISPLAY_PRIMARY, 0}, {HWC_DISPLAY_EXTERNAL, 0}}};
+    mutable std::unordered_map<DisplayId, nsecs_t> mLastHwVSync;
 
     // thread-safe
     mutable Mutex mVsyncLock;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index c111a27..27d3dc5 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -48,34 +48,34 @@
     }
 }
 
-VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
-        const sp<IGraphicBufferProducer>& sink,
-        const sp<IGraphicBufferProducer>& bqProducer,
-        const sp<IGraphicBufferConsumer>& bqConsumer,
-        const std::string& name)
-:   ConsumerBase(bqConsumer),
-    mHwc(hwc),
-    mDisplayId(dispId),
-    mDisplayName(name),
-    mSource{},
-    mDefaultOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
-    mOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
-    mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
-    mProducerSlotSource(0),
-    mProducerBuffers(),
-    mQueueBufferOutput(),
-    mSinkBufferWidth(0),
-    mSinkBufferHeight(0),
-    mCompositionType(COMPOSITION_UNKNOWN),
-    mFbFence(Fence::NO_FENCE),
-    mOutputFence(Fence::NO_FENCE),
-    mFbProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mOutputProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mDbgState(DBG_STATE_IDLE),
-    mDbgLastCompositionType(COMPOSITION_UNKNOWN),
-    mMustRecompose(false),
-    mForceHwcCopy(SurfaceFlinger::useHwcForRgbToYuv)
-{
+VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc,
+                                             const std::optional<DisplayId>& displayId,
+                                             const sp<IGraphicBufferProducer>& sink,
+                                             const sp<IGraphicBufferProducer>& bqProducer,
+                                             const sp<IGraphicBufferConsumer>& bqConsumer,
+                                             const std::string& name)
+      : ConsumerBase(bqConsumer),
+        mHwc(hwc),
+        mDisplayId(displayId),
+        mDisplayName(name),
+        mSource{},
+        mDefaultOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
+        mOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
+        mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
+        mProducerSlotSource(0),
+        mProducerBuffers(),
+        mQueueBufferOutput(),
+        mSinkBufferWidth(0),
+        mSinkBufferHeight(0),
+        mCompositionType(COMPOSITION_UNKNOWN),
+        mFbFence(Fence::NO_FENCE),
+        mOutputFence(Fence::NO_FENCE),
+        mFbProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mOutputProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mDbgState(DBG_STATE_IDLE),
+        mDbgLastCompositionType(COMPOSITION_UNKNOWN),
+        mMustRecompose(false),
+        mForceHwcCopy(SurfaceFlinger::useHwcForRgbToYuv) {
     mSource[SOURCE_SINK] = sink;
     mSource[SOURCE_SCRATCH] = bqProducer;
 
@@ -116,8 +116,9 @@
 }
 
 status_t VirtualDisplaySurface::beginFrame(bool mustRecompose) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     mMustRecompose = mustRecompose;
 
@@ -129,8 +130,9 @@
 }
 
 status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_BEGUN,
             "Unexpected prepareFrame() in %s state", dbgStateStr());
@@ -177,8 +179,9 @@
 }
 
 status_t VirtualDisplaySurface::advanceFrame() {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     if (mCompositionType == COMPOSITION_HWC) {
         VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
@@ -211,7 +214,7 @@
 
     // At this point we know the output buffer acquire fence,
     // so update HWC state with it.
-    mHwc.setOutputBuffer(mDisplayId, mOutputFence, outBuffer);
+    mHwc.setOutputBuffer(*mDisplayId, mOutputFence, outBuffer);
 
     status_t result = NO_ERROR;
     if (fbBuffer != nullptr) {
@@ -221,22 +224,23 @@
                 &hwcSlot, &hwcBuffer);
 
         // TODO: Correctly propagate the dataspace from GL composition
-        result = mHwc.setClientTarget(mDisplayId, hwcSlot, mFbFence,
-                hwcBuffer, ui::Dataspace::UNKNOWN);
+        result = mHwc.setClientTarget(*mDisplayId, hwcSlot, mFbFence, hwcBuffer,
+                                      ui::Dataspace::UNKNOWN);
     }
 
     return result;
 }
 
 void VirtualDisplaySurface::onFrameCommitted() {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return;
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_HWC,
             "Unexpected onFrameCommitted() in %s state", dbgStateStr());
     mDbgState = DBG_STATE_IDLE;
 
-    sp<Fence> retireFence = mHwc.getPresentFence(mDisplayId);
+    sp<Fence> retireFence = mHwc.getPresentFence(*mDisplayId);
     if (mCompositionType == COMPOSITION_MIXED && mFbProducerSlot >= 0) {
         // release the scratch buffer back to the pool
         Mutex::Autolock lock(mMutex);
@@ -291,8 +295,9 @@
 
 status_t VirtualDisplaySurface::requestBuffer(int pslot,
         sp<GraphicBuffer>* outBuf) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected requestBuffer pslot=%d in %s state",
@@ -313,7 +318,7 @@
 
 status_t VirtualDisplaySurface::dequeueBuffer(Source source,
         PixelFormat format, uint64_t usage, int* sslot, sp<Fence>* fence) {
-    LOG_FATAL_IF(mDisplayId < 0, "mDisplayId=%d but should not be < 0.", mDisplayId);
+    LOG_FATAL_IF(!mDisplayId);
 
     status_t result =
             mSource[source]->dequeueBuffer(sslot, fence, mSinkBufferWidth, mSinkBufferHeight,
@@ -359,7 +364,7 @@
                                               PixelFormat format, uint64_t usage,
                                               uint64_t* outBufferAge,
                                               FrameEventHistoryDelta* outTimestamps) {
-    if (mDisplayId < 0) {
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->dequeueBuffer(pslot, fence, w, h, format, usage, outBufferAge,
                                                    outTimestamps);
     }
@@ -446,8 +451,9 @@
 
 status_t VirtualDisplaySurface::queueBuffer(int pslot,
         const QueueBufferInput& input, QueueBufferOutput* output) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
@@ -504,8 +510,9 @@
 
 status_t VirtualDisplaySurface::cancelBuffer(int pslot,
         const sp<Fence>& fence) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
@@ -616,6 +623,8 @@
 }
 
 status_t VirtualDisplaySurface::refreshOutputBuffer() {
+    LOG_FATAL_IF(!mDisplayId);
+
     if (mOutputProducerSlot >= 0) {
         mSource[SOURCE_SINK]->cancelBuffer(
                 mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot),
@@ -633,8 +642,8 @@
     // until after GLES calls queueBuffer(). So here we just set the buffer
     // (for use in HWC prepare) but not the fence; we'll call this again with
     // the proper fence once we have it.
-    result = mHwc.setOutputBuffer(mDisplayId, Fence::NO_FENCE,
-            mProducerBuffers[mOutputProducerSlot]);
+    result = mHwc.setOutputBuffer(*mDisplayId, Fence::NO_FENCE,
+                                  mProducerBuffers[mOutputProducerSlot]);
 
     return result;
 }
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 4bd4d0f..33678df 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -17,8 +17,10 @@
 #ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 #define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 
+#include <optional>
 #include <string>
 
+#include "DisplayIdentification.h"
 #include "DisplaySurface.h"
 #include "HWComposerBufferCache.h"
 
@@ -75,11 +77,10 @@
                               public BnGraphicBufferProducer,
                               private ConsumerBase {
 public:
-    VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
-            const sp<IGraphicBufferProducer>& sink,
-            const sp<IGraphicBufferProducer>& bqProducer,
-            const sp<IGraphicBufferConsumer>& bqConsumer,
-            const std::string& name);
+    VirtualDisplaySurface(HWComposer& hwc, const std::optional<DisplayId>& displayId,
+                          const sp<IGraphicBufferProducer>& sink,
+                          const sp<IGraphicBufferProducer>& bqProducer,
+                          const sp<IGraphicBufferConsumer>& bqConsumer, const std::string& name);
 
     //
     // DisplaySurface interface
@@ -154,7 +155,7 @@
     // Immutable after construction
     //
     HWComposer& mHwc;
-    const int32_t mDisplayId;
+    const std::optional<DisplayId> mDisplayId;
     const std::string mDisplayName;
     sp<IGraphicBufferProducer> mSource[2]; // indexed by SOURCE_*
     uint32_t mDefaultOutputFormat;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f29dfc0..f41a753 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -196,9 +196,9 @@
 // h/w composer set-up
 // ---------------------------------------------------------------------------
 
-bool Layer::createHwcLayer(HWComposer* hwc, int32_t displayId) {
+bool Layer::createHwcLayer(HWComposer* hwc, DisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(getBE().mHwcLayers.count(displayId) != 0,
-                        "Already have a layer for display %d", displayId);
+                        "Already have a layer for display %" PRIu64, displayId);
     auto layer = std::shared_ptr<HWC2::Layer>(
             hwc->createLayer(displayId),
             [hwc, displayId](HWC2::Layer* layer) {
@@ -214,7 +214,7 @@
     return true;
 }
 
-bool Layer::destroyHwcLayer(int32_t displayId) {
+bool Layer::destroyHwcLayer(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         return false;
     }
@@ -461,12 +461,13 @@
 
 void Layer::setGeometry(const sp<const DisplayDevice>& display, uint32_t z) {
     const auto displayId = display->getId();
-    if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setGeometry: no HWC layer found (%d)",
-              mName.string(), displayId);
+    LOG_ALWAYS_FATAL_IF(!displayId);
+    if (!hasHwcLayer(*displayId)) {
+        ALOGE("[%s] failed to setGeometry: no HWC layer found for display %" PRIu64, mName.string(),
+              *displayId);
         return;
     }
-    auto& hwcInfo = getBE().mHwcLayers[displayId];
+    auto& hwcInfo = getBE().mHwcLayers[*displayId];
 
     // enable this layer
     hwcInfo.forceClientComposition = false;
@@ -618,7 +619,7 @@
     if (orientation & ui::Transform::ROT_INVALID) {
         // we can only handle simple transformation
         hwcInfo.forceClientComposition = true;
-        getBE().mHwcLayers[displayId].compositionType = HWC2::Composition::Client;
+        getBE().mHwcLayers[*displayId].compositionType = HWC2::Composition::Client;
     } else {
         auto transform = static_cast<HWC2::Transform>(orientation);
         hwcInfo.transform = transform;
@@ -632,18 +633,18 @@
     }
 }
 
-void Layer::forceClientComposition(int32_t displayId) {
+void Layer::forceClientComposition(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
-        ALOGE("forceClientComposition: no HWC layer found (%d)", displayId);
+        ALOGE("forceClientComposition: no HWC layer found (display %" PRIu64 ")", displayId);
         return;
     }
 
     getBE().mHwcLayers[displayId].forceClientComposition = true;
 }
 
-bool Layer::getForceClientComposition(int32_t displayId) {
+bool Layer::getForceClientComposition(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
-        ALOGE("getForceClientComposition: no HWC layer found (%d)", displayId);
+        ALOGE("getForceClientComposition: no HWC layer found (display %" PRIu64 ")", displayId);
         return false;
     }
 
@@ -652,7 +653,7 @@
 
 void Layer::updateCursorPosition(const sp<const DisplayDevice>& display) {
     const auto displayId = display->getId();
-    if (getBE().mHwcLayers.count(displayId) == 0 ||
+    if (getBE().mHwcLayers.count(*displayId) == 0 ||
         getCompositionType(displayId) != HWC2::Composition::Cursor) {
         return;
     }
@@ -675,8 +676,8 @@
     auto position = displayTransform.transform(frame);
 
     auto error =
-            (getBE().mHwcLayers[displayId].layer)->setCursorPosition(
-                    position.left, position.top);
+            getBE().mHwcLayers[*displayId].layer->setCursorPosition(position.left, position.top);
+
     ALOGE_IF(error != HWC2::Error::None,
              "[%s] Failed to set cursor position "
              "to (%d, %d): %s (%d)",
@@ -708,7 +709,7 @@
     clearWithOpenGL(renderArea, 0, 0, 0, 0);
 }
 
-void Layer::setCompositionType(int32_t displayId, HWC2::Composition type, bool callIntoHwc) {
+void Layer::setCompositionType(DisplayId displayId, HWC2::Composition type, bool callIntoHwc) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("setCompositionType called without a valid HWC layer");
         return;
@@ -731,16 +732,20 @@
     }
 }
 
-HWC2::Composition Layer::getCompositionType(int32_t displayId) const {
-    if (getBE().mHwcLayers.count(displayId) == 0) {
+HWC2::Composition Layer::getCompositionType(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // If we're querying the composition type for a display that does not
         // have a HWC counterpart, then it will always be Client
         return HWC2::Composition::Client;
     }
-    return getBE().mHwcLayers[displayId].compositionType;
+    if (getBE().mHwcLayers.count(*displayId) == 0) {
+        ALOGE("getCompositionType called with an invalid HWC layer");
+        return HWC2::Composition::Invalid;
+    }
+    return getBE().mHwcLayers.at(*displayId).compositionType;
 }
 
-void Layer::setClearClientTarget(int32_t displayId, bool clear) {
+void Layer::setClearClientTarget(DisplayId displayId, bool clear) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("setClearClientTarget called without a valid HWC layer");
         return;
@@ -748,7 +753,7 @@
     getBE().mHwcLayers[displayId].clearClientTarget = clear;
 }
 
-bool Layer::getClearClientTarget(int32_t displayId) const {
+bool Layer::getClearClientTarget(DisplayId displayId) const {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("getClearClientTarget called without a valid HWC layer");
         return false;
@@ -1416,7 +1421,7 @@
     result.append("-----------------------------\n");
 }
 
-void Layer::miniDump(String8& result, int32_t displayId) const {
+void Layer::miniDump(String8& result, DisplayId displayId) const {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         return;
     }
@@ -1980,7 +1985,7 @@
     }
 }
 
-void Layer::writeToProto(LayerProto* layerInfo, int32_t displayId) {
+void Layer::writeToProto(LayerProto* layerInfo, DisplayId displayId) {
     if (!hasHwcLayer(displayId)) {
         return;
     }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 6921ca9..ced6532 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -38,6 +38,7 @@
 
 #include <cstdint>
 #include <list>
+#include <optional>
 #include <vector>
 
 #include "Client.h"
@@ -351,7 +352,7 @@
     void writeToProto(LayerProto* layerInfo,
                       LayerVector::StateSet stateSet = LayerVector::StateSet::Drawing);
 
-    void writeToProto(LayerProto* layerInfo, int32_t displayId);
+    void writeToProto(LayerProto* layerInfo, DisplayId displayId);
 
     virtual Geometry getActiveGeometry(const Layer::State& s) const { return s.active_legacy; }
     virtual uint32_t getActiveWidth(const Layer::State& s) const { return s.active_legacy.w; }
@@ -377,16 +378,17 @@
     virtual bool isHdrY410() const { return false; }
 
     void setGeometry(const sp<const DisplayDevice>& display, uint32_t z);
-    void forceClientComposition(int32_t displayId);
-    bool getForceClientComposition(int32_t displayId);
-    virtual void setPerFrameData(const sp<const DisplayDevice>& display) = 0;
+    void forceClientComposition(DisplayId displayId);
+    bool getForceClientComposition(DisplayId displayId);
+    virtual void setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                 const Rect& viewport, int32_t supportedPerFrameMetadata) = 0;
 
     // callIntoHwc exists so we can update our local state and call
     // acceptDisplayChanges without unnecessarily updating the device's state
-    void setCompositionType(int32_t displayId, HWC2::Composition type, bool callIntoHwc = true);
-    HWC2::Composition getCompositionType(int32_t displayId) const;
-    void setClearClientTarget(int32_t displayId, bool clear);
-    bool getClearClientTarget(int32_t displayId) const;
+    void setCompositionType(DisplayId displayId, HWC2::Composition type, bool callIntoHwc = true);
+    HWC2::Composition getCompositionType(const std::optional<DisplayId>& displayId) const;
+    void setClearClientTarget(DisplayId displayId, bool clear);
+    bool getClearClientTarget(DisplayId displayId) const;
     void updateCursorPosition(const sp<const DisplayDevice>& display);
 
     /*
@@ -407,7 +409,8 @@
      * called after composition.
      * returns true if the layer latched a new buffer this frame.
      */
-    virtual bool onPostComposition(const std::shared_ptr<FenceTime>& /*glDoneFence*/,
+    virtual bool onPostComposition(const std::optional<DisplayId>& /*displayId*/,
+                                   const std::shared_ptr<FenceTime>& /*glDoneFence*/,
                                    const std::shared_ptr<FenceTime>& /*presentFence*/,
                                    const CompositorTiming& /*compositorTiming*/) {
         return false;
@@ -492,24 +495,24 @@
 
     // -----------------------------------------------------------------------
 
-    bool createHwcLayer(HWComposer* hwc, int32_t displayId);
-    bool destroyHwcLayer(int32_t displayId);
+    bool createHwcLayer(HWComposer* hwc, DisplayId displayId);
+    bool destroyHwcLayer(DisplayId displayId);
     void destroyAllHwcLayers();
 
-    bool hasHwcLayer(int32_t displayId) { return getBE().mHwcLayers.count(displayId) > 0; }
+    bool hasHwcLayer(DisplayId displayId) { return getBE().mHwcLayers.count(displayId) > 0; }
 
-    HWC2::Layer* getHwcLayer(int32_t displayId) {
-        if (getBE().mHwcLayers.count(displayId) == 0) {
+    HWC2::Layer* getHwcLayer(DisplayId displayId) {
+        if (!hasHwcLayer(displayId)) {
             return nullptr;
         }
         return getBE().mHwcLayers[displayId].layer.get();
     }
 
-    bool setHwcLayer(int32_t hwcId) {
-        if (getBE().mHwcLayers.count(hwcId) == 0) {
+    bool setHwcLayer(DisplayId displayId) {
+        if (!hasHwcLayer(displayId)) {
             return false;
         }
-        getBE().compositionInfo.hwc.hwcLayer = getBE().mHwcLayers[hwcId].layer;
+        getBE().compositionInfo.hwc.hwcLayer = getBE().mHwcLayers[displayId].layer;
         return true;
     }
 
@@ -524,7 +527,7 @@
 
     /* always call base class first */
     static void miniDumpHeader(String8& result);
-    void miniDump(String8& result, int32_t displayId) const;
+    void miniDump(String8& result, DisplayId displayId) const;
     void dumpFrameStats(String8& result) const;
     void dumpFrameEvents(String8& result);
     void clearFrameStats();
diff --git a/services/surfaceflinger/LayerBE.h b/services/surfaceflinger/LayerBE.h
index 463c46c..3f5134e 100644
--- a/services/surfaceflinger/LayerBE.h
+++ b/services/surfaceflinger/LayerBE.h
@@ -24,6 +24,7 @@
 #include <renderengine/Texture.h>
 #include <ui/Region.h>
 
+#include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/HWComposerBufferCache.h"
 #include "SurfaceFlinger.h"
@@ -41,7 +42,7 @@
     std::shared_ptr<LayerBE> layer;
     struct {
         std::shared_ptr<HWC2::Layer> hwcLayer;
-        int32_t displayId = -1;
+        DisplayId displayId;
         sp<Fence> fence;
         HWC2::BlendMode blendMode = HWC2::BlendMode::Invalid;
         Rect displayFrame;
@@ -123,12 +124,11 @@
         HWC2::Transform transform;
     };
 
-
     // A layer can be attached to multiple displays when operating in mirror mode
     // (a.k.a: when several displays are attached with equal layerStack). In this
     // case we need to keep track. In non-mirror mode, a layer will have only one
-    // HWCInfo. This map key is a display layerStack.
-    std::unordered_map<int32_t, HWCInfo> mHwcLayers;
+    // HWCInfo.
+    std::unordered_map<DisplayId, HWCInfo> mHwcLayers;
 
     CompositionInfo compositionInfo;
 };
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index dec08fd..276aa85 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -105,8 +105,6 @@
 
 #include <layerproto/LayerProtoParser.h>
 
-#define DISPLAY_COUNT       1
-
 namespace android {
 
 using namespace android::hardware::configstore;
@@ -173,6 +171,11 @@
     bool mLocked;
 };
 
+// Currently we only support V0_SRGB and DISPLAY_P3 as composition preference.
+bool validateCompositionDataspace(Dataspace dataspace) {
+    return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
+}
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -191,7 +194,6 @@
 bool SurfaceFlinger::hasSyncFramework;
 bool SurfaceFlinger::useVrFlinger;
 int64_t SurfaceFlinger::maxFrameBufferAcquiredBuffers;
-// TODO(courtneygo): Rename hasWideColorDisplay to clarify its actual meaning.
 bool SurfaceFlinger::hasWideColorDisplay;
 int SurfaceFlinger::primaryDisplayOrientation = DisplayState::eOrientationDefault;
 bool SurfaceFlinger::useColorManagement;
@@ -247,7 +249,6 @@
         mLayersRemoved(false),
         mLayersAdded(false),
         mBootTime(systemTime()),
-        mDisplayTokens(),
         mVisibleRegionsDirty(false),
         mGeometryInvalid(false),
         mAnimCompositionPending(false),
@@ -313,6 +314,8 @@
                     wideColorGamutCompositionPixelFormat = tmpWideColorGamutPixelFormat;
                 });
     }
+    mDefaultCompositionDataspace = defaultCompositionDataspace;
+    mWideColorGamutCompositionDataspace = wideColorGamutCompositionDataspace;
 
     useContextPriority = getBool<ISurfaceFlingerConfigs,
                                  &ISurfaceFlingerConfigs::useContextPriority>(true);
@@ -482,40 +485,49 @@
     sp<BBinder> token = new DisplayToken(this);
 
     Mutex::Autolock _l(mStateLock);
-    DisplayDeviceState info;
-    info.type = DisplayDevice::DISPLAY_VIRTUAL;
-    info.displayName = displayName;
-    info.isSecure = secure;
-    mCurrentState.displays.add(token, info);
-    mInterceptor->saveDisplayCreation(info);
+    // Display ID is assigned when virtual display is allocated by HWC.
+    DisplayDeviceState state;
+    state.isSecure = secure;
+    state.displayName = displayName;
+    mCurrentState.displays.add(token, state);
+    mInterceptor->saveDisplayCreation(state);
     return token;
 }
 
 void SurfaceFlinger::destroyDisplay(const sp<IBinder>& displayToken) {
     Mutex::Autolock _l(mStateLock);
 
-    ssize_t idx = mCurrentState.displays.indexOfKey(displayToken);
-    if (idx < 0) {
+    ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
+    if (index < 0) {
         ALOGE("destroyDisplay: Invalid display token %p", displayToken.get());
         return;
     }
 
-    const DisplayDeviceState& info(mCurrentState.displays.valueAt(idx));
-    if (!info.isVirtual()) {
+    const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+    if (!state.isVirtual()) {
         ALOGE("destroyDisplay called for non-virtual display");
         return;
     }
-    mInterceptor->saveDisplayDeletion(info.sequenceId);
-    mCurrentState.displays.removeItemsAt(idx);
+    mInterceptor->saveDisplayDeletion(state.sequenceId);
+    mCurrentState.displays.removeItemsAt(index);
     setTransactionFlags(eDisplayTransactionNeeded);
 }
 
 sp<IBinder> SurfaceFlinger::getBuiltInDisplay(int32_t id) {
-    if (uint32_t(id) >= DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES) {
-        ALOGE("getDefaultDisplay: id=%d is not a valid default display id", id);
+    std::optional<DisplayId> displayId;
+
+    if (id == HWC_DISPLAY_PRIMARY) {
+        displayId = getInternalDisplayId();
+    } else if (id == HWC_DISPLAY_EXTERNAL) {
+        displayId = getExternalDisplayId();
+    }
+
+    if (!displayId) {
+        ALOGE("%s: Invalid display %d", __FUNCTION__, id);
         return nullptr;
     }
-    return mDisplayTokens[id];
+
+    return getPhysicalDisplayToken(*displayId);
 }
 
 status_t SurfaceFlinger::getColorManagement(bool* outGetColorManagement) const {
@@ -598,8 +610,10 @@
 
     // start the EventThread
     if (mUseScheduler) {
-        mScheduler = getFactory().createScheduler(
-                [this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });
+        mScheduler = getFactory().createScheduler([this](bool enabled) {
+            setVsyncEnabled(EventThread::DisplayType::Primary, enabled);
+        });
+
         mAppConnectionHandle =
                 mScheduler->createConnection("appConnection", SurfaceFlinger::vsyncPhaseOffsetNs,
                                              [this] { resyncWithRateLimit(); },
@@ -659,7 +673,7 @@
     processDisplayHotplugEventsLocked();
     const auto display = getDefaultDisplayDeviceLocked();
     LOG_ALWAYS_FATAL_IF(!display, "Missing internal display after registering composer callback.");
-    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(display->getId()),
+    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(*display->getId()),
                         "Internal display is disconnected.");
 
     // make the default display GLContext current so that we can create textures
@@ -682,7 +696,7 @@
         };
         mVrFlinger = dvr::VrFlinger::Create(getHwComposer().getComposer(),
                                             getHwComposer()
-                                                    .getHwcDisplayId(display->getId())
+                                                    .fromPhysicalDisplayId(*display->getId())
                                                     .value_or(0),
                                             vrFlingerRequestDisplayCallback);
         if (!mVrFlinger) {
@@ -691,7 +705,7 @@
     }
 
     mEventControlThread = getFactory().createEventControlThread(
-            [this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });
+            [this](bool enabled) { setVsyncEnabled(EventThread::DisplayType::Primary, enabled); });
 
     // initialize our drawing state
     mDrawingState = mCurrentState;
@@ -717,8 +731,8 @@
     // and apply this saturation matrix on Display P3 content. Unless the risk of applying
     // such saturation matrix on Display P3 is understood fully, the API should always return
     // identify matrix.
-    mEnhancedSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(display->getId(),
-            Dataspace::SRGB_LINEAR);
+    mEnhancedSaturationMatrix =
+            getHwComposer().getDataspaceSaturationMatrix(*display->getId(), Dataspace::SRGB_LINEAR);
 
     // we will apply this on Display P3.
     if (mEnhancedSaturationMatrix != mat4()) {
@@ -805,16 +819,9 @@
         return BAD_VALUE;
     }
 
-    int32_t type = NAME_NOT_FOUND;
-    for (int i = 0; i < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES; ++i) {
-        if (displayToken == mDisplayTokens[i]) {
-            type = i;
-            break;
-        }
-    }
-
-    if (type < 0) {
-        return type;
+    const auto displayId = getPhysicalDisplayId(displayToken);
+    if (!displayId) {
+        return NAME_NOT_FOUND;
     }
 
     // TODO: Not sure if display density should handled by SF any longer
@@ -838,7 +845,7 @@
 
     ConditionalLock _l(mStateLock,
             std::this_thread::get_id() != mMainThreadId);
-    for (const auto& hwConfig : getHwComposer().getConfigs(type)) {
+    for (const auto& hwConfig : getHwComposer().getConfigs(*displayId)) {
         DisplayInfo info = DisplayInfo();
 
         float xdpi = hwConfig->getDpiX();
@@ -850,7 +857,7 @@
         info.viewportW = info.w;
         info.viewportH = info.h;
 
-        if (type == DisplayDevice::DISPLAY_PRIMARY) {
+        if (displayId == getInternalDisplayId()) {
             // The density of the device is provided by a build property
             float density = Density::getBuildDensity() / 160.0f;
             if (density == 0) {
@@ -906,7 +913,7 @@
         // All non-virtual displays are currently considered secure.
         info.secure = true;
 
-        if (type == DisplayDevice::DISPLAY_PRIMARY &&
+        if (displayId == getInternalDisplayId() &&
             primaryDisplayOrientation & DisplayState::eOrientationSwapMask) {
             std::swap(info.w, info.h);
         }
@@ -922,7 +929,6 @@
         return BAD_VALUE;
     }
 
-    // FIXME for now we always return stats for the primary display.
     if (mUseScheduler) {
         mScheduler->getDisplayStatInfo(stats);
     } else {
@@ -943,18 +949,21 @@
 }
 
 void SurfaceFlinger::setActiveConfigInternal(const sp<DisplayDevice>& display, int mode) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
+        return;
+    }
+
     int currentMode = display->getActiveConfig();
     if (mode == currentMode) {
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set config for virtual display");
-        return;
-    }
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
 
     display->setActiveConfig(mode);
-    getHwComposer().setActiveConfig(display->getDisplayType(), mode);
+    getHwComposer().setActiveConfig(*displayId, mode);
 }
 
 status_t SurfaceFlinger::setActiveConfig(const sp<IBinder>& displayToken, int mode) {
@@ -979,29 +988,23 @@
 
     return NO_ERROR;
 }
+
 status_t SurfaceFlinger::getDisplayColorModes(const sp<IBinder>& displayToken,
                                               Vector<ColorMode>* outColorModes) {
     if (!displayToken || !outColorModes) {
         return BAD_VALUE;
     }
 
-    int32_t type = NAME_NOT_FOUND;
-    for (int i = 0; i < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES; ++i) {
-        if (displayToken == mDisplayTokens[i]) {
-            type = i;
-            break;
-        }
-    }
-
-    if (type < 0) {
-        return type;
+    const auto displayId = getPhysicalDisplayId(displayToken);
+    if (!displayId) {
+        return NAME_NOT_FOUND;
     }
 
     std::vector<ColorMode> modes;
     {
         ConditionalLock _l(mStateLock,
                 std::this_thread::get_id() != mMainThreadId);
-        modes = getHwComposer().getColorModes(type);
+        modes = getHwComposer().getColorModes(*displayId);
     }
     outColorModes->clear();
     std::copy(modes.cbegin(), modes.cend(), std::back_inserter(*outColorModes));
@@ -1018,6 +1021,11 @@
 
 void SurfaceFlinger::setActiveColorModeInternal(const sp<DisplayDevice>& display, ColorMode mode,
                                                 Dataspace dataSpace, RenderIntent renderIntent) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
+        return;
+    }
+
     ColorMode currentMode = display->getActiveColorMode();
     Dataspace currentDataSpace = display->getCompositionDataSpace();
     RenderIntent currentRenderIntent = display->getActiveRenderIntent();
@@ -1027,19 +1035,17 @@
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set config for virtual display");
-        return;
-    }
-
     display->setActiveColorMode(mode);
     display->setCompositionDataSpace(dataSpace);
     display->setActiveRenderIntent(renderIntent);
-    getHwComposer().setActiveColorMode(display->getDisplayType(), mode, renderIntent);
 
-    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), type=%d",
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
+    getHwComposer().setActiveColorMode(*displayId, mode, renderIntent);
+
+    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), display=%" PRIu64,
           decodeColorMode(mode).c_str(), mode, decodeRenderIntent(renderIntent).c_str(),
-          renderIntent, display->getDisplayType());
+          renderIntent, *displayId);
 }
 
 status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
@@ -1170,9 +1176,9 @@
         Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
         Dataspace* outWideColorGamutDataspace,
         ui::PixelFormat* outWideColorGamutPixelFormat) const {
-    *outDataspace = defaultCompositionDataspace;
+    *outDataspace = mDefaultCompositionDataspace;
     *outPixelFormat = defaultCompositionPixelFormat;
-    *outWideColorGamutDataspace = wideColorGamutCompositionDataspace;
+    *outWideColorGamutDataspace = mWideColorGamutCompositionDataspace;
     *outWideColorGamutPixelFormat = wideColorGamutCompositionPixelFormat;
     return NO_ERROR;
 }
@@ -1255,12 +1261,12 @@
         return;
     }
 
-    const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-    if (!getHwComposer().isConnected(displayId)) {
+    const auto displayId = getInternalDisplayId();
+    if (!displayId || !getHwComposer().isConnected(*displayId)) {
         return;
     }
 
-    const auto activeConfig = getHwComposer().getActiveConfig(displayId);
+    const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
     const nsecs_t period = activeConfig->getVsyncPeriod();
 
     if (mUseScheduler) {
@@ -1311,12 +1317,11 @@
         return;
     }
 
-    int32_t type;
-    if (!getBE().mHwc->onVsync(hwcDisplayId, timestamp, &type)) {
+    if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) {
         return;
     }
 
-    if (type != DisplayDevice::DISPLAY_PRIMARY) {
+    if (hwcDisplayId != getHwComposer().getInternalHwcDisplayId()) {
         // For now, we don't do anything with external display vsyncs.
         return;
     }
@@ -1379,11 +1384,13 @@
     repaintEverything();
 }
 
-void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) {
+void SurfaceFlinger::setVsyncEnabled(EventThread::DisplayType /*displayType*/, bool enabled) {
     ATRACE_CALL();
     Mutex::Autolock lock(mStateLock);
-    getHwComposer().setVsyncEnabled(disp,
-            enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
+    if (const auto displayId = getInternalDisplayId()) {
+        getHwComposer().setVsyncEnabled(*displayId,
+                                        enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
+    }
 }
 
 // Note: it is assumed the caller holds |mStateLock| when this is called
@@ -1454,7 +1461,7 @@
     setPowerModeInternal(display, currentDisplayPowerMode, /*stateLockHeld*/ true);
 
     // Reset the timing values to account for the period of the swapped in HWC
-    const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    const auto activeConfig = getHwComposer().getActiveConfig(*display->getId());
     const nsecs_t period = activeConfig->getVsyncPeriod();
     mAnimFrameTracker.setDisplayRefreshPeriod(period);
 
@@ -1595,24 +1602,25 @@
         mGeometryInvalid = false;
         for (const auto& [token, display] : mDisplays) {
             const auto displayId = display->getId();
-            if (displayId >= 0) {
-                const Vector<sp<Layer>>& currentLayers(
-                        display->getVisibleLayersSortedByZ());
-                for (size_t i = 0; i < currentLayers.size(); i++) {
-                    const auto& layer = currentLayers[i];
+            if (!displayId) {
+                continue;
+            }
 
-                    if (!layer->hasHwcLayer(displayId)) {
-                        if (!layer->createHwcLayer(getBE().mHwc.get(), displayId)) {
-                            layer->forceClientComposition(displayId);
-                            continue;
-                        }
-                    }
+            const Vector<sp<Layer>>& currentLayers = display->getVisibleLayersSortedByZ();
+            for (size_t i = 0; i < currentLayers.size(); i++) {
+                const auto& layer = currentLayers[i];
 
-                    layer->setGeometry(display, i);
-                    if (mDebugDisableHWC || mDebugRegion) {
-                        layer->forceClientComposition(displayId);
+                if (!layer->hasHwcLayer(*displayId)) {
+                    if (!layer->createHwcLayer(&getHwComposer(), *displayId)) {
+                        layer->forceClientComposition(*displayId);
+                        continue;
                     }
                 }
+
+                layer->setGeometry(display, i);
+                if (mDebugDisableHWC || mDebugRegion) {
+                    layer->forceClientComposition(*displayId);
+                }
             }
         }
     }
@@ -1620,41 +1628,43 @@
     // Set the per-frame data
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = display->getId();
-        if (displayId < 0) {
+        if (!displayId) {
             continue;
         }
 
         if (mDrawingState.colorMatrixChanged) {
             display->setColorTransform(mDrawingState.colorMatrix);
-            status_t result = getBE().mHwc->setColorTransform(displayId, mDrawingState.colorMatrix);
-            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on "
-                    "display %d: %d", displayId, result);
+            status_t result =
+                    getHwComposer().setColorTransform(*displayId, mDrawingState.colorMatrix);
+            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on display %" PRIu64 ": %d",
+                     *displayId, result);
         }
         for (auto& layer : display->getVisibleLayersSortedByZ()) {
             if (layer->isHdrY410()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_PQ ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_PQ) &&
                     !display->hasHDR10Support()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_HLG ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_HLG) &&
                     !display->hasHLGSupport()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             }
 
             // TODO(b/111562338) remove when composer 2.3 is shipped.
             if (layer->hasColorTransform()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             }
 
-            if (layer->getForceClientComposition(displayId)) {
+            if (layer->getForceClientComposition(*displayId)) {
                 ALOGV("[%s] Requesting Client composition", layer->getName().string());
-                layer->setCompositionType(displayId, HWC2::Composition::Client);
+                layer->setCompositionType(*displayId, HWC2::Composition::Client);
                 continue;
             }
 
-            layer->setPerFrameData(display);
+            layer->setPerFrameData(*displayId, display->getTransform(), display->getViewport(),
+                                   display->getSupportedPerFrameMetadata());
         }
 
         if (useColorManagement) {
@@ -1674,10 +1684,14 @@
         for (auto& layer : display->getVisibleLayersSortedByZ()) {
             const auto displayId = display->getId();
             layer->getBE().compositionInfo.compositionType = layer->getCompositionType(displayId);
-            if (!layer->setHwcLayer(displayId)) {
-                ALOGV("Need to create HWCLayer for %s", layer->getName().string());
+
+            if (displayId) {
+                if (!layer->setHwcLayer(*displayId)) {
+                    ALOGV("Need to create HWCLayer for %s", layer->getName().string());
+                }
+                layer->getBE().compositionInfo.hwc.displayId = *displayId;
             }
-            layer->getBE().compositionInfo.hwc.displayId = displayId;
+
             getBE().mCompositionInfo[token].push_back(layer->getBE().compositionInfo);
             layer->getBE().compositionInfo.hwc.hwcLayer = nullptr;
         }
@@ -1837,7 +1851,7 @@
 
     getBE().mDisplayTimeline.updateSignalTimes();
     mPreviousPresentFence =
-            display ? getHwComposer().getPresentFence(display->getId()) : Fence::NO_FENCE;
+            display ? getHwComposer().getPresentFence(*display->getId()) : Fence::NO_FENCE;
     auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFence);
     getBE().mDisplayTimeline.push(presentFenceTime);
 
@@ -1860,8 +1874,8 @@
     }
 
     mDrawingState.traverseInZOrder([&](Layer* layer) {
-        bool frameLatched = layer->onPostComposition(glCompositionDoneFenceTime,
-                presentFenceTime, compositorTiming);
+        bool frameLatched = layer->onPostComposition(display->getId(), glCompositionDoneFenceTime,
+                                                     presentFenceTime, compositorTiming);
         if (frameLatched) {
             recordBufferingStats(layer->getName().string(),
                     layer->getOccupancyHistory(false));
@@ -1881,7 +1895,7 @@
     }
 
     if (!hasSyncFramework) {
-        if (display && getHwComposer().isConnected(display->getId()) && display->isPoweredOn()) {
+        if (display && getHwComposer().isConnected(*display->getId()) && display->isPoweredOn()) {
             if (mUseScheduler) {
                 mScheduler->enableHardwareVsync();
             } else {
@@ -1896,10 +1910,10 @@
         if (presentFenceTime->isValid()) {
             mAnimFrameTracker.setActualPresentFence(
                     std::move(presentFenceTime));
-        } else if (display && getHwComposer().isConnected(display->getId())) {
+        } else if (display && getHwComposer().isConnected(*display->getId())) {
             // The HWC doesn't support present fences, so use the refresh
             // timestamp instead.
-            const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(display->getId());
+            const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(*display->getId());
             mAnimFrameTracker.setActualPresentTime(presentTime);
         }
         mAnimFrameTracker.advanceFrame();
@@ -1912,8 +1926,7 @@
 
     mTimeStats.setPresentFenceGlobal(presentFenceTime);
 
-    if (display && getHwComposer().isConnected(display->getId()) &&
-        display->getPowerMode() == HWC_POWER_MODE_OFF) {
+    if (display && getHwComposer().isConnected(*display->getId()) && !display->isPoweredOn()) {
         return;
     }
 
@@ -1967,6 +1980,7 @@
 
                 mDrawingState.traverseInZOrder([&](Layer* layer) {
                     bool hwcLayerDestroyed = false;
+                    const auto displayId = display->getId();
                     if (layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
                         Region drawRegion(tr.transform(
                                 layer->visibleNonTransparentRegion));
@@ -1976,13 +1990,13 @@
                         } else {
                             // Clear out the HWC layer if this layer was
                             // previously visible, but no longer is
-                            hwcLayerDestroyed = layer->destroyHwcLayer(display->getId());
+                            hwcLayerDestroyed = displayId && layer->destroyHwcLayer(*displayId);
                         }
                     } else {
                         // WM changes display->layerStack upon sleep/awake.
                         // Here we make sure we delete the HWC layers even if
                         // WM changed their layer stack.
-                        hwcLayerDestroyed = layer->destroyHwcLayer(display->getId());
+                        hwcLayerDestroyed = displayId && layer->destroyHwcLayer(*displayId);
                     }
 
                     // If a layer is not going to get a release fence because
@@ -2107,12 +2121,11 @@
     //   emit any black frames until a layer is added to the layer stack.
     bool mustRecompose = dirty && !(empty && wasEmpty);
 
-    ALOGV_IF(display->getDisplayType() == DisplayDevice::DISPLAY_VIRTUAL,
-            "id[%d]: %s composition (%sdirty %sempty %swasEmpty)", display->getId(),
-            mustRecompose ? "doing" : "skipping",
-            dirty ? "+" : "-",
-            empty ? "+" : "-",
-            wasEmpty ? "+" : "-");
+    const char flagPrefix[] = {'-', '+'};
+    static_cast<void>(flagPrefix);
+    ALOGV_IF(display->isVirtual(), "%s: %s composition for %s (%cdirty %cempty %cwasEmpty)",
+             __FUNCTION__, mustRecompose ? "doing" : "skipping", display->getDebugName().c_str(),
+             flagPrefix[dirty], flagPrefix[empty], flagPrefix[wasEmpty]);
 
     display->beginFrame(mustRecompose);
 
@@ -2129,10 +2142,8 @@
 
     status_t result = display->prepareFrame(getHwComposer(),
                                             getBE().mCompositionInfo[display->getDisplayToken()]);
-    ALOGE_IF(result != NO_ERROR,
-             "prepareFrame for display %d failed:"
-             " %d (%s)",
-             display->getId(), result, strerror(-result));
+    ALOGE_IF(result != NO_ERROR, "prepareFrame failed for %s: %d (%s)",
+             display->getDebugName().c_str(), result, strerror(-result));
 }
 
 void SurfaceFlinger::doComposition(const sp<DisplayDevice>& display, bool repaintEverything) {
@@ -2155,8 +2166,9 @@
 void SurfaceFlinger::postFrame()
 {
     // |mStateLock| not needed as we are on the main thread
-    if (getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY)) {
-        uint32_t flipCount = getDefaultDisplayDeviceLocked()->getPageFlipCount();
+    const auto display = getDefaultDisplayDeviceLocked();
+    if (display && getHwComposer().isConnected(*display->getId())) {
+        uint32_t flipCount = display->getPageFlipCount();
         if (flipCount % LOG_FRAME_STATS_PERIOD == 0) {
             logFrameStats();
         }
@@ -2172,8 +2184,8 @@
 
     if (display->isPoweredOn()) {
         const auto displayId = display->getId();
-        if (displayId >= 0) {
-            getBE().mHwc->presentAndGetReleaseFences(displayId);
+        if (displayId) {
+            getHwComposer().presentAndGetReleaseFences(*displayId);
         }
         display->onSwapBuffersCompleted();
         display->makeCurrent();
@@ -2183,9 +2195,9 @@
             // The layer buffer from the previous frame (if any) is released
             // by HWC only when the release fence from this frame (if any) is
             // signaled.  Always get the release fence from HWC first.
-            auto hwcLayer = layer->getHwcLayer(displayId);
-            if (displayId >= 0) {
-                releaseFence = getBE().mHwc->getLayerReleaseFence(displayId, hwcLayer);
+            if (displayId && layer->hasHwcLayer(*displayId)) {
+                releaseFence = getHwComposer().getLayerReleaseFence(*displayId,
+                                                                    layer->getHwcLayer(*displayId));
             }
 
             // If the layer was client composited in the previous frame, we
@@ -2205,14 +2217,15 @@
         // display->getVisibleLayersSortedByZ.  The best we can do is to
         // supply them with the present fence.
         if (!display->getLayersNeedingFences().isEmpty()) {
-            sp<Fence> presentFence = getBE().mHwc->getPresentFence(displayId);
+            sp<Fence> presentFence =
+                    displayId ? getBE().mHwc->getPresentFence(*displayId) : Fence::NO_FENCE;
             for (auto& layer : display->getLayersNeedingFences()) {
                 layer->getBE().onLayerDisplayed(presentFence);
             }
         }
 
-        if (displayId >= 0) {
-            getBE().mHwc->clearReleaseFences(displayId);
+        if (displayId) {
+            getHwComposer().clearReleaseFences(*displayId);
         }
     }
 }
@@ -2247,72 +2260,36 @@
     // here the transaction has been committed
 }
 
-DisplayDevice::DisplayType SurfaceFlinger::determineDisplayType(hwc2_display_t hwcDisplayId,
-                                                                HWC2::Connection connection) const {
-    // Figure out whether the event is for the primary display or an
-    // external display by matching the Hwc display id against one for a
-    // connected display. If we did not find a match, we then check what
-    // displays are not already connected to determine the type. If we don't
-    // have a connected primary display, we assume the new display is meant to
-    // be the primary display, and then if we don't have an external display,
-    // we assume it is that.
-    const auto primaryHwcDisplayId = getBE().mHwc->getHwcDisplayId(DisplayDevice::DISPLAY_PRIMARY);
-    const auto externalHwcDisplayId =
-            getBE().mHwc->getHwcDisplayId(DisplayDevice::DISPLAY_EXTERNAL);
-    if (primaryHwcDisplayId && primaryHwcDisplayId == hwcDisplayId) {
-        return DisplayDevice::DISPLAY_PRIMARY;
-    } else if (externalHwcDisplayId && externalHwcDisplayId == hwcDisplayId) {
-        return DisplayDevice::DISPLAY_EXTERNAL;
-    } else if (connection == HWC2::Connection::Connected && !primaryHwcDisplayId) {
-        return DisplayDevice::DISPLAY_PRIMARY;
-    } else if (connection == HWC2::Connection::Connected && !externalHwcDisplayId) {
-        return DisplayDevice::DISPLAY_EXTERNAL;
-    }
-
-    return DisplayDevice::DISPLAY_ID_INVALID;
-}
-
 void SurfaceFlinger::processDisplayHotplugEventsLocked() {
     for (const auto& event : mPendingHotplugEvents) {
-        auto displayType = determineDisplayType(event.hwcDisplayId, event.connection);
-        if (displayType == DisplayDevice::DISPLAY_ID_INVALID) {
-            ALOGW("Unable to determine the display type for display %" PRIu64, event.hwcDisplayId);
-            continue;
-        }
+        const std::optional<DisplayIdentificationInfo> info =
+                getHwComposer().onHotplug(event.hwcDisplayId, event.connection);
 
-        if (getBE().mHwc->isUsingVrComposer() && displayType == DisplayDevice::DISPLAY_EXTERNAL) {
-            ALOGE("External displays are not supported by the vr hardware composer.");
+        if (!info) {
             continue;
         }
 
-        const auto displayId =
-                getBE().mHwc->onHotplug(event.hwcDisplayId, displayType, event.connection);
-        if (displayId) {
-            ALOGV("Display %" PRIu64 " has stable ID %" PRIu64, event.hwcDisplayId, *displayId);
-        }
-
         if (event.connection == HWC2::Connection::Connected) {
-            if (!mDisplayTokens[displayType].get()) {
-                ALOGV("Creating built in display %d", displayType);
-                mDisplayTokens[displayType] = new BBinder();
-                DisplayDeviceState info;
-                info.type = displayType;
-                info.displayName = displayType == DisplayDevice::DISPLAY_PRIMARY ?
-                        "Built-in Screen" : "External Screen";
-                info.isSecure = true; // All physical displays are currently considered secure.
-                mCurrentState.displays.add(mDisplayTokens[displayType], info);
-                mInterceptor->saveDisplayCreation(info);
+            if (!mPhysicalDisplayTokens.count(info->id)) {
+                ALOGV("Creating display %" PRIu64, info->id);
+                mPhysicalDisplayTokens[info->id] = new BBinder();
+                DisplayDeviceState state;
+                state.displayId = info->id;
+                state.isSecure = true; // All physical displays are currently considered secure.
+                state.displayName = info->name;
+                mCurrentState.displays.add(mPhysicalDisplayTokens[info->id], state);
+                mInterceptor->saveDisplayCreation(state);
             }
         } else {
-            ALOGV("Removing built in display %d", displayType);
+            ALOGV("Removing display %" PRIu64, info->id);
 
-            ssize_t idx = mCurrentState.displays.indexOfKey(mDisplayTokens[displayType]);
-            if (idx >= 0) {
-                const DisplayDeviceState& info(mCurrentState.displays.valueAt(idx));
-                mInterceptor->saveDisplayDeletion(info.sequenceId);
-                mCurrentState.displays.removeItemsAt(idx);
+            ssize_t index = mCurrentState.displays.indexOfKey(mPhysicalDisplayTokens[info->id]);
+            if (index >= 0) {
+                const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+                mInterceptor->saveDisplayDeletion(state.sequenceId);
+                mCurrentState.displays.removeItemsAt(index);
             }
-            mDisplayTokens[displayType].clear();
+            mPhysicalDisplayTokens.erase(info->id);
         }
 
         processDisplayChangesLocked();
@@ -2322,31 +2299,36 @@
 }
 
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
-        const wp<IBinder>& displayToken, int32_t displayId, const DisplayDeviceState& state,
-        const sp<DisplaySurface>& dispSurface, const sp<IGraphicBufferProducer>& producer) {
-    DisplayDeviceCreationArgs creationArgs(this, displayToken, state.type, displayId);
+        const wp<IBinder>& displayToken, const std::optional<DisplayId>& displayId,
+        const DisplayDeviceState& state, const sp<DisplaySurface>& dispSurface,
+        const sp<IGraphicBufferProducer>& producer) {
+    DisplayDeviceCreationArgs creationArgs(this, displayToken, displayId);
+    creationArgs.isVirtual = state.isVirtual();
     creationArgs.isSecure = state.isSecure;
     creationArgs.displaySurface = dispSurface;
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
 
-    if (useColorManagement && displayId >= 0) {
-        std::vector<ColorMode> modes = getHwComposer().getColorModes(displayId);
+    const bool isInternalDisplay = displayId && displayId == getInternalDisplayId();
+    creationArgs.isPrimary = isInternalDisplay;
+
+    if (useColorManagement && displayId) {
+        std::vector<ColorMode> modes = getHwComposer().getColorModes(*displayId);
         for (ColorMode colorMode : modes) {
             if (isWideColorMode(colorMode)) {
                 creationArgs.hasWideColorGamut = true;
             }
 
             std::vector<RenderIntent> renderIntents =
-                    getHwComposer().getRenderIntents(displayId, colorMode);
+                    getHwComposer().getRenderIntents(*displayId, colorMode);
             creationArgs.hwcColorModes.emplace(colorMode, renderIntents);
         }
     }
 
-    if (displayId >= 0) {
-        getHwComposer().getHdrCapabilities(displayId, &creationArgs.hdrCapabilities);
+    if (displayId) {
+        getHwComposer().getHdrCapabilities(*displayId, &creationArgs.hdrCapabilities);
         creationArgs.supportedPerFrameMetadata =
-                getHwComposer().getSupportedPerFrameMetadata(displayId);
+                getHwComposer().getSupportedPerFrameMetadata(*displayId);
     }
 
     auto nativeWindowSurface = getFactory().createNativeWindowSurface(producer);
@@ -2357,7 +2339,7 @@
      * Create our display's surface
      */
     std::unique_ptr<renderengine::Surface> renderSurface = getRenderEngine().createSurface();
-    renderSurface->setCritical(state.type == DisplayDevice::DISPLAY_PRIMARY);
+    renderSurface->setCritical(isInternalDisplay);
     renderSurface->setAsync(state.isVirtual());
     renderSurface->setNativeWindow(nativeWindow.get());
     creationArgs.displayWidth = renderSurface->getWidth();
@@ -2375,9 +2357,8 @@
         nativeWindow->setSwapInterval(nativeWindow.get(), 0);
     }
 
-    creationArgs.displayInstallOrientation = state.type == DisplayDevice::DISPLAY_PRIMARY
-            ? primaryDisplayOrientation
-            : DisplayState::eOrientationDefault;
+    creationArgs.displayInstallOrientation =
+            isInternalDisplay ? primaryDisplayOrientation : DisplayState::eOrientationDefault;
 
     // virtual displays are always considered enabled
     creationArgs.initialPowerMode = state.isVirtual() ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF;
@@ -2396,9 +2377,11 @@
     }
     setActiveColorModeInternal(display, defaultColorMode, defaultDataSpace,
                                RenderIntent::COLORIMETRIC);
-    if (state.type < DisplayDevice::DISPLAY_VIRTUAL) {
-        display->setActiveConfig(getHwComposer().getActiveConfigIndex(state.type));
+    if (!state.isVirtual()) {
+        LOG_ALWAYS_FATAL_IF(!displayId);
+        display->setActiveConfig(getHwComposer().getActiveConfigIndex(*displayId));
     }
+
     display->setLayerStack(state.layerStack);
     display->setProjection(state.orientation, state.viewport, state.frame);
     display->setDisplayName(state.displayName);
@@ -2424,6 +2407,10 @@
         for (size_t i = 0; i < dc;) {
             const ssize_t j = curr.indexOfKey(draw.keyAt(i));
             if (j < 0) {
+                // Save display IDs before disconnecting.
+                const auto internalDisplayId = getInternalDisplayId();
+                const auto externalDisplayId = getExternalDisplayId();
+
                 // in drawing state but not in current state
                 // Call makeCurrent() on the primary display so we can
                 // be sure that nothing associated with this display
@@ -2434,14 +2421,14 @@
                 if (const auto display = getDisplayDeviceLocked(draw.keyAt(i))) {
                     display->disconnect(getHwComposer());
                 }
-                if (draw[i].type == DisplayDevice::DISPLAY_PRIMARY) {
+                if (internalDisplayId && internalDisplayId == draw[i].displayId) {
                     if (mUseScheduler) {
                         mScheduler->hotplugReceived(mAppConnectionHandle,
                                                     EventThread::DisplayType::Primary, false);
                     } else {
                         mEventThread->onHotplugReceived(EventThread::DisplayType::Primary, false);
                     }
-                } else if (draw[i].type == DisplayDevice::DISPLAY_EXTERNAL) {
+                } else if (externalDisplayId && externalDisplayId == draw[i].displayId) {
                     if (mUseScheduler) {
                         mScheduler->hotplugReceived(mAppConnectionHandle,
                                                     EventThread::DisplayType::External, false);
@@ -2499,14 +2486,14 @@
                 sp<IGraphicBufferConsumer> bqConsumer;
                 getFactory().createBufferQueue(&bqProducer, &bqConsumer, false);
 
-                int32_t displayId = -1;
+                std::optional<DisplayId> displayId;
                 if (state.isVirtual()) {
                     // Virtual displays without a surface are dormant:
                     // they have external state (layer stack, projection,
                     // etc.) but no internal state (i.e. a DisplayDevice).
                     if (state.surface != nullptr) {
                         // Allow VR composer to use virtual displays.
-                        if (mUseHwcVirtualDisplays || getBE().mHwc->isUsingVrComposer()) {
+                        if (mUseHwcVirtualDisplays || getHwComposer().isUsingVrComposer()) {
                             int width = 0;
                             int status = state.surface->query(NATIVE_WINDOW_WIDTH, &width);
                             ALOGE_IF(status != NO_ERROR, "Unable to query width (%d)", status);
@@ -2518,14 +2505,14 @@
                             ALOGE_IF(status != NO_ERROR, "Unable to query format (%d)", status);
                             auto format = static_cast<ui::PixelFormat>(intFormat);
 
-                            getBE().mHwc->allocateVirtualDisplay(width, height, &format,
-                                                                 &displayId);
+                            displayId =
+                                    getHwComposer().allocateVirtualDisplay(width, height, &format);
                         }
 
                         // TODO: Plumb requested format back up to consumer
 
                         sp<VirtualDisplaySurface> vds =
-                                new VirtualDisplaySurface(*getBE().mHwc, displayId, state.surface,
+                                new VirtualDisplaySurface(getHwComposer(), displayId, state.surface,
                                                           bqProducer, bqConsumer,
                                                           state.displayName);
 
@@ -2538,8 +2525,9 @@
                              "surface is provided (%p), ignoring it",
                              state.surface.get());
 
-                    displayId = state.type;
-                    dispSurface = new FramebufferSurface(*getBE().mHwc, displayId, bqConsumer);
+                    displayId = state.displayId;
+                    LOG_ALWAYS_FATAL_IF(!displayId);
+                    dispSurface = new FramebufferSurface(getHwComposer(), *displayId, bqConsumer);
                     producer = bqProducer;
                 }
 
@@ -2549,7 +2537,9 @@
                                       setupNewDisplayDeviceInternal(displayToken, displayId, state,
                                                                     dispSurface, producer));
                     if (!state.isVirtual()) {
-                        if (state.type == DisplayDevice::DISPLAY_PRIMARY) {
+                        LOG_ALWAYS_FATAL_IF(!displayId);
+
+                        if (displayId == getInternalDisplayId()) {
                             if (mUseScheduler) {
                                 mScheduler->hotplugReceived(mAppConnectionHandle,
                                                             EventThread::DisplayType::Primary,
@@ -2558,7 +2548,7 @@
                                 mEventThread->onHotplugReceived(EventThread::DisplayType::Primary,
                                                                 true);
                             }
-                        } else if (state.type == DisplayDevice::DISPLAY_EXTERNAL) {
+                        } else if (displayId == getExternalDisplayId()) {
                             if (mUseScheduler) {
                                 mScheduler->hotplugReceived(mAppConnectionHandle,
                                                             EventThread::DisplayType::External,
@@ -2712,7 +2702,7 @@
 void SurfaceFlinger::updateCursorAsync()
 {
     for (const auto& [token, display] : mDisplays) {
-        if (display->getId() < 0) {
+        if (!display->getId()) {
             continue;
         }
 
@@ -3104,8 +3094,9 @@
                 case HWC2::Composition::Device:
                 case HWC2::Composition::Sideband:
                 case HWC2::Composition::SolidColor: {
+                    LOG_ALWAYS_FATAL_IF(!displayId);
                     const Layer::State& state(layer->getDrawingState());
-                    if (layer->getClearClientTarget(displayId) && !firstLayer &&
+                    if (layer->getClearClientTarget(*displayId) && !firstLayer &&
                         layer->isOpaque(state) && (layer->getAlpha() == 1.0f) &&
                         hasClientComposition) {
                         // never clear the very first layer since we're
@@ -3806,7 +3797,7 @@
 // ---------------------------------------------------------------------------
 
 void SurfaceFlinger::onInitializeDisplays() {
-    const auto displayToken = mDisplayTokens[DisplayDevice::DISPLAY_PRIMARY];
+    const auto displayToken = getInternalDisplayToken();
     if (!displayToken) return;
 
     // reset screen orientation and use primary layer stack
@@ -3830,7 +3821,7 @@
 
     setPowerModeInternal(display, HWC_POWER_MODE_NORMAL, /*stateLockHeld*/ false);
 
-    const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    const auto activeConfig = getHwComposer().getActiveConfig(*display->getId());
     const nsecs_t period = activeConfig->getVsyncPeriod();
     mAnimFrameTracker.setDisplayRefreshPeriod(period);
 
@@ -3847,16 +3838,18 @@
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, int mode,
                                           bool stateLockHeld) {
-    const int32_t displayId = display->getId();
-    ALOGD("Setting power mode %d on display %d", mode, displayId);
-
-    int currentMode = display->getPowerMode();
-    if (mode == currentMode) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set power mode for virtual display");
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
+
+    ALOGD("Setting power mode %d on display %" PRIu64, mode, *displayId);
+
+    int currentMode = display->getPowerMode();
+    if (mode == currentMode) {
         return;
     }
 
@@ -3872,12 +3865,10 @@
         mInterceptor->savePowerModeUpdate(mCurrentState.displays.valueAt(idx).sequenceId, mode);
     }
 
-    int32_t type = display->getDisplayType();
     if (currentMode == HWC_POWER_MODE_OFF) {
         // Turn on the display
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         if (display->isPrimary() && mode != HWC_POWER_MODE_DOZE_SUSPEND) {
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenAcquired(mAppConnectionHandle);
             } else {
@@ -3908,7 +3899,6 @@
             } else {
                 disableHardwareVsync(true); // also cancels any in-progress resync
             }
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenReleased(mAppConnectionHandle);
             } else {
@@ -3916,15 +3906,14 @@
             }
         }
 
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         mVisibleRegionsDirty = true;
         // from this point on, SF will stop drawing on this display
     } else if (mode == HWC_POWER_MODE_DOZE ||
                mode == HWC_POWER_MODE_NORMAL) {
         // Update display while dozing
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         if (display->isPrimary() && currentMode == HWC_POWER_MODE_DOZE_SUSPEND) {
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenAcquired(mAppConnectionHandle);
             } else {
@@ -3940,24 +3929,23 @@
             } else {
                 disableHardwareVsync(true); // also cancels any in-progress resync
             }
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenReleased(mAppConnectionHandle);
             } else {
                 mEventThread->onScreenReleased();
             }
         }
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
     } else {
         ALOGE("Attempting to set unknown power mode: %d\n", mode);
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
     }
 
     if (display->isPrimary()) {
         mTimeStats.setPowerMode(mode);
     }
 
-    ALOGD("Finished setting power mode %d on display %d", mode, displayId);
+    ALOGD("Finished setting power mode %d on display %" PRIu64, mode, *displayId);
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
@@ -4136,9 +4124,9 @@
         index++;
     }
 
-    if (const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-        getHwComposer().isConnected(displayId)) {
-        const auto activeConfig = getBE().mHwc->getActiveConfig(displayId);
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         const nsecs_t period = activeConfig->getVsyncPeriod();
         result.appendFormat("%" PRId64 "\n", period);
     }
@@ -4279,13 +4267,17 @@
 
 void SurfaceFlinger::dumpDisplayIdentificationData(String8& result) const {
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        const auto hwcDisplayId = getHwComposer().getHwcDisplayId(displayId);
+        const auto displayId = display->getId();
+        if (!displayId) {
+            continue;
+        }
+        const auto hwcDisplayId = getHwComposer().fromPhysicalDisplayId(*displayId);
         if (!hwcDisplayId) {
             continue;
         }
 
-        result.appendFormat("Display %d (HWC display %" PRIu64 "): ", displayId, *hwcDisplayId);
+        result.appendFormat("Display %" PRIu64 " (HWC display %" PRIu64 "): ", *displayId,
+                            *hwcDisplayId);
         uint8_t port;
         DisplayIdentificationData data;
         if (!getHwComposer().getDisplayIdentificationData(*hwcDisplayId, &port, &data)) {
@@ -4316,7 +4308,6 @@
         result.append(edid->displayName.data(), edid->displayName.length());
         result.append("\"\n");
     }
-    result.append("\n");
 }
 
 void SurfaceFlinger::dumpWideColorInfo(String8& result) const {
@@ -4328,13 +4319,13 @@
     // TODO: print out if wide-color mode is active or not
 
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+        const auto displayId = display->getId();
+        if (!displayId) {
             continue;
         }
 
-        result.appendFormat("Display %d color modes:\n", displayId);
-        std::vector<ColorMode> modes = getHwComposer().getColorModes(displayId);
+        result.appendFormat("Display %" PRIu64 " color modes:\n", *displayId);
+        std::vector<ColorMode> modes = getHwComposer().getColorModes(*displayId);
         for (auto&& mode : modes) {
             result.appendFormat("    %s (%d)\n", decodeColorMode(mode).c_str(), mode);
         }
@@ -4390,11 +4381,12 @@
     layersProto.set_color_transform(decodeColorTransform(display.getColorTransform()));
     layersProto.set_global_transform(static_cast<int32_t>(display.getOrientationTransform()));
 
-    const int32_t displayId = display.getId();
+    const auto displayId = display.getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
     mDrawingState.traverseInZOrder([&](Layer* layer) {
-        if (!layer->visibleRegion.isEmpty() && layer->getBE().mHwcLayers.count(displayId)) {
+        if (!layer->visibleRegion.isEmpty() && layer->getBE().mHwcLayers.count(*displayId)) {
             LayerProto* layerProto = layersProto.add_layers();
-            layer->writeToProto(layerProto, displayId);
+            layer->writeToProto(layerProto, *displayId);
         }
     });
 
@@ -4448,25 +4440,19 @@
 
     const auto [sfEarlyOffset, appEarlyOffset] = mVsyncModulator.getEarlyOffsets();
     const auto [sfEarlyGlOffset, appEarlyGlOffset] = mVsyncModulator.getEarlyGlOffsets();
-    if (const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-        getHwComposer().isConnected(displayId)) {
-        const auto activeConfig = getHwComposer().getActiveConfig(displayId);
-        result.appendFormat("Display %d: "
-                "app phase %" PRId64 " ns, "
-                "sf phase %" PRId64 " ns, "
-                "early app phase %" PRId64 " ns, "
-                "early sf phase %" PRId64 " ns, "
-                "early app gl phase %" PRId64 " ns, "
-                "early sf gl phase %" PRId64 " ns, "
-                "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
-                displayId,
-                vsyncPhaseOffsetNs,
-                sfVsyncPhaseOffsetNs,
-                appEarlyOffset,
-                sfEarlyOffset,
-                appEarlyGlOffset,
-                sfEarlyGlOffset,
-                dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
+        result.appendFormat("Display %" PRIu64 ": app phase %" PRId64 " ns, "
+                            "sf phase %" PRId64 " ns, "
+                            "early app phase %" PRId64 " ns, "
+                            "early sf phase %" PRId64 " ns, "
+                            "early app gl phase %" PRId64 " ns, "
+                            "early sf gl phase %" PRId64 " ns, "
+                            "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
+                            *displayId, vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs, appEarlyOffset,
+                            sfEarlyOffset, appEarlyGlOffset, sfEarlyGlOffset,
+                            dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
     }
     result.append("\n");
 
@@ -4519,12 +4505,9 @@
     result.append("SurfaceFlinger global state:\n");
     colorizer.reset(result);
 
-    HWComposer& hwc(getHwComposer());
-    const auto display = getDefaultDisplayDeviceLocked();
-
     getBE().mRenderEngine->dump(result);
 
-    if (display) {
+    if (const auto display = getDefaultDisplayDeviceLocked()) {
         display->undefinedRegion.dump(result, "undefinedRegion");
         result.appendFormat("  orientation=%d, isPoweredOn=%d\n", display->getOrientation(),
                             display->isPoweredOn());
@@ -4533,8 +4516,9 @@
                         "  gpu_to_cpu_unsupported    : %d\n",
                         mTransactionFlags.load(), !mGpuToCpuSupported);
 
-    if (display) {
-        const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         result.appendFormat("  refresh-rate              : %f fps\n"
                             "  x-dpi                     : %f\n"
                             "  y-dpi                     : %f\n",
@@ -4566,14 +4550,14 @@
      * HWC layer minidump
      */
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+        const auto displayId = display->getId();
+        if (!displayId) {
             continue;
         }
 
-        result.appendFormat("Display %d HWC layers:\n", displayId);
+        result.appendFormat("Display %" PRIu64 " HWC layers:\n", *displayId);
         Layer::miniDumpHeader(result);
-        mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, displayId); });
+        mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, *displayId); });
         result.append("\n");
     }
 
@@ -4586,7 +4570,7 @@
     bool hwcDisabled = mDebugDisableHWC || mDebugRegion;
     result.appendFormat("  h/w composer %s\n",
             hwcDisabled ? "disabled" : "enabled");
-    hwc.dump(result);
+    getHwComposer().dump(result);
 
     /*
      * Dump gralloc state
@@ -4604,7 +4588,7 @@
     }
 }
 
-const Vector<sp<Layer>>& SurfaceFlinger::getLayerSortedByZForHwcDisplay(int32_t displayId) {
+const Vector<sp<Layer>>& SurfaceFlinger::getLayerSortedByZForHwcDisplay(DisplayId displayId) {
     // Note: mStateLock is held here
     for (const auto& [token, display] : mDisplays) {
         if (display->getId() == displayId) {
@@ -4612,7 +4596,7 @@
         }
     }
 
-    ALOGE("%s: Invalid display %d", __FUNCTION__, displayId);
+    ALOGE("%s: Invalid display %" PRIu64, __FUNCTION__, displayId);
     static const Vector<sp<Layer>> empty;
     return empty;
 }
@@ -4746,9 +4730,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1030 are currently use for backdoors. The code
+    // Numbers from 1000 to 1031 are currently use for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1030) {
+    if (code >= 1000 && code <= 1031) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -5002,6 +4986,38 @@
                 reply->writeBool(useColorManagement);
                 return NO_ERROR;
             }
+            // Override default composition data space
+            // adb shell service call SurfaceFlinger 1031 i32 1 DATASPACE_NUMBER DATASPACE_NUMBER \
+            // && adb shell stop zygote && adb shell start zygote
+            // to restore: adb shell service call SurfaceFlinger 1031 i32 0 && \
+            // adb shell stop zygote && adb shell start zygote
+            case 1031: {
+                Mutex::Autolock _l(mStateLock);
+                n = data.readInt32();
+                if (n) {
+                    n = data.readInt32();
+                    if (n) {
+                        Dataspace dataspace = static_cast<Dataspace>(n);
+                        if (!validateCompositionDataspace(dataspace)) {
+                            return BAD_VALUE;
+                        }
+                        mDefaultCompositionDataspace = dataspace;
+                    }
+                    n = data.readInt32();
+                    if (n) {
+                        Dataspace dataspace = static_cast<Dataspace>(n);
+                        if (!validateCompositionDataspace(dataspace)) {
+                            return BAD_VALUE;
+                        }
+                        mWideColorGamutCompositionDataspace = dataspace;
+                    }
+                } else {
+                    // restore composition data space.
+                    mDefaultCompositionDataspace = defaultCompositionDataspace;
+                    mWideColorGamutCompositionDataspace = wideColorGamutCompositionDataspace;
+                }
+                return NO_ERROR;
+            }
         }
     }
     return err;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 51168a6..e449e20 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -76,10 +76,11 @@
 #include <map>
 #include <mutex>
 #include <queue>
+#include <set>
 #include <string>
 #include <thread>
+#include <unordered_map>
 #include <utility>
-#include "RenderArea.h"
 
 #include <layerproto/LayerProtoHeader.h>
 
@@ -348,7 +349,7 @@
 
     // enable/disable h/w composer event
     // TODO: this should be made accessible only to EventThread
-    void setVsyncEnabled(int disp, int enabled);
+    void setVsyncEnabled(EventThread::DisplayType displayType, bool enabled);
 
     // called on the main thread by MessageQueue when an internal message
     // is received
@@ -357,7 +358,7 @@
 
     // for debugging only
     // TODO: this should be made accessible only to HWComposer
-    const Vector< sp<Layer> >& getLayerSortedByZForHwcDisplay(int id);
+    const Vector<sp<Layer>>& getLayerSortedByZForHwcDisplay(DisplayId displayId);
 
     renderengine::RenderEngine& getRenderEngine() const { return *getBE().mRenderEngine; }
 
@@ -659,7 +660,10 @@
     }
 
     sp<DisplayDevice> getDefaultDisplayDeviceLocked() {
-        return getDisplayDeviceLocked(mDisplayTokens[DisplayDevice::DISPLAY_PRIMARY]);
+        if (const auto token = getInternalDisplayToken()) {
+            return getDisplayDeviceLocked(token);
+        }
+        return nullptr;
     }
 
     // mark a region of a layer stack dirty. this updates the dirty
@@ -724,10 +728,8 @@
     /* ------------------------------------------------------------------------
      * Display management
      */
-    DisplayDevice::DisplayType determineDisplayType(hwc2_display_t hwcDisplayId,
-                                                    HWC2::Connection connection) const;
     sp<DisplayDevice> setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken,
-                                                    int32_t displayId,
+                                                    const std::optional<DisplayId>& displayId,
                                                     const DisplayDeviceState& state,
                                                     const sp<DisplaySurface>& dispSurface,
                                                     const sp<IGraphicBufferProducer>& producer);
@@ -759,6 +761,37 @@
     }
 
 private:
+    sp<IBinder> getPhysicalDisplayToken(DisplayId displayId) const {
+        const auto it = mPhysicalDisplayTokens.find(displayId);
+        return it != mPhysicalDisplayTokens.end() ? it->second : nullptr;
+    }
+
+    std::optional<DisplayId> getPhysicalDisplayId(const sp<IBinder>& displayToken) const {
+        for (const auto& [id, token] : mPhysicalDisplayTokens) {
+            if (token == displayToken) {
+                return id;
+            }
+        }
+        return {};
+    }
+
+    // TODO(b/74619554): Remove special cases for primary display.
+    sp<IBinder> getInternalDisplayToken() const {
+        const auto displayId = getInternalDisplayId();
+        return displayId ? getPhysicalDisplayToken(*displayId) : nullptr;
+    }
+
+    std::optional<DisplayId> getInternalDisplayId() const {
+        const auto hwcDisplayId = getHwComposer().getInternalHwcDisplayId();
+        return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
+    }
+
+    // TODO(b/74619554): Remove special cases for external display.
+    std::optional<DisplayId> getExternalDisplayId() const {
+        const auto hwcDisplayId = getHwComposer().getExternalHwcDisplayId();
+        return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
+    }
+
     void listLayersLocked(const Vector<String16>& args, size_t& index, String8& result) const;
     void dumpStatsLocked(const Vector<String16>& args, size_t& index, String8& result) const;
     void clearStatsLocked(const Vector<String16>& args, size_t& index, String8& result);
@@ -836,7 +869,7 @@
     std::unique_ptr<VSyncSource> mSfEventThreadSource;
     std::unique_ptr<InjectVSyncSource> mVSyncInjector;
     std::unique_ptr<EventControlThread> mEventControlThread;
-    sp<IBinder> mDisplayTokens[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES];
+    std::unordered_map<DisplayId, sp<IBinder>> mPhysicalDisplayTokens;
 
     VSyncModulator mVsyncModulator;
 
@@ -937,6 +970,9 @@
     // Applied on Display P3 layers when the render intent is non-colorimetric.
     mat4 mEnhancedSaturationMatrix;
 
+    ui::Dataspace mDefaultCompositionDataspace;
+    ui::Dataspace mWideColorGamutCompositionDataspace;
+
     SurfaceFlingerBE mBE;
 
     bool mUseScheduler = false;
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 57bda5a..7faaff6 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -495,8 +495,10 @@
     DisplayCreation* creation(increment->mutable_display_creation());
     creation->set_id(info.sequenceId);
     creation->set_name(info.displayName);
-    creation->set_type(info.type);
     creation->set_is_secure(info.isSecure);
+    if (info.displayId) {
+        creation->set_display_id(*info.displayId);
+    }
 }
 
 void SurfaceInterceptor::addDisplayDeletionLocked(Increment* increment, int32_t sequenceId) {
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index f1a8842..09bb8c5 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -65,6 +65,7 @@
 constexpr hwc2_layer_t HWC_LAYER = 5000;
 constexpr Transform DEFAULT_TRANSFORM = static_cast<Transform>(0);
 
+constexpr DisplayId DEFAULT_DISPLAY_ID = 42;
 constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
 
@@ -226,12 +227,13 @@
     static constexpr int INIT_POWER_MODE = HWC_POWER_MODE_NORMAL;
 
     static void setupPreconditions(CompositionTest* test) {
-        FakeHwcDisplayInjector(DisplayDevice::DISPLAY_PRIMARY, HWC2::DisplayType::Physical)
+        FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, HWC2::DisplayType::Physical,
+                               true /* isPrimary */)
                 .setCapabilities(&test->mDefaultCapabilities)
                 .inject(&test->mFlinger, test->mComposer);
 
-        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, DisplayDevice::DISPLAY_PRIMARY,
-                                                   DisplayDevice::DISPLAY_PRIMARY)
+        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, DEFAULT_DISPLAY_ID,
+                                                   false /* isVirtual */, true /* isPrimary */)
                                  .setDisplaySize(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT)
                                  .setDisplaySurface(test->mDisplaySurface)
                                  .setRenderSurface(std::unique_ptr<renderengine::Surface>(
@@ -745,7 +747,9 @@
         EXPECT_CALL(*test->mComposer, createLayer(HWC_DISPLAY, _))
                 .WillOnce(DoAll(SetArgPointee<1>(HWC_LAYER), Return(Error::NONE)));
 
-        layer->createHwcLayer(test->mFlinger.mFlinger->getBE().mHwc.get(), test->mDisplay->getId());
+        const auto displayId = test->mDisplay->getId();
+        ASSERT_TRUE(displayId);
+        layer->createHwcLayer(test->mFlinger.mFlinger->getBE().mHwc.get(), *displayId);
 
         Mock::VerifyAndClear(test->mComposer);
 
@@ -758,8 +762,10 @@
     static void cleanupInjectedLayers(CompositionTest* test) {
         EXPECT_CALL(*test->mComposer, destroyLayer(HWC_DISPLAY, HWC_LAYER))
                 .WillOnce(Return(Error::NONE));
+        const auto displayId = test->mDisplay->getId();
+        ASSERT_TRUE(displayId);
         for (auto layer : test->mFlinger.mutableDrawingState().layersSortedByZ) {
-            layer->destroyHwcLayer(test->mDisplay->getId());
+            layer->destroyHwcLayer(*displayId);
         }
         test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
     }
@@ -947,7 +953,7 @@
 
 struct ForcedClientCompositionResultVariant : public RECompositionResultVariant {
     static void setupLayerState(CompositionTest*, sp<Layer> layer) {
-        layer->forceClientComposition(DisplayDevice::DISPLAY_PRIMARY);
+        layer->forceClientComposition(DEFAULT_DISPLAY_ID);
     }
 
     template <typename Case>
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
index 4f1c99e..55995d0 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
@@ -68,32 +68,44 @@
 
 } // namespace
 
+const DisplayIdentificationData& getInternalEdid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    return data;
+}
+
+const DisplayIdentificationData& getExternalEdid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEdid);
+    return data;
+}
+
+const DisplayIdentificationData& getExternalEedid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEedid);
+    return data;
+}
+
 TEST(DisplayIdentificationTest, isEdid) {
     EXPECT_FALSE(isEdid({}));
 
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kInternalEdid)));
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kExternalEdid)));
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kExternalEedid)));
+    EXPECT_TRUE(isEdid(getInternalEdid()));
+    EXPECT_TRUE(isEdid(getExternalEdid()));
+    EXPECT_TRUE(isEdid(getExternalEedid()));
 }
 
 TEST(DisplayIdentificationTest, parseEdid) {
-    auto data = asDisplayIdentificationData(kInternalEdid);
-    auto edid = parseEdid(data);
+    auto edid = parseEdid(getInternalEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x4ca3u, edid->manufacturerId);
     EXPECT_STREQ("SEC", edid->pnpId.data());
     // ASCII text should be used as fallback if display name and serial number are missing.
     EXPECT_EQ("121AT11-801", edid->displayName);
 
-    data = asDisplayIdentificationData(kExternalEdid);
-    edid = parseEdid(data);
+    edid = parseEdid(getExternalEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x22f0u, edid->manufacturerId);
     EXPECT_STREQ("HWP", edid->pnpId.data());
     EXPECT_EQ("HP ZR30w", edid->displayName);
 
-    data = asDisplayIdentificationData(kExternalEedid);
-    edid = parseEdid(data);
+    edid = parseEdid(getExternalEedid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x4c2du, edid->manufacturerId);
     EXPECT_STREQ("SAM", edid->pnpId.data());
@@ -105,7 +117,7 @@
     EXPECT_FALSE(parseEdid({}));
 
     // Display name must be printable.
-    auto data = asDisplayIdentificationData(kExternalEdid);
+    auto data = getExternalEdid();
     data[97] = '\x1b';
     auto edid = parseEdid(data);
     ASSERT_TRUE(edid);
@@ -128,20 +140,32 @@
     EXPECT_STREQ("SAM", getPnpId(0x4c2du).value_or(PnpId{}).data());
 }
 
-TEST(DisplayIdentificationTest, generateDisplayId) {
-    const auto primaryId = generateDisplayId(0, asDisplayIdentificationData(kInternalEdid));
-    ASSERT_TRUE(primaryId);
+TEST(DisplayIdentificationTest, parseDisplayIdentificationData) {
+    const auto primaryInfo = parseDisplayIdentificationData(0, getInternalEdid());
+    ASSERT_TRUE(primaryInfo);
 
-    const auto secondaryId = generateDisplayId(1, asDisplayIdentificationData(kExternalEdid));
-    ASSERT_TRUE(secondaryId);
+    const auto secondaryInfo = parseDisplayIdentificationData(1, getExternalEdid());
+    ASSERT_TRUE(secondaryInfo);
 
-    const auto tertiaryId = generateDisplayId(2, asDisplayIdentificationData(kExternalEedid));
-    ASSERT_TRUE(tertiaryId);
+    const auto tertiaryInfo = parseDisplayIdentificationData(2, getExternalEedid());
+    ASSERT_TRUE(tertiaryInfo);
 
     // Display IDs should be unique.
-    EXPECT_NE(primaryId, secondaryId);
-    EXPECT_NE(primaryId, tertiaryId);
-    EXPECT_NE(secondaryId, tertiaryId);
+    EXPECT_NE(primaryInfo->id, secondaryInfo->id);
+    EXPECT_NE(primaryInfo->id, tertiaryInfo->id);
+    EXPECT_NE(secondaryInfo->id, tertiaryInfo->id);
+}
+
+TEST(DisplayIdentificationTest, getFallbackDisplayId) {
+    // Manufacturer ID should be invalid.
+    ASSERT_FALSE(getPnpId(getFallbackDisplayId(0)));
+    ASSERT_FALSE(getPnpId(getFallbackDisplayId(0xffu)));
+}
+
+TEST(DisplayIdentificationTest, getVirtualDisplayId) {
+    // Manufacturer ID should be invalid.
+    ASSERT_FALSE(getPnpId(getVirtualDisplayId(0)));
+    ASSERT_FALSE(getPnpId(getVirtualDisplayId(0xffff'ffffu)));
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
new file mode 100644
index 0000000..1c8e5cc
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "DisplayHardware/DisplayIdentification.h"
+
+namespace android {
+
+const DisplayIdentificationData& getInternalEdid();
+const DisplayIdentificationData& getExternalEdid();
+const DisplayIdentificationData& getExternalEedid();
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index d32627a..2e90a59 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -17,12 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <type_traits>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <log/log.h>
 
 #include <ui/DebugUtils.h>
+
+#include "DisplayIdentificationTest.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplaySurface.h"
@@ -75,9 +79,11 @@
 
 #define BOOL_SUBSTITUTE(TYPENAME) enum class TYPENAME : bool { FALSE = false, TRUE = true };
 
-BOOL_SUBSTITUTE(Critical);
 BOOL_SUBSTITUTE(Async);
+BOOL_SUBSTITUTE(Critical);
+BOOL_SUBSTITUTE(Primary);
 BOOL_SUBSTITUTE(Secure);
+BOOL_SUBSTITUTE(Virtual);
 
 /* ------------------------------------------------------------------------
  *
@@ -98,7 +104,7 @@
     // --------------------------------------------------------------------
     // Postcondition helpers
 
-    bool hasHwcDisplay(hwc2_display_t displayId);
+    bool hasPhysicalHwcDisplay(hwc2_display_t hwcDisplayId);
     bool hasTransactionFlagSet(int flag);
     bool hasDisplayDevice(sp<IBinder> displayToken);
     sp<DisplayDevice> getDisplayDevice(sp<IBinder> displayToken);
@@ -204,8 +210,8 @@
     });
 }
 
-bool DisplayTransactionTest::hasHwcDisplay(hwc2_display_t displayId) {
-    return mFlinger.mutableHwcDisplaySlots().count(displayId) == 1;
+bool DisplayTransactionTest::hasPhysicalHwcDisplay(hwc2_display_t hwcDisplayId) {
+    return mFlinger.mutableHwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
 }
 
 bool DisplayTransactionTest::hasTransactionFlagSet(int flag) {
@@ -240,20 +246,67 @@
  *
  */
 
-template <DisplayDevice::DisplayType type, DisplayDevice::DisplayType displayId, int width,
-          int height, Critical critical, Async async, Secure secure, int grallocUsage>
+template <typename PhysicalDisplay>
+struct PhysicalDisplayId {};
+
+template <DisplayId displayId>
+using VirtualDisplayId = std::integral_constant<DisplayId, displayId>;
+
+struct NoDisplayId {};
+
+template <typename>
+struct IsPhysicalDisplayId : std::bool_constant<false> {};
+
+template <typename PhysicalDisplay>
+struct IsPhysicalDisplayId<PhysicalDisplayId<PhysicalDisplay>> : std::bool_constant<true> {};
+
+template <typename>
+struct DisplayIdGetter;
+
+template <typename PhysicalDisplay>
+struct DisplayIdGetter<PhysicalDisplayId<PhysicalDisplay>> {
+    static std::optional<DisplayId> get() {
+        if (!PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
+            return getFallbackDisplayId(static_cast<bool>(PhysicalDisplay::PRIMARY)
+                                                ? HWC_DISPLAY_PRIMARY
+                                                : HWC_DISPLAY_EXTERNAL);
+        }
+
+        const auto info =
+                parseDisplayIdentificationData(PhysicalDisplay::PORT,
+                                               PhysicalDisplay::GET_IDENTIFICATION_DATA());
+        return info ? std::make_optional(info->id) : std::nullopt;
+    }
+};
+
+template <DisplayId displayId>
+struct DisplayIdGetter<VirtualDisplayId<displayId>> {
+    static std::optional<DisplayId> get() { return displayId; }
+};
+
+template <>
+struct DisplayIdGetter<NoDisplayId> {
+    static std::optional<DisplayId> get() { return {}; }
+};
+
+// DisplayIdType can be:
+//     1) PhysicalDisplayId<...> for generated ID of physical display backed by HWC.
+//     2) VirtualDisplayId<...> for hard-coded ID of virtual display backed by HWC.
+//     3) NoDisplayId for virtual display without HWC backing.
+template <typename DisplayIdType, int width, int height, Critical critical, Async async,
+          Secure secure, Primary primary, int grallocUsage>
 struct DisplayVariant {
+    using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
+
     // The display width and height
     static constexpr int WIDTH = width;
     static constexpr int HEIGHT = height;
 
     static constexpr int GRALLOC_USAGE = grallocUsage;
 
-    // The type for this display
-    static constexpr DisplayDevice::DisplayType TYPE = type;
-    static_assert(TYPE != DisplayDevice::DISPLAY_ID_INVALID);
-
-    static constexpr DisplayDevice::DisplayType DISPLAY_ID = displayId;
+    // Whether the display is virtual or physical
+    static constexpr Virtual VIRTUAL =
+            IsPhysicalDisplayId<DisplayIdType>{} ? Virtual::FALSE : Virtual::TRUE;
 
     // When creating native window surfaces for the framebuffer, whether those should be critical
     static constexpr Critical CRITICAL = critical;
@@ -264,8 +317,14 @@
     // Whether the display should be treated as secure
     static constexpr Secure SECURE = secure;
 
+    // Whether the display is primary
+    static constexpr Primary PRIMARY = primary;
+
     static auto makeFakeExistingDisplayInjector(DisplayTransactionTest* test) {
-        auto injector = FakeDisplayDeviceInjector(test->mFlinger, TYPE, DISPLAY_ID);
+        auto injector =
+                FakeDisplayDeviceInjector(test->mFlinger, DISPLAY_ID::get(),
+                                          static_cast<bool>(VIRTUAL), static_cast<bool>(PRIMARY));
+
         injector.setSecure(static_cast<bool>(SECURE));
         return injector;
     }
@@ -306,7 +365,8 @@
     }
 };
 
-template <hwc2_display_t hwcDisplayId, HWC2::DisplayType hwcDisplayType, typename DisplayVariant>
+template <hwc2_display_t hwcDisplayId, HWC2::DisplayType hwcDisplayType, typename DisplayVariant,
+          typename PhysicalDisplay = void>
 struct HwcDisplayVariant {
     // The display id supplied by the HWC
     static constexpr hwc2_display_t HWC_DISPLAY_ID = hwcDisplayId;
@@ -325,7 +385,10 @@
 
     // Called by tests to inject a HWC display setup
     static void injectHwcDisplay(DisplayTransactionTest* test) {
-        FakeHwcDisplayInjector(DisplayVariant::TYPE, HWC_DISPLAY_TYPE)
+        const auto displayId = DisplayVariant::DISPLAY_ID::get();
+        ASSERT_TRUE(displayId);
+        FakeHwcDisplayInjector(*displayId, HWC_DISPLAY_TYPE,
+                               static_cast<bool>(DisplayVariant::PRIMARY))
                 .setHwcDisplayId(HWC_DISPLAY_ID)
                 .setWidth(DisplayVariant::WIDTH)
                 .setHeight(DisplayVariant::HEIGHT)
@@ -362,8 +425,16 @@
                     getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
                                         IComposerClient::Attribute::DPI_Y, _))
                 .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
-        EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
-                .WillRepeatedly(Return(Error::UNSUPPORTED));
+
+        if (PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
+            EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
+                    .WillOnce(DoAll(SetArgPointee<1>(PhysicalDisplay::PORT),
+                                    SetArgPointee<2>(PhysicalDisplay::GET_IDENTIFICATION_DATA()),
+                                    Return(Error::NONE)));
+        } else {
+            EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
+                    .WillOnce(Return(Error::UNSUPPORTED));
+        }
     }
 
     // Called by tests to set up HWC call expectations
@@ -373,50 +444,67 @@
     }
 };
 
-struct NonHwcDisplayVariant {
-    static void injectHwcDisplay(DisplayTransactionTest*) {}
-
-    static void setupHwcGetActiveConfigCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mComposer, getActiveConfig(_, _)).Times(0);
-    }
-};
-
 // Physical displays are expected to be synchronous, secure, and have a HWC display for output.
 constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY =
         GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_FB;
 
-template <hwc2_display_t hwcDisplayId, DisplayDevice::DisplayType type, int width, int height,
+template <hwc2_display_t hwcDisplayId, typename PhysicalDisplay, int width, int height,
           Critical critical>
 struct PhysicalDisplayVariant
-      : public DisplayVariant<type, type, width, height, critical, Async::FALSE, Secure::TRUE,
-                              GRALLOC_USAGE_PHYSICAL_DISPLAY>,
-        public HwcDisplayVariant<hwcDisplayId, HWC2::DisplayType::Physical,
-                                 DisplayVariant<type, type, width, height, critical, Async::FALSE,
-                                                Secure::TRUE, GRALLOC_USAGE_PHYSICAL_DISPLAY>> {};
+      : DisplayVariant<PhysicalDisplayId<PhysicalDisplay>, width, height, critical, Async::FALSE,
+                       Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY>,
+        HwcDisplayVariant<hwcDisplayId, HWC2::DisplayType::Physical,
+                          DisplayVariant<PhysicalDisplayId<PhysicalDisplay>, width, height,
+                                         critical, Async::FALSE, Secure::TRUE,
+                                         PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY>,
+                          PhysicalDisplay> {};
+
+template <bool hasIdentificationData>
+struct PrimaryDisplay {
+    static constexpr Primary PRIMARY = Primary::TRUE;
+    static constexpr uint8_t PORT = 255;
+    static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
+    static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
+};
+
+template <bool hasIdentificationData>
+struct ExternalDisplay {
+    static constexpr Primary PRIMARY = Primary::FALSE;
+    static constexpr uint8_t PORT = 254;
+    static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
+    static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
+};
+
+struct TertiaryDisplay {
+    static constexpr Primary PRIMARY = Primary::FALSE;
+};
 
 // A primary display is a physical display that is critical
 using PrimaryDisplayVariant =
-        PhysicalDisplayVariant<1001, DisplayDevice::DISPLAY_PRIMARY, 3840, 2160, Critical::TRUE>;
+        PhysicalDisplayVariant<1001, PrimaryDisplay<false>, 3840, 2160, Critical::TRUE>;
 
 // An external display is physical display that is not critical.
 using ExternalDisplayVariant =
-        PhysicalDisplayVariant<1002, DisplayDevice::DISPLAY_EXTERNAL, 1920, 1280, Critical::FALSE>;
+        PhysicalDisplayVariant<1002, ExternalDisplay<false>, 1920, 1280, Critical::FALSE>;
 
 using TertiaryDisplayVariant =
-        PhysicalDisplayVariant<1003, DisplayDevice::DISPLAY_EXTERNAL, 1600, 1200, Critical::FALSE>;
+        PhysicalDisplayVariant<1003, TertiaryDisplay, 1600, 1200, Critical::FALSE>;
 
 // A virtual display not supported by the HWC.
 constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0;
 
 template <int width, int height, Secure secure>
 struct NonHwcVirtualDisplayVariant
-      : public DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_ID_INVALID,
-                              width, height, Critical::FALSE, Async::TRUE, secure,
-                              GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>,
-        public NonHwcDisplayVariant {
-    using Base = DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_ID_INVALID,
-                                width, height, Critical::FALSE, Async::TRUE, secure,
-                                GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>;
+      : DisplayVariant<NoDisplayId, width, height, Critical::FALSE, Async::TRUE, secure,
+                       Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY> {
+    using Base = DisplayVariant<NoDisplayId, width, height, Critical::FALSE, Async::TRUE, secure,
+                                Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>;
+
+    static void injectHwcDisplay(DisplayTransactionTest*) {}
+
+    static void setupHwcGetActiveConfigCallExpectations(DisplayTransactionTest* test) {
+        EXPECT_CALL(*test->mComposer, getActiveConfig(_, _)).Times(0);
+    }
 
     static void setupNativeWindowSurfaceCreationCallExpectations(DisplayTransactionTest* test) {
         Base::setupNativeWindowSurfaceCreationCallExpectations(test);
@@ -429,14 +517,14 @@
 
 template <int width, int height, Secure secure>
 struct HwcVirtualDisplayVariant
-      : public DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_VIRTUAL, width,
-                              height, Critical::FALSE, Async::TRUE, secure,
-                              GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>,
-        public HwcDisplayVariant<1010, HWC2::DisplayType::Virtual,
-                                 NonHwcVirtualDisplayVariant<width, height, secure>> {
-    using Base =
-            DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_VIRTUAL, width,
-                           height, Critical::FALSE, Async::TRUE, secure, GRALLOC_USAGE_HW_COMPOSER>;
+      : DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE, secure,
+                       Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>,
+        HwcDisplayVariant<
+                1010, HWC2::DisplayType::Virtual,
+                DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE,
+                               secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>> {
+    using Base = DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE,
+                                secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER>;
     using Self = HwcVirtualDisplayVariant<width, height, secure>;
 
     static void setupNativeWindowSurfaceCreationCallExpectations(DisplayTransactionTest* test) {
@@ -850,8 +938,8 @@
     // The display should have been added to the current state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& display = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(DisplayDevice::DISPLAY_VIRTUAL, display.type);
-    EXPECT_EQ(false, display.isSecure);
+    EXPECT_TRUE(display.isVirtual());
+    EXPECT_FALSE(display.isSecure);
     EXPECT_EQ(name.string(), display.displayName);
 
     // --------------------------------------------------------------------
@@ -881,8 +969,8 @@
     // The display should have been added to the current state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& display = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(DisplayDevice::DISPLAY_VIRTUAL, display.type);
-    EXPECT_EQ(true, display.isSecure);
+    EXPECT_TRUE(display.isVirtual());
+    EXPECT_TRUE(display.isSecure);
     EXPECT_EQ(name.string(), display.displayName);
 
     // --------------------------------------------------------------------
@@ -1002,9 +1090,12 @@
  */
 class GetBestColorModeTest : public DisplayTransactionTest {
 public:
+    static constexpr DisplayId DEFAULT_DISPLAY_ID = 777;
+
     GetBestColorModeTest()
           : DisplayTransactionTest(),
-            mInjector(FakeDisplayDeviceInjector(mFlinger, DisplayDevice::DISPLAY_PRIMARY, 0)) {}
+            mInjector(FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID, false /* isVirtual */,
+                                                true /* isPrimary */)) {}
 
     void setHasWideColorGamut(bool hasWideColorGamut) { mHasWideColorGamut = hasWideColorGamut; }
 
@@ -1127,18 +1218,22 @@
     // Invocation
 
     DisplayDeviceState state;
-    state.type = Case::Display::TYPE;
+    state.displayId = static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                                : Case::Display::DISPLAY_ID::get();
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
-    auto device = mFlinger.setupNewDisplayDeviceInternal(displayToken, Case::Display::TYPE, state,
-                                                         displaySurface, producer);
+    auto device =
+            mFlinger.setupNewDisplayDeviceInternal(displayToken, Case::Display::DISPLAY_ID::get(),
+                                                   state, displaySurface, producer);
 
     // --------------------------------------------------------------------
     // Postconditions
 
     ASSERT_TRUE(device != nullptr);
-    EXPECT_EQ(Case::Display::TYPE, device->getDisplayType());
+    EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), device->isVirtual());
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
     EXPECT_EQ(Case::Display::WIDTH, device->getWidth());
     EXPECT_EQ(Case::Display::HEIGHT, device->getHeight());
     EXPECT_EQ(Case::WideColorSupport::WIDE_COLOR_SUPPORTED, device->hasWideColorGamut());
@@ -1166,11 +1261,14 @@
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createHwcVirtualDisplay) {
-    // We need to resize this so that the HWC thinks the virtual display
-    // is something it created.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    using Case = HwcVirtualDisplayCase;
 
-    setupNewDisplayDeviceInternalTest<HwcVirtualDisplayCase>();
+    // Insert display data so that the HWC thinks it created the virtual display.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
+
+    setupNewDisplayDeviceInternalTest<Case>();
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createWideColorP3Display) {
@@ -1258,7 +1356,7 @@
 
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1);
     EXPECT_CALL(*mEventThread,
-                onHotplugReceived(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY
+                onHotplugReceived(static_cast<bool>(Case::Display::PRIMARY)
                                           ? EventThread::DisplayType::Primary
                                           : EventThread::DisplayType::External,
                                   true))
@@ -1269,7 +1367,7 @@
 void HandleTransactionLockedTest::setupCommonCallExpectationsForDisconnectProcessing() {
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
     EXPECT_CALL(*mEventThread,
-                onHotplugReceived(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY
+                onHotplugReceived(static_cast<bool>(Case::Display::PRIMARY)
                                           ? EventThread::DisplayType::Primary
                                           : EventThread::DisplayType::External,
                                   false))
@@ -1282,30 +1380,35 @@
     ASSERT_TRUE(hasDisplayDevice(displayToken));
     const auto& device = getDisplayDevice(displayToken);
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
-    EXPECT_EQ(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY, device->isPrimary());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
 
     // The display should have been set up in the current display state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& current = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, current.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), current.isVirtual());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                        : Case::Display::DISPLAY_ID::get(),
+              current.displayId);
 
     // The display should have been set up in the drawing display state
     ASSERT_TRUE(hasDrawingDisplayState(displayToken));
     const auto& draw = getDrawingDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, draw.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                        : Case::Display::DISPLAY_ID::get(),
+              draw.displayId);
 }
 
 template <typename Case>
 void HandleTransactionLockedTest::verifyPhysicalDisplayIsConnected() {
     // HWComposer should have an entry for the display
-    EXPECT_TRUE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_TRUE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should be set up as a built-in display.
-    static_assert(0 <= Case::Display::TYPE &&
-                          Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES,
-                  "Must use a valid physical display type index for the fixed-size array");
-    auto& displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    ASSERT_TRUE(displayToken != nullptr);
+    // SF should have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 1);
+    auto& displayToken = mFlinger.mutablePhysicalDisplayTokens()[*displayId];
 
     verifyDisplayIsConnected<Case>(displayToken);
 }
@@ -1371,7 +1474,7 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 }
 
 template <typename Case>
@@ -1407,13 +1510,12 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should not be set up as a built-in display.
-    ASSERT_TRUE(0 <= Case::Display::TYPE &&
-                Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES);
-    auto displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    EXPECT_TRUE(displayToken == nullptr);
+    // SF should not have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 0);
 
     // The existing token should have been removed
     verifyDisplayIsNotConnected(existing.token());
@@ -1500,13 +1602,12 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should not be set up as a primary built-in display.
-    ASSERT_TRUE(0 <= Case::Display::TYPE &&
-                Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES);
-    auto displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    EXPECT_TRUE(displayToken == nullptr);
+    // SF should not have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 0);
 }
 
 TEST_F(HandleTransactionLockedTest, processesHotplugDisconnectThenConnectPrimary) {
@@ -1545,10 +1646,10 @@
 
     // The existing token should have been removed
     verifyDisplayIsNotConnected(existing.token());
-    static_assert(0 <= Case::Display::TYPE &&
-                          Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES,
-                  "Display type must be a built-in display");
-    EXPECT_NE(existing.token(), mFlinger.mutableDisplayTokens()[Case::Display::TYPE]);
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 1);
+    EXPECT_NE(existing.token(), mFlinger.mutablePhysicalDisplayTokens()[*displayId]);
 
     // A new display should be connected in its place
 
@@ -1578,13 +1679,12 @@
     // surface(producer)
     sp<BBinder> displayToken = new BBinder();
 
-    DisplayDeviceState info;
-    info.type = Case::Display::TYPE;
-    info.isSecure = static_cast<bool>(Case::Display::SECURE);
+    DisplayDeviceState state;
+    state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
     sp<mock::GraphicBufferProducer> surface{new mock::GraphicBufferProducer()};
-    info.surface = surface;
-    mFlinger.mutableCurrentState().displays.add(displayToken, info);
+    state.surface = surface;
+    mFlinger.mutableCurrentState().displays.add(displayToken, state);
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -1646,11 +1746,10 @@
     // surface.
     sp<BBinder> displayToken = new BBinder();
 
-    DisplayDeviceState info;
-    info.type = Case::Display::TYPE;
-    info.isSecure = static_cast<bool>(Case::Display::SECURE);
+    DisplayDeviceState state;
+    state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
-    mFlinger.mutableCurrentState().displays.add(displayToken, info);
+    mFlinger.mutableCurrentState().displays.add(displayToken, state);
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -1669,7 +1768,7 @@
     // The drawing display state will be set from the current display state.
     ASSERT_TRUE(hasDrawingDisplayState(displayToken));
     const auto& draw = getDrawingDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, draw.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
 }
 
 TEST_F(HandleTransactionLockedTest, processesVirtualDisplayRemoval) {
@@ -1679,7 +1778,9 @@
     // Preconditions
 
     // A virtual display is set up but is removed from the current state.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
     Case::Display::injectHwcDisplay(this);
     auto existing = Case::Display::makeFakeExistingDisplayInjector(this);
     existing.inject();
@@ -2799,9 +2900,10 @@
     // --------------------------------------------------------------------
     // Preconditions
 
-    // We need to resize this so that the HWC thinks the virtual display
-    // is something it created.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    // Insert display data so that the HWC thinks it created the virtual display.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
 
     // A virtual display device is set up
     Case::Display::injectHwcDisplay(this);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index c3534e8..b046e4a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -178,10 +178,6 @@
         layer->getBE().compositionInfo.hwc.sidebandStream = sidebandStream;
     }
 
-    void setLayerCompositionType(sp<Layer> layer, HWC2::Composition type) {
-        layer->getBE().mHwcLayers[DisplayDevice::DISPLAY_PRIMARY].compositionType = type;
-    };
-
     void setLayerPotentialCursor(sp<Layer> layer, bool potentialCursor) {
         layer->mPotentialCursor = potentialCursor;
     }
@@ -200,7 +196,8 @@
 
     auto resetDisplayState() { return mFlinger->resetDisplayState(); }
 
-    auto setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken, int32_t displayId,
+    auto setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken,
+                                       const std::optional<DisplayId>& displayId,
                                        const DisplayDeviceState& state,
                                        const sp<DisplaySurface>& dispSurface,
                                        const sp<IGraphicBufferProducer>& producer) {
@@ -263,7 +260,6 @@
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
     auto& mutableDisplays() { return mFlinger->mDisplays; }
-    auto& mutableDisplayTokens() { return mFlinger->mDisplayTokens; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableEventControlThread() { return mFlinger->mEventControlThread; }
     auto& mutableEventQueue() { return mFlinger->mEventQueue; }
@@ -273,6 +269,7 @@
     auto& mutableInterceptor() { return mFlinger->mInterceptor; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
+    auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; }
     auto& mutablePrimaryDispSync() { return mFlinger->mPrimaryDispSync; }
     auto& mutablePrimaryHWVsyncEnabled() { return mFlinger->mPrimaryHWVsyncEnabled; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
@@ -280,8 +277,13 @@
     auto& mutableUseHwcVirtualDisplays() { return mFlinger->mUseHwcVirtualDisplays; }
 
     auto& mutableComposerSequenceId() { return mFlinger->getBE().mComposerSequenceId; }
-    auto& mutableHwcDisplayData() { return mFlinger->getBE().mHwc->mDisplayData; }
-    auto& mutableHwcDisplaySlots() { return mFlinger->getBE().mHwc->mHwcDisplaySlots; }
+    auto& mutableHwcDisplayData() { return mFlinger->getHwComposer().mDisplayData; }
+    auto& mutableHwcPhysicalDisplayIdMap() {
+        return mFlinger->getHwComposer().mPhysicalDisplayIdMap;
+    }
+
+    auto& mutableInternalHwcDisplayId() { return mFlinger->getHwComposer().mInternalHwcDisplayId; }
+    auto& mutableExternalHwcDisplayId() { return mFlinger->getHwComposer().mExternalHwcDisplayId; }
 
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
@@ -333,8 +335,9 @@
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr int32_t DEFAULT_ACTIVE_CONFIG = 0;
 
-        FakeHwcDisplayInjector(DisplayDevice::DisplayType type, HWC2::DisplayType hwcDisplayType)
-              : mType(type), mHwcDisplayType(hwcDisplayType) {}
+        FakeHwcDisplayInjector(DisplayId displayId, HWC2::DisplayType hwcDisplayType,
+                               bool isPrimary)
+              : mDisplayId(displayId), mHwcDisplayType(hwcDisplayType), mIsPrimary(isPrimary) {}
 
         auto& setHwcDisplayId(hwc2_display_t displayId) {
             mHwcDisplayId = displayId;
@@ -403,17 +406,22 @@
             display->mutableConfigs().emplace(mActiveConfig, config.build());
             display->mutableIsConnected() = true;
 
-            ASSERT_TRUE(flinger->mutableHwcDisplayData().size() > static_cast<size_t>(mType));
-            flinger->mutableHwcDisplayData()[mType] = HWComposer::DisplayData();
-            flinger->mutableHwcDisplayData()[mType].hwcDisplay = display.get();
-            flinger->mutableHwcDisplaySlots().emplace(mHwcDisplayId, mType);
+            flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = display.get();
+
+            if (mHwcDisplayType == HWC2::DisplayType::Physical) {
+                flinger->mutableHwcPhysicalDisplayIdMap().emplace(mHwcDisplayId, mDisplayId);
+                (mIsPrimary ? flinger->mutableInternalHwcDisplayId()
+                            : flinger->mutableExternalHwcDisplayId()) = mHwcDisplayId;
+            }
 
             flinger->mFakeHwcDisplays.push_back(std::move(display));
         }
 
     private:
-        DisplayDevice::DisplayType mType;
-        HWC2::DisplayType mHwcDisplayType;
+        const DisplayId mDisplayId;
+        const HWC2::DisplayType mHwcDisplayType;
+        const bool mIsPrimary;
+
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
@@ -427,10 +435,13 @@
 
     class FakeDisplayDeviceInjector {
     public:
-        FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger, DisplayDevice::DisplayType type,
-                                  int32_t displayId)
-              : mFlinger(flinger),
-                mCreationArgs(flinger.mFlinger.get(), mDisplayToken, type, displayId) {}
+        FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
+                                  const std::optional<DisplayId>& displayId, bool isVirtual,
+                                  bool isPrimary)
+              : mFlinger(flinger), mCreationArgs(flinger.mFlinger.get(), mDisplayToken, displayId) {
+            mCreationArgs.isVirtual = isVirtual;
+            mCreationArgs.isPrimary = isPrimary;
+        }
 
         sp<IBinder> token() const { return mDisplayToken; }
 
@@ -497,7 +508,7 @@
 
         sp<DisplayDevice> inject() {
             DisplayDeviceState state;
-            state.type = mCreationArgs.type;
+            state.displayId = mCreationArgs.isVirtual ? std::nullopt : mCreationArgs.displayId;
             state.isSecure = mCreationArgs.isSecure;
 
             sp<DisplayDevice> device = new DisplayDevice(std::move(mCreationArgs));
@@ -505,9 +516,9 @@
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
-            if (state.type >= DisplayDevice::DISPLAY_PRIMARY &&
-                state.type < DisplayDevice::DISPLAY_VIRTUAL) {
-                mFlinger.mutableDisplayTokens()[state.type] = mDisplayToken;
+            if (!mCreationArgs.isVirtual) {
+                LOG_ALWAYS_FATAL_IF(!state.displayId);
+                mFlinger.mutablePhysicalDisplayTokens()[*state.displayId] = mDisplayToken;
             }
 
             return device;