Upgrade drm_hwcomposer to 7009cc1909804ef707376441b5b3f986b492d66a

This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/drm_hwcomposer
For more info, check https://cs.android.com/android/platform/superproject/main/+/main:tools/external_updater/README.md

Test: TreeHugger
Change-Id: Iccce48405a92ce7e469f1c60b04fb576e2702838
diff --git a/.ci/Makefile b/.ci/Makefile
index 0dba6cc..d57ea86 100644
--- a/.ci/Makefile
+++ b/.ci/Makefile
@@ -9,7 +9,7 @@
 SRC_DIR := .
 
 CXXFLAGS := -Wall -Wextra -Werror -Wno-missing-designated-field-initializers
-CXXFLAGS += -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
+CXXFLAGS += -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -Wthread-safety
 CXXFLAGS += -fvisibility-inlines-hidden -std=gnu++17 -DHWC2_USE_CPP11 -DHWC2_INCLUDE_STRINGIFICATION -fno-rtti
 
 CXXARGS := $(shell cat $(BASE_DIR)/toolchain_wrapper/sharedlib.cppflags)
diff --git a/.ci/container/.gitlab-ci.yml b/.ci/container/.gitlab-ci.yml
new file mode 100644
index 0000000..85024f8
--- /dev/null
+++ b/.ci/container/.gitlab-ci.yml
@@ -0,0 +1,37 @@
+
+.incorporate-templates-commit:
+  variables:
+    FDO_DISTRIBUTION_TAG: "${HWC_TAG}--${CI_TEMPLATES_COMMIT}"
+
+.container:
+  stage: container
+  extends:
+    - .incorporate-templates-commit
+  variables:
+    FDO_REPO_SUFFIX: $CI_JOB_NAME
+    # no need to pull the whole repo to build the container image
+    GIT_STRATEGY: none
+
+ubuntu/x86_64_hwc:
+  extends:
+    - .fdo.container-build@ubuntu
+    - .container
+  variables:
+    FDO_DISTRIBUTION_VERSION: "24.10"
+    FDO_DISTRIBUTION_EXEC: 'bash .ci/container/ubuntu/x86_64_aospless.sh'
+    HWC_TAG: $UBUNTU_HWC_TAG
+
+.set-image:
+  extends:
+    - .incorporate-templates-commit
+  image: "$CI_REGISTRY_IMAGE/${HWC_IMAGE}:${FDO_DISTRIBUTION_TAG}"
+
+.use-ubuntu/x86_64_hwc:
+  extends:
+    - .fdo.container-build@ubuntu
+    - .set-image
+  variables:
+    HWC_TAG: $UBUNTU_HWC_TAG
+    HWC_IMAGE: $UBUNTU_HWC_IMAGE
+  needs:
+    - ubuntu/x86_64_hwc
diff --git a/.ci/container/ubuntu/x86_64_aospless.sh b/.ci/container/ubuntu/x86_64_aospless.sh
new file mode 100644
index 0000000..9b6e49d
--- /dev/null
+++ b/.ci/container/ubuntu/x86_64_aospless.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+# For any changes to this file to take effect, the UBUNTU_HWC_TAG has
+# to be bumped to generate a new image.
+
+set -ex
+
+DEPS=(
+    clang
+    llvm
+    clang-19
+    clang-tidy-19
+    clang-format-19
+    ca-certificates
+    git
+    libdrm-dev
+    blueprint-tools
+    libgtest-dev
+    make
+    python3
+    wget
+    sudo
+    rsync
+    lld
+    pkg-config
+    ninja-build
+    meson
+    python3-mako
+    python3-jinja2
+    python3-ply
+    python3-yaml
+    wget
+    gnupg
+    xz-utils
+)
+
+export DEBIAN_FRONTEND=noninteractive
+
+apt-get update
+apt-get upgrade -y
+
+apt-get install -y --no-remove --no-install-recommends "${DEPS[@]}"
+
+wget https://gitlab.freedesktop.org/-/project/5/uploads/cafa930dad28acf7ee44d50101d5e8f0/aospless_drm_hwcomposer_arm64.tar.xz
+
+sha256sum aospless_drm_hwcomposer_arm64.tar.xz
+if echo f792b1140861112f80c8a3a22e1af8e3eccf4910fe4449705e62d2032b713bf9 aospless_drm_hwcomposer_arm64.tar.xz | sha256sum --check; then
+    tar --no-same-owner -xf aospless_drm_hwcomposer_arm64.tar.xz -C /
+else
+    echo "Tar file check failed"
+    exit 1
+fi
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fbe2a85..3d39f17 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,3 @@
-image: ubuntu:24.10
-
 workflow:
   rules:
     - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
@@ -7,52 +5,53 @@
 
 variables:
   DEBIAN_FRONTEND: noninteractive
+  CI_TEMPLATES_COMMIT: &ci-templates-commit e195d80f35b45cc73668be3767b923fd76c70ed5
+  FDO_UPSTREAM_REPO: "drm-hwcomposer/drm-hwcomposer"
+  UBUNTU_HWC_IMAGE: "ubuntu/x86_64_hwc"
+  UBUNTU_HWC_TAG: "20250218_hwc"
 
-before_script:
-  - apt-get --quiet update --yes >/dev/null
-  - apt-get --quiet install --yes clang-19 clang-tidy-19 clang-format-19 git libdrm-dev blueprint-tools libgtest-dev make >/dev/null
-  - apt-get --quiet install --yes clang llvm make python3 wget sudo rsync lld pkg-config ninja-build meson >/dev/null
-  - apt-get --quiet install --yes python3-mako python3-jinja2 python3-ply python3-yaml >/dev/null
+include:
+  - project: 'freedesktop/ci-templates'
+    ref: *ci-templates-commit
+    file:
+      - '/templates/ubuntu.yml'
+  - local: '.ci/container/.gitlab-ci.yml'
 
 stages:
+  - container
   - build
   - tidy
   - style
 
 build:
   stage: build
+  extends:
+    .use-ubuntu/x86_64_hwc
   script:
-    - mkdir -p install/arm64
-    - cd ..
-    - rm -f aospless_drm_hwcomposer_arm64.tar.xz
-    - rm -rf aospless/*
-    - wget https://gitlab.freedesktop.org/-/project/5/uploads/cafa930dad28acf7ee44d50101d5e8f0/aospless_drm_hwcomposer_arm64.tar.xz
-    - tar xf aospless_drm_hwcomposer_arm64.tar.xz
-    - rm -rf aospless/src
-    - ln -s ../drm-hwcomposer/ aospless/src
-    - make -C ./aospless install
-    - cp -r aospless/install/* drm-hwcomposer/install/arm64
-    - ls drm-hwcomposer/install/arm64
-
+    - ln -s "${CI_PROJECT_DIR}" "/aospless/src"
+    - make -C /aospless install
+    - mkdir -p "${CI_PROJECT_DIR}/install/arm64"
+    - cp -r /aospless/install/* "${CI_PROJECT_DIR}/install/arm64"
   artifacts:
     paths:
-      - install/
+      - "${CI_PROJECT_DIR}/install/arm64"
     expire_in: 1 week
 
 tidy:
   stage: tidy
+  extends:
+    .use-ubuntu/x86_64_hwc
   script:
-    - cd ..
-    - rm -f aospless_drm_hwcomposer_arm64.tar.xz
-    - rm -rf aospless/*
-    - wget https://gitlab.freedesktop.org/-/project/5/uploads/cafa930dad28acf7ee44d50101d5e8f0/aospless_drm_hwcomposer_arm64.tar.xz
-    - tar xf aospless_drm_hwcomposer_arm64.tar.xz
-    - cd -
-    - make -j$(nproc) -k -f .ci/Makefile
+    - cp -r /aospless ../
+    - make -j$(nproc) -k -f "${CI_PROJECT_DIR}/.ci/Makefile"
 
 checkstyle:
   stage: style
+  extends:
+    .use-ubuntu/x86_64_hwc
   script: "./.ci/.gitlab-ci-checkcommit.sh"
   artifacts:
     when: on_failure
     untracked: true
+  tags:
+    - placeholder-job
diff --git a/METADATA b/METADATA
index afe2045..2d137a9 100644
--- a/METADATA
+++ b/METADATA
@@ -8,12 +8,12 @@
   license_type: NOTICE
   last_upgrade_date {
     year: 2025
-    month: 2
-    day: 18
+    month: 3
+    day: 13
   }
   identifier {
     type: "Git"
     value: "https://gitlab.freedesktop.org/drm-hwcomposer/drm-hwcomposer"
-    version: "75855a6ab877011a2e26ae95da7ddf06dd3a04b2"
+    version: "7009cc1909804ef707376441b5b3f986b492d66a"
   }
 }
diff --git a/backend/Backend.cpp b/backend/Backend.cpp
index bf45c08..650e29e 100644
--- a/backend/Backend.cpp
+++ b/backend/Backend.cpp
@@ -130,7 +130,7 @@
 std::tuple<int, int> Backend::GetExtraClientRange(
     HwcDisplay *display, const std::vector<HwcLayer *> &layers,
     int client_start, size_t client_size) {
-  auto planes = display->GetPipe().GetUsablePlanes();
+  auto [planes, cursor_plane] = display->GetPipe().GetUsablePlanes();
   size_t avail_planes = planes.size();
 
   /*
diff --git a/compositor/DrmKmsPlan.cpp b/compositor/DrmKmsPlan.cpp
index 4f75e89..443b515 100644
--- a/compositor/DrmKmsPlan.cpp
+++ b/compositor/DrmKmsPlan.cpp
@@ -28,7 +28,7 @@
     -> std::unique_ptr<DrmKmsPlan> {
   auto plan = std::make_unique<DrmKmsPlan>();
 
-  auto avail_planes = pipe.GetUsablePlanes();
+  auto [avail_planes, cursor_plane] = pipe.GetUsablePlanes();
 
   int z_pos = 0;
   for (auto &dhl : composition) {
diff --git a/compositor/LayerData.h b/compositor/LayerData.h
index 962141f..90ae7be 100644
--- a/compositor/LayerData.h
+++ b/compositor/LayerData.h
@@ -30,6 +30,8 @@
 
 class DrmFbIdHandle;
 
+using ILayerId = int64_t;
+
 /* Rotation is defined in the clockwise direction */
 /* The flip is done before rotation */
 struct LayerTransform {
diff --git a/drm/DrmDevice.cpp b/drm/DrmDevice.cpp
index 6fd5c0c..f6141d4 100644
--- a/drm/DrmDevice.cpp
+++ b/drm/DrmDevice.cpp
@@ -90,6 +90,14 @@
   }
   HasAddFb2ModifiersSupport_ = cap_value != 0;
 
+  uint64_t cursor_width = 0;
+  uint64_t cursor_height = 0;
+  if (drmGetCap(*GetFd(), DRM_CAP_CURSOR_WIDTH, &cursor_width) == 0 &&
+      drmGetCap(*GetFd(), DRM_CAP_CURSOR_HEIGHT, &cursor_height) == 0) {
+    cap_cursor_size_ = std::pair<uint64_t, uint64_t>(cursor_width,
+                                                     cursor_height);
+  }
+
   drmSetMaster(*GetFd());
   if (drmIsMaster(*GetFd()) == 0) {
     ALOGE("DRM/KMS master access required");
@@ -200,7 +208,7 @@
     drmModePropertyPtr p = drmModeGetProperty(*GetFd(), props->props[i]);
     if (strcmp(p->name, prop_name) == 0) {
       // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
-      property->Init(obj_id, p, props->prop_values[i]);
+      property->Init(GetFd(), obj_id, p, props->prop_values[i]);
       found = true;
     }
     drmModeFreeProperty(p);
diff --git a/drm/DrmDevice.h b/drm/DrmDevice.h
index baa719d..ac20855 100644
--- a/drm/DrmDevice.h
+++ b/drm/DrmDevice.h
@@ -106,6 +106,10 @@
   int GetProperty(uint32_t obj_id, uint32_t obj_type, const char *prop_name,
                   DrmProperty *property) const;
 
+  const std::optional<std::pair<uint64_t, uint64_t>> &GetCapCursorSize() const {
+    return cap_cursor_size_;
+  }
+
  private:
   explicit DrmDevice(ResourceManager *res_man, uint32_t index);
   auto Init(const char *path) -> int;
@@ -123,6 +127,7 @@
 
   std::pair<uint32_t, uint32_t> min_resolution_;
   std::pair<uint32_t, uint32_t> max_resolution_;
+  std::optional<std::pair<uint64_t, uint64_t>> cap_cursor_size_;
 
   bool HasAddFb2ModifiersSupport_{};
 
@@ -130,4 +135,5 @@
 
   ResourceManager *const res_man_;
 };
+
 }  // namespace android
diff --git a/drm/DrmDisplayPipeline.cpp b/drm/DrmDisplayPipeline.cpp
index 7588ee2..8062b2c 100644
--- a/drm/DrmDisplayPipeline.cpp
+++ b/drm/DrmDisplayPipeline.cpp
@@ -65,18 +65,21 @@
   }
 
   std::vector<DrmPlane *> primary_planes;
-  std::vector<DrmPlane *> overlay_planes;
 
   /* Attach necessary resources */
   auto display_planes = std::vector<DrmPlane *>();
   for (const auto &plane : dev.GetPlanes()) {
     if (plane->IsCrtcSupported(crtc)) {
-      if (plane->GetType() == DRM_PLANE_TYPE_PRIMARY) {
-        primary_planes.emplace_back(plane.get());
-      } else if (plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
-        overlay_planes.emplace_back(plane.get());
-      } else {
-        ALOGI("Ignoring cursor plane %d", plane->GetId());
+      switch (plane->GetType()) {
+        case DRM_PLANE_TYPE_PRIMARY:
+          primary_planes.emplace_back(plane.get());
+          break;
+        case DRM_PLANE_TYPE_OVERLAY:
+        case DRM_PLANE_TYPE_CURSOR:
+          break;
+        default:
+          ALOGE("Unknown type for plane %d", plane->GetId());
+          break;
       }
     }
   }
@@ -158,25 +161,34 @@
   return {};
 }
 
-auto DrmDisplayPipeline::GetUsablePlanes()
-    -> std::vector<std::shared_ptr<BindingOwner<DrmPlane>>> {
-  std::vector<std::shared_ptr<BindingOwner<DrmPlane>>> planes;
+auto DrmDisplayPipeline::GetUsablePlanes() -> UsablePlanes {
+  UsablePlanes pair;
+  auto &[planes, cursor] = pair;
+
   planes.emplace_back(primary_plane);
 
-  if (Properties::UseOverlayPlanes()) {
-    for (const auto &plane : device->GetPlanes()) {
-      if (plane->IsCrtcSupported(*crtc->Get())) {
-        if (plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
-          auto op = plane->BindPipeline(this, true);
-          if (op) {
-            planes.emplace_back(op);
-          }
+  for (const auto &plane : device->GetPlanes()) {
+    if (plane->IsCrtcSupported(*crtc->Get())) {
+      if (Properties::UseOverlayPlanes() &&
+          plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
+        auto op = plane->BindPipeline(this, true);
+        if (op) {
+          planes.emplace_back(op);
+        }
+      } else if (plane->GetType() == DRM_PLANE_TYPE_CURSOR) {
+        if (cursor) {
+          ALOGW(
+              "Encountered multiple cursor planes for CRTC %d. Ignoring "
+              "plane %d",
+              crtc->Get()->GetId(), plane->GetId());
+        } else {
+          cursor = plane->BindPipeline(this, true);
         }
       }
     }
   }
 
-  return planes;
+  return pair;
 }
 
 DrmDisplayPipeline::~DrmDisplayPipeline() {
diff --git a/drm/DrmDisplayPipeline.h b/drm/DrmDisplayPipeline.h
index cf64a36..0d05288 100644
--- a/drm/DrmDisplayPipeline.h
+++ b/drm/DrmDisplayPipeline.h
@@ -67,12 +67,15 @@
   B *const bindable_;
 };
 
+using UsablePlanes = std::pair<
+    std::vector<std::shared_ptr<BindingOwner<DrmPlane>>>,
+    std::shared_ptr<BindingOwner<DrmPlane>>>;
+
 struct DrmDisplayPipeline {
   static auto CreatePipeline(DrmConnector &connector)
       -> std::unique_ptr<DrmDisplayPipeline>;
 
-  auto GetUsablePlanes()
-      -> std::vector<std::shared_ptr<BindingOwner<DrmPlane>>>;
+  auto GetUsablePlanes() -> UsablePlanes;
 
   ~DrmDisplayPipeline();
 
diff --git a/drm/DrmPlane.cpp b/drm/DrmPlane.cpp
index 68887dd..0fe060d 100644
--- a/drm/DrmPlane.cpp
+++ b/drm/DrmPlane.cpp
@@ -132,6 +132,12 @@
     }
   }
 
+  if (type_ == DRM_PLANE_TYPE_CURSOR &&
+      GetPlaneProperty("SIZE_HINTS", size_hints_property_,
+                       Presence::kOptional)) {
+    size_hints_property_.GetBlobData(size_hints_);
+  }
+
   return 0;
 }
 
@@ -208,6 +214,13 @@
     return false;
   }
 
+  if (type_ == DRM_PLANE_TYPE_CURSOR &&
+      !IsBufferValidForCursorPlane(layer->bi.value())) {
+    ALOGV("Buffer size %dx%d is not supported by cursor plane %d",
+          layer->bi->width, layer->bi->height, GetId());
+    return false;
+  }
+
   return true;
 }
 
@@ -342,4 +355,21 @@
   return true;
 }
 
+bool DrmPlane::HasCursorSizeConstraints() const {
+  return drm_->GetCapCursorSize().has_value() || !size_hints_.empty();
+}
+
+bool DrmPlane::IsBufferValidForCursorPlane(const BufferInfo &bi) const {
+  if (std::find_if(size_hints_.begin(), size_hints_.end(),
+                   [&](const auto &hint) -> bool {
+                     return bi.width == hint.width && bi.height == hint.height;
+                   }) != size_hints_.end()) {
+    return true;
+  }
+
+  const auto &cap_size = drm_->GetCapCursorSize();
+  return cap_size.has_value() && bi.width == cap_size->first &&
+         bi.height == cap_size->second;
+}
+
 }  // namespace android
diff --git a/drm/DrmPlane.h b/drm/DrmPlane.h
index 81306d9..2fa6388 100644
--- a/drm/DrmPlane.h
+++ b/drm/DrmPlane.h
@@ -30,6 +30,12 @@
 class DrmDevice;
 struct LayerData;
 
+// NOLINTNEXTLINE(readability-identifier-naming)
+struct drm_plane_size_hint_local {
+  __u16 width;
+  __u16 height;
+};
+
 class DrmPlane : public PipelineBindable<DrmPlane> {
  public:
   DrmPlane(const DrmPlane &) = delete;
@@ -59,6 +65,8 @@
     return plane_->plane_id;
   }
 
+  bool HasCursorSizeConstraints() const;
+
  private:
   DrmPlane(DrmDevice &dev, DrmModePlaneUnique plane)
       : drm_(&dev), plane_(std::move(plane)){};
@@ -70,6 +78,7 @@
   auto Init() -> int;
   auto GetPlaneProperty(const char *prop_name, DrmProperty &property,
                         Presence presence = Presence::kMandatory) -> bool;
+  bool IsBufferValidForCursorPlane(const BufferInfo &bi) const;
 
   uint32_t type_{};
 
@@ -92,10 +101,12 @@
   DrmProperty in_fence_fd_property_;
   DrmProperty color_encoding_property_;
   DrmProperty color_range_property_;
+  DrmProperty size_hints_property_;
 
   std::map<BufferBlendMode, uint64_t> blending_enum_map_;
   std::map<BufferColorSpace, uint64_t> color_encoding_enum_map_;
   std::map<BufferSampleRange, uint64_t> color_range_enum_map_;
   uint64_t transform_enum_mask_ = DRM_MODE_ROTATE_0;
+  std::vector<drm_plane_size_hint_local> size_hints_;
 };
 }  // namespace android
diff --git a/drm/DrmProperty.cpp b/drm/DrmProperty.cpp
index 8dc62f6..24a67bc 100644
--- a/drm/DrmProperty.cpp
+++ b/drm/DrmProperty.cpp
@@ -35,12 +35,14 @@
     : value(e->value), name(e->name) {
 }
 
-DrmProperty::DrmProperty(uint32_t obj_id, drmModePropertyPtr p,
-                         uint64_t value) {
-  Init(obj_id, p, value);
+DrmProperty::DrmProperty(const SharedFd &fd, uint32_t obj_id,
+                         drmModePropertyPtr p, uint64_t value) {
+  Init(fd, obj_id, p, value);
 }
 
-void DrmProperty::Init(uint32_t obj_id, drmModePropertyPtr p, uint64_t value) {
+void DrmProperty::Init(const SharedFd &fd, uint32_t obj_id,
+                       drmModePropertyPtr p, uint64_t value) {
+  fd_ = fd;
   obj_id_ = obj_id;
   id_ = p->prop_id;
   flags_ = p->flags;
diff --git a/drm/DrmProperty.h b/drm/DrmProperty.h
index 99bddb1..c0f6161 100644
--- a/drm/DrmProperty.h
+++ b/drm/DrmProperty.h
@@ -18,22 +18,29 @@
 
 #include <xf86drmMode.h>
 
+#include <cinttypes>
 #include <cstdint>
 #include <map>
 #include <optional>
 #include <string>
 #include <vector>
 
+#include "drm/DrmUnique.h"
+#include "utils/fd.h"
+#include "utils/log.h"
+
 namespace android {
 
 class DrmProperty {
  public:
   DrmProperty() = default;
-  DrmProperty(uint32_t obj_id, drmModePropertyPtr p, uint64_t value);
+  DrmProperty(const SharedFd &fd, uint32_t obj_id, drmModePropertyPtr p,
+              uint64_t value);
   DrmProperty(const DrmProperty &) = delete;
   DrmProperty &operator=(const DrmProperty &) = delete;
 
-  auto Init(uint32_t obj_id, drmModePropertyPtr p, uint64_t value) -> void;
+  auto Init(const SharedFd &fd, uint32_t obj_id, drmModePropertyPtr p,
+            uint64_t value) -> void;
   std::tuple<uint64_t, int> GetEnumValueWithName(const std::string &name) const;
 
   auto GetId() const {
@@ -80,6 +87,12 @@
 
   auto GetEnumNameFromValue(uint64_t value) const -> std::optional<std::string>;
 
+  bool IsBlob() const {
+    return id_ != 0 && (flags_ & DRM_MODE_PROP_BLOB) != 0;
+  }
+  template <typename T>
+  bool GetBlobData(std::vector<T> &data_out) const;
+
  private:
   class DrmPropertyEnum {
    public:
@@ -90,6 +103,7 @@
     std::string name;
   };
 
+  SharedFd fd_ = nullptr;
   uint32_t obj_id_ = 0;
   uint32_t id_ = 0;
 
@@ -130,4 +144,43 @@
   return false;
 }
 
+template <typename T>
+bool DrmProperty::GetBlobData(std::vector<T> &data_out) const {
+  auto value = GetValue();
+  if (!fd_) {
+    ALOGE("Could not read blob data from property %s: No fd", name_.c_str());
+    return false;
+  }
+  if (!IsBlob()) {
+    ALOGE("Property %s is not blob type", name_.c_str());
+    return false;
+  }
+  if (!value.has_value()) {
+    ALOGE("Could not read blob data from property %s: No blob id",
+          name_.c_str());
+    return false;
+  }
+
+  auto blob = MakeDrmModePropertyBlobUnique(*fd_, value.value());
+  if (blob == nullptr) {
+    ALOGE("Failed to read blob with id=%" PRIu64 " from property %s",
+          value.value(), name_.c_str());
+    return false;
+  }
+
+  if (blob->length % sizeof(T) != 0) {
+    ALOGE(
+        "Property %s blob size of %u bytes is not divisible by type argument "
+        "size of %zu bytes",
+        name_.c_str(), blob->length, sizeof(T));
+    return false;
+  }
+
+  auto cast_data = static_cast<T *>(blob->data);
+  size_t cast_data_length = blob->length / sizeof(T);
+  data_out.assign(cast_data, cast_data + cast_data_length);
+
+  return true;
+}
+
 }  // namespace android
diff --git a/drm/VSyncWorker.cpp b/drm/VSyncWorker.cpp
index defbe42..7ec1c28 100644
--- a/drm/VSyncWorker.cpp
+++ b/drm/VSyncWorker.cpp
@@ -55,7 +55,6 @@
   {
     const std::lock_guard<std::mutex> lock(mutex_);
     enabled_ = ShouldEnable();
-    last_timestamp_ = -1;
   }
 
   cv_.notify_all();
@@ -64,6 +63,7 @@
 void VSyncWorker::SetVsyncPeriodNs(uint32_t vsync_period_ns) {
   const std::lock_guard<std::mutex> lock(mutex_);
   vsync_period_ns_ = vsync_period_ns;
+  last_timestamp_ = std::nullopt;
 }
 
 void VSyncWorker::SetVsyncTimestampTracking(bool enabled) {
@@ -71,9 +71,9 @@
     const std::lock_guard<std::mutex> lock(mutex_);
     enable_vsync_timestamps_ = enabled;
     if (enabled) {
-      // Reset the last timestamp so the caller knows if a vsync timestamp is
-      // fresh or not.
-      last_vsync_timestamp_ = 0;
+      // Reset the freshness flag to ensure that only a fresh timestamp is
+      // returned from GetLastVsyncTimestamp.
+      last_timestamp_is_fresh_ = false;
     }
   }
   UpdateVSyncControl();
@@ -81,7 +81,7 @@
 
 uint32_t VSyncWorker::GetLastVsyncTimestamp() {
   const std::lock_guard<std::mutex> lock(mutex_);
-  return last_vsync_timestamp_;
+  return last_timestamp_is_fresh_ ? last_timestamp_.value_or(0) : 0;
 }
 
 void VSyncWorker::SetTimestampCallback(
@@ -121,19 +121,23 @@
  *  timestamp.
  */
 int64_t VSyncWorker::GetPhasedVSync(int64_t frame_ns, int64_t current) const {
-  if (last_timestamp_ < 0)
+  if (!last_timestamp_.has_value())
     return current + frame_ns;
 
-  return (frame_ns * ((current - last_timestamp_) / frame_ns + 1)) +
-         last_timestamp_;
+  return (frame_ns * ((current - *last_timestamp_) / frame_ns + 1)) +
+         *last_timestamp_;
 }
 
 static const int64_t kOneSecondNs = 1LL * 1000 * 1000 * 1000;
 
 int VSyncWorker::SyntheticWaitVBlank(int64_t *timestamp) {
-  auto time_now = ResourceManager::GetTimeMonotonicNs();
+  int64_t phased_timestamp = 0;
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+    int64_t time_now = ResourceManager::GetTimeMonotonicNs();
+    phased_timestamp = GetPhasedVSync(vsync_period_ns_, time_now);
+  }
 
-  auto phased_timestamp = GetPhasedVSync(vsync_period_ns_, time_now);
   struct timespec vsync {};
   vsync.tv_sec = int(phased_timestamp / kOneSecondNs);
   vsync.tv_nsec = int(phased_timestamp - (vsync.tv_sec * kOneSecondNs));
@@ -155,6 +159,9 @@
   for (;;) {
     {
       std::unique_lock<std::mutex> lock(mutex_);
+      // Thread safety analysis doesn't understand std::unique_lock.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
       if (thread_exit_)
         break;
 
@@ -163,6 +170,7 @@
 
       if (!enabled_)
         continue;
+#pragma clang diagnostic pop
     }
 
     ret = -EAGAIN;
@@ -191,21 +199,23 @@
     }
 
     std::optional<VsyncTimestampCallback> vsync_callback;
+    int64_t vsync_period_ns = 0;
 
     {
       const std::lock_guard<std::mutex> lock(mutex_);
       if (!enabled_)
         continue;
       if (enable_vsync_timestamps_) {
-        last_vsync_timestamp_ = timestamp;
+        last_timestamp_is_fresh_ = true;
       }
       vsync_callback = callback_;
+      vsync_period_ns = vsync_period_ns_;
+      last_timestamp_ = timestamp;
     }
 
     if (vsync_callback) {
-      vsync_callback.value()(timestamp, vsync_period_ns_);
+      vsync_callback.value()(timestamp, vsync_period_ns);
     }
-    last_timestamp_ = timestamp;
   }
 
   ALOGI("VSyncWorker thread exit");
diff --git a/drm/VSyncWorker.h b/drm/VSyncWorker.h
index c76dd14..8e410f4 100644
--- a/drm/VSyncWorker.h
+++ b/drm/VSyncWorker.h
@@ -23,6 +23,7 @@
 #include <thread>
 
 #include "DrmDevice.h"
+#include "utils/thread_annotations.h"
 
 namespace android {
 
@@ -36,7 +37,8 @@
   auto static CreateInstance(std::shared_ptr<DrmDisplayPipeline> &pipe)
       -> std::unique_ptr<VSyncWorker>;
 
-  // Set the expected vsync period.
+  // Set the expected vsync period. Resets internal timestamp tracking until the
+  // next vsync event is tracked.
   void SetVsyncPeriodNs(uint32_t vsync_period_ns);
 
   // Set or clear a callback to be fired on vsync.
@@ -55,27 +57,26 @@
 
   void ThreadFn();
 
-  int64_t GetPhasedVSync(int64_t frame_ns, int64_t current) const;
+  int64_t GetPhasedVSync(int64_t frame_ns, int64_t current) const
+      REQUIRES(mutex_);
   int SyntheticWaitVBlank(int64_t *timestamp);
 
-  // Must hold the lock before calling these.
   void UpdateVSyncControl();
-  bool ShouldEnable() const;
+  bool ShouldEnable() const REQUIRES(mutex_);
 
   SharedFd drm_fd_;
   uint32_t high_crtc_ = 0;
 
-  bool enabled_ = false;
-  bool thread_exit_ = false;
-  int64_t last_timestamp_ = -1;
+  bool enabled_ GUARDED_BY(mutex_) = false;
+  bool thread_exit_ GUARDED_BY(mutex_) = false;
+  std::optional<int64_t> last_timestamp_ GUARDED_BY(mutex_);
 
   // Default to 60Hz refresh rate
   static constexpr uint32_t kDefaultVSPeriodNs = 16666666;
-  // Needs to be threadsafe.
-  uint32_t vsync_period_ns_ = kDefaultVSPeriodNs;
-  bool enable_vsync_timestamps_ = false;
-  uint32_t last_vsync_timestamp_ = 0;
-  std::optional<VsyncTimestampCallback> callback_;
+  uint32_t vsync_period_ns_ GUARDED_BY(mutex_) = kDefaultVSPeriodNs;
+  bool enable_vsync_timestamps_ GUARDED_BY(mutex_) = false;
+  bool last_timestamp_is_fresh_ GUARDED_BY(mutex_) = false;
+  std::optional<VsyncTimestampCallback> callback_ GUARDED_BY(mutex_);
 
   std::condition_variable cv_;
   std::thread vswt_;
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index 13287ef..15a4c6e 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -216,7 +216,7 @@
     case 2:  // SDR
       [[fallthrough]];
     default:
-      hdr_metadata_.reset();
+      hdr_metadata_ = std::make_shared<hdr_output_metadata>();
       min_bpc_ = 6;
       colorspace_ = Colorspace::kDefault;
   }
@@ -355,9 +355,22 @@
   return changed_layers;
 }
 
+auto HwcDisplay::GetDisplayBoundsMm() -> std::pair<int32_t, int32_t> {
+
+  const auto bounds = GetEdid()->GetBoundsMm();
+  if (bounds.first > 0 || bounds.second > 0) {
+    return bounds;
+  }
+
+  ALOGE("Failed to get display bounds for d=%d\n", int(handle_));
+  // mm_width and mm_height are unreliable. so only provide mm_width to avoid
+  // wrong dpi computations or other use of the values.
+  return {configs_.mm_width, -1};
+}
+
 auto HwcDisplay::AcceptValidatedComposition() -> void {
-  for (std::pair<const hwc2_layer_t, HwcLayer> &l : layers_) {
-    l.second.AcceptTypeChange();
+  for (auto &[_, layer] : layers_) {
+    layer.AcceptTypeChange();
   }
 }
 
@@ -506,20 +519,18 @@
   return SetActiveConfig(configs_.preferred_config_id);
 }
 
-HWC2::Error HwcDisplay::CreateLayer(hwc2_layer_t *layer) {
-  layers_.emplace(static_cast<hwc2_layer_t>(layer_idx_), HwcLayer(this));
-  *layer = static_cast<hwc2_layer_t>(layer_idx_);
-  ++layer_idx_;
-  return HWC2::Error::None;
+auto HwcDisplay::CreateLayer(ILayerId new_layer_id) -> bool {
+  if (layers_.count(new_layer_id) > 0)
+    return false;
+
+  layers_.emplace(new_layer_id, HwcLayer(this));
+
+  return true;
 }
 
-HWC2::Error HwcDisplay::DestroyLayer(hwc2_layer_t layer) {
-  if (!get_layer(layer)) {
-    return HWC2::Error::BadLayer;
-  }
-
-  layers_.erase(layer);
-  return HWC2::Error::None;
+auto HwcDisplay::DestroyLayer(ILayerId layer_id) -> bool {
+  auto count = layers_.erase(layer_id);
+  return count != 0;
 }
 
 HWC2::Error HwcDisplay::GetActiveConfig(hwc2_config_t *config) const {
@@ -588,15 +599,25 @@
       *value = hwc_config.mode.GetVSyncPeriodNs();
       break;
     case HWC2::Attribute::DpiY:
-      // ideally this should be vdisplay/mm_heigth, however mm_height
-      // comes from edid parsing and is highly unreliable. Viewing the
-      // rarity of anisotropic displays, falling back to a single value
-      // for dpi yield more correct output.
+      *value = GetEdid()->GetDpiY();
+      if (*value < 0) {
+        // default to raw mode DpiX for both x and y when no good value
+        // can be provided from edid.
+        *value = mm_width ? int(hwc_config.mode.GetRawMode().hdisplay *
+                                kUmPerInch / mm_width)
+                          : -1;
+      }
+      break;
     case HWC2::Attribute::DpiX:
       // Dots per 1000 inches
-      *value = mm_width ? int(hwc_config.mode.GetRawMode().hdisplay *
-                              kUmPerInch / mm_width)
-                        : -1;
+      *value = GetEdid()->GetDpiX();
+      if (*value < 0) {
+        // default to raw mode DpiX for both x and y when no good value
+        // can be provided from edid.
+        *value = mm_width ? int(hwc_config.mode.GetRawMode().hdisplay *
+                                kUmPerInch / mm_width)
+                          : -1;
+      }
       break;
 #if __ANDROID_API__ > 29
     case HWC2::Attribute::ConfigGroup:
@@ -760,15 +781,15 @@
   bool use_client_layer = false;
   uint32_t client_z_order = UINT32_MAX;
   std::map<uint32_t, HwcLayer *> z_map;
-  for (std::pair<const hwc2_layer_t, HwcLayer> &l : layers_) {
-    switch (l.second.GetValidatedType()) {
+  for (auto &[_, layer] : layers_) {
+    switch (layer.GetValidatedType()) {
       case HWC2::Composition::Device:
-        z_map.emplace(l.second.GetZOrder(), &l.second);
+        z_map.emplace(layer.GetZOrder(), &layer);
         break;
       case HWC2::Composition::Client:
         // Place it at the z_order of the lowest client layer
         use_client_layer = true;
-        client_z_order = std::min(client_z_order, l.second.GetZOrder());
+        client_z_order = std::min(client_z_order, layer.GetZOrder());
         break;
       default:
         continue;
@@ -842,7 +863,6 @@
   }
 
   if (new_vsync_period_ns) {
-    vsync_worker_->SetVsyncPeriodNs(new_vsync_period_ns.value());
     staged_mode_config_id_.reset();
 
     vsync_worker_->SetVsyncTimestampTracking(false);
@@ -852,6 +872,7 @@
                                                       last_vsync_ts +
                                                           prev_vperiod_ns);
     }
+    vsync_worker_->SetVsyncPeriodNs(new_vsync_period_ns.value());
   }
 
   return HWC2::Error::None;
@@ -1287,4 +1308,12 @@
   backend_ = std::move(backend);
 }
 
+bool HwcDisplay::NeedsClientLayerUpdate() const {
+  return std::any_of(layers_.begin(), layers_.end(), [](const auto &pair) {
+    const auto &layer = pair.second;
+    return layer.GetSfType() == HWC2::Composition::Client ||
+           layer.GetValidatedType() == HWC2::Composition::Client;
+  });
+}
+
 }  // namespace android
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index 94f3f14..b8c52fe 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -97,10 +97,12 @@
   // Get the HwcDisplayConfig, or nullptor if none.
   auto GetConfig(hwc2_config_t config_id) const -> const HwcDisplayConfig *;
 
+  auto GetDisplayBoundsMm() -> std::pair<int32_t, int32_t>;
+  
   // To be called after SetDisplayProperties. Returns an empty vector if the
   // requested layers have been validated, otherwise the vector describes
   // the requested composition type changes.
-  using ChangedLayer = std::pair<hwc2_layer_t, HWC2::Composition>;
+  using ChangedLayer = std::pair<ILayerId, HWC2::Composition>;
   auto ValidateStagedComposition() -> std::vector<ChangedLayer>;
 
   // Mark previously validated properties as ready to present.
@@ -109,7 +111,7 @@
   // Present previously staged properties, and return fences to indicate when
   // the new content has been presented, and when the previous buffers have
   // been released.
-  using ReleaseFence = std::pair<hwc2_layer_t, SharedFd>;
+  using ReleaseFence = std::pair<ILayerId, SharedFd>;
   auto PresentStagedComposition(SharedFd &out_present_fence,
                                 std::vector<ReleaseFence> &out_release_fences)
       -> bool;
@@ -122,9 +124,10 @@
     frontend_private_data_ = std::move(data);
   }
 
+  auto CreateLayer(ILayerId new_layer_id) -> bool;
+  auto DestroyLayer(ILayerId layer_id) -> bool;
+
   // HWC2 Hooks - these should not be used outside of the hwc2 device.
-  HWC2::Error CreateLayer(hwc2_layer_t *layer);
-  HWC2::Error DestroyLayer(hwc2_layer_t layer);
   HWC2::Error GetActiveConfig(hwc2_config_t *config) const;
   HWC2::Error GetColorModes(uint32_t *num_modes, int32_t *modes);
   HWC2::Error GetDisplayAttribute(hwc2_config_t config, int32_t attribute,
@@ -167,7 +170,7 @@
   HWC2::Error SetColorTransform(const float *matrix, int32_t hint);
   HWC2::Error SetPowerMode(int32_t mode);
   HWC2::Error SetVsyncEnabled(int32_t enabled);
-  HwcLayer *get_layer(hwc2_layer_t layer) {
+  HwcLayer *get_layer(ILayerId layer) {
     auto it = layers_.find(layer);
     if (it == layers_.end())
       return nullptr;
@@ -200,7 +203,7 @@
     return hwc_;
   }
 
-  std::map<hwc2_layer_t, HwcLayer> &layers() {
+  auto layers() -> std::map<ILayerId, HwcLayer> & {
     return layers_;
   }
 
@@ -245,6 +248,8 @@
 
   auto getDisplayPhysicalOrientation() -> std::optional<PanelOrientation>;
 
+  bool NeedsClientLayerUpdate() const;
+
  private:
   AtomicCommitArgs CreateModesetCommit(
       const HwcDisplayConfig *config,
@@ -268,9 +273,7 @@
   const hwc2_display_t handle_;
   HWC2::DisplayType type_;
 
-  uint32_t layer_idx_{};
-
-  std::map<hwc2_layer_t, HwcLayer> layers_;
+  std::map<ILayerId, HwcLayer> layers_;
   HwcLayer client_layer_;
   std::unique_ptr<HwcLayer> writeback_layer_;
   uint16_t virtual_disp_width_{};
diff --git a/hwc2_device/HwcLayer.cpp b/hwc2_device/HwcLayer.cpp
index d0ff216..400ac9b 100644
--- a/hwc2_device/HwcLayer.cpp
+++ b/hwc2_device/HwcLayer.cpp
@@ -101,6 +101,10 @@
     return;
   }
 
+  if (slots_.count(*active_slot_id_) == 0) {
+    return;
+  }
+
   layer_data_.bi = slots_[*active_slot_id_].bi;
   layer_data_.fb = slots_[*active_slot_id_].fb;
 
diff --git a/hwc2_device/hwc2_device.cpp b/hwc2_device/hwc2_device.cpp
index 72d0aa9..c1c5157 100644
--- a/hwc2_device/hwc2_device.cpp
+++ b/hwc2_device/hwc2_device.cpp
@@ -53,6 +53,8 @@
  public:
   std::vector<HwcDisplay::ReleaseFence> release_fences;
   std::vector<HwcDisplay::ChangedLayer> changed_layers;
+
+  int64_t next_layer_id = 1;
 };
 
 static auto GetHwc2DeviceDisplay(HwcDisplay &display)
@@ -267,6 +269,37 @@
 }
 
 /* Display functions */
+static int32_t CreateLayer(hwc2_device_t *device, hwc2_display_t display,
+                           hwc2_layer_t *out_layer) {
+  ALOGV("CreateLayer");
+  LOCK_COMPOSER(device);
+  GET_DISPLAY(display);
+
+  auto hwc2display = GetHwc2DeviceDisplay(*idisplay);
+
+  if (!idisplay->CreateLayer(hwc2display->next_layer_id)) {
+    return static_cast<int32_t>(HWC2::Error::BadDisplay);
+  }
+
+  *out_layer = (hwc2_layer_t)hwc2display->next_layer_id;
+  hwc2display->next_layer_id++;
+
+  return 0;
+}
+
+static int32_t DestroyLayer(hwc2_device_t *device, hwc2_display_t display,
+                            hwc2_layer_t layer) {
+  ALOGV("DestroyLayer");
+  LOCK_COMPOSER(device);
+  GET_DISPLAY(display);
+
+  if (!idisplay->DestroyLayer((ILayerId)layer)) {
+    return static_cast<int32_t>(HWC2::Error::BadLayer);
+  }
+
+  return 0;
+}
+
 static int32_t GetDisplayRequests(hwc2_device_t * /*device*/,
                                   hwc2_display_t /*display*/,
                                   int32_t * /* out_display_requests */,
@@ -763,13 +796,9 @@
     case HWC2::FunctionDescriptor::AcceptDisplayChanges:
       return (hwc2_function_pointer_t)AcceptDisplayChanges;
     case HWC2::FunctionDescriptor::CreateLayer:
-      return ToHook<HWC2_PFN_CREATE_LAYER>(
-          DisplayHook<decltype(&HwcDisplay::CreateLayer),
-                      &HwcDisplay::CreateLayer, hwc2_layer_t *>);
+      return (hwc2_function_pointer_t)CreateLayer;
     case HWC2::FunctionDescriptor::DestroyLayer:
-      return ToHook<HWC2_PFN_DESTROY_LAYER>(
-          DisplayHook<decltype(&HwcDisplay::DestroyLayer),
-                      &HwcDisplay::DestroyLayer, hwc2_layer_t>);
+      return (hwc2_function_pointer_t)DestroyLayer;
     case HWC2::FunctionDescriptor::GetActiveConfig:
       return ToHook<HWC2_PFN_GET_ACTIVE_CONFIG>(
           DisplayHook<decltype(&HwcDisplay::GetActiveConfig),
diff --git a/hwc3/Composer.cpp b/hwc3/Composer.cpp
index 28d096c..de875db 100644
--- a/hwc3/Composer.cpp
+++ b/hwc3/Composer.cpp
@@ -79,6 +79,10 @@
     caps->emplace_back(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
   }
 
+#if __ANDROID_API__ >= 35
+  caps->emplace_back(Capability::LAYER_LIFECYCLE_BATCH_COMMAND);
+#endif
+
   return ndk::ScopedAStatus::ok();
 }
 
diff --git a/hwc3/ComposerClient.cpp b/hwc3/ComposerClient.cpp
index d6efe07..c7ec522 100644
--- a/hwc3/ComposerClient.cpp
+++ b/hwc3/ComposerClient.cpp
@@ -273,7 +273,7 @@
 #endif
 
 DisplayConfiguration HwcDisplayConfigToAidlConfiguration(
-    const HwcDisplayConfigs& configs, const HwcDisplayConfig& config) {
+    int32_t width, int32_t height, const HwcDisplayConfig& config) {
   DisplayConfiguration aidl_configuration =
       {.configId = static_cast<int32_t>(config.id),
        .width = config.mode.GetRawMode().hdisplay,
@@ -282,15 +282,14 @@
        .vsyncPeriod = config.mode.GetVSyncPeriodNs(),
        .hdrOutputType = static_cast<OutputType>(config.output_type)};
 
-  if (configs.mm_width != 0) {
-    // ideally this should be vdisplay/mm_heigth, however mm_height
-    // comes from edid parsing and is highly unreliable. Viewing the
-    // rarity of anisotropic displays, falling back to a single value
-    // for dpi yield more correct output.
+  if (width > 0) {
     static const float kMmPerInch = 25.4;
-    float dpi = float(config.mode.GetRawMode().hdisplay) * kMmPerInch /
-                float(configs.mm_width);
-    aidl_configuration.dpi = {.x = dpi, .y = dpi};
+    float dpi_x = float(config.mode.GetRawMode().hdisplay) * kMmPerInch /
+                float(width);
+    float dpi_y = height <= 0 ? dpi_x :
+                  float(config.mode.GetRawMode().vdisplay) * kMmPerInch /
+                    float(height);
+    aidl_configuration.dpi = {.x = dpi_x, .y = dpi_y};
   }
   // TODO: Populate vrrConfig.
   return aidl_configuration;
@@ -361,9 +360,14 @@
       -> std::shared_ptr<Hwc3BufferHandle> {
     auto hwc3 = std::shared_ptr<Hwc3BufferHandle>(new Hwc3BufferHandle());
 
-    ::android::GraphicBufferMapper::get()
+    auto result = ::android::GraphicBufferMapper::get()
         .importBufferNoValidate(handle, &hwc3->imported_handle_);
 
+    if (result != ::android::NO_ERROR) {
+      ALOGE("Failed to import buffer handle: %d", result);
+      return nullptr;
+    }
+
     return hwc3;
   }
 
@@ -407,17 +411,15 @@
 
     auto bi = ::android::BufferInfoGetter::GetInstance()->GetBoInfo(
         hwc3->GetHandle());
-    if (!bi) {
-      return std::nullopt;
+    if (bi) {
+      bi->fds_shared = hwc3;
+
+      lp.slot_buffer = {
+          .slot_id = slot_id,
+          .bi = bi,
+      };
     }
 
-    bi->fds_shared = hwc3;
-
-    lp.slot_buffer = {
-        .slot_id = slot_id,
-        .bi = bi,
-    };
-
     lp.active_slot = {
         .slot_id = slot_id,
         .fence = std::move(fence_fd),
@@ -493,13 +495,16 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  hwc2_layer_t hwc2_layer_id = 0;
-  auto err = Hwc2toHwc3Error(display->CreateLayer(&hwc2_layer_id));
-  if (err != hwc3::Error::kNone) {
-    return ToBinderStatus(err);
+  auto hwc3display = DrmHwcThree::GetHwc3Display(*display);
+
+  if (!display->CreateLayer(hwc3display->next_layer_id)) {
+    return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  *layer_id = Hwc2LayerToHwc3(hwc2_layer_id);
+  *layer_id = hwc3display->next_layer_id;
+
+  hwc3display->next_layer_id++;
+
   return ndk::ScopedAStatus::ok();
 }
 
@@ -533,12 +538,11 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  auto err = Hwc2toHwc3Error(display->DestroyLayer(Hwc3LayerToHwc2(layer_id)));
-  if (err != hwc3::Error::kNone) {
-    return ToBinderStatus(err);
+  if (!display->DestroyLayer(layer_id)) {
+    return ToBinderStatus(hwc3::Error::kBadLayer);
   }
 
-  return ToBinderStatus(err);
+  return ToBinderStatus(hwc3::Error::kNone);
 }
 
 ndk::ScopedAStatus ComposerClient::destroyVirtualDisplay(int64_t display_id) {
@@ -560,6 +564,24 @@
     return;
   }
 
+#if __ANDROID_API__ >= 35
+  auto batch_command = command.layerLifecycleBatchCommandType;
+  if (batch_command == LayerLifecycleBatchCommandType::CREATE) {
+    if (!display->CreateLayer(command.layer)) {
+      cmd_result_writer_->AddError(hwc3::Error::kBadLayer);
+      return;
+    }
+  }
+
+  if (batch_command == LayerLifecycleBatchCommandType::DESTROY) {
+    if (!display->DestroyLayer(command.layer)) {
+      cmd_result_writer_->AddError(hwc3::Error::kBadLayer);
+    }
+
+    return;
+  }
+#endif
+
   auto* layer = display->get_layer(command.layer);
   if (layer == nullptr) {
     cmd_result_writer_->AddError(hwc3::Error::kBadLayer);
@@ -695,13 +717,14 @@
     display->SetColorTransformMatrix(ctm.value());
   }
 
+  bool shall_present_now = false;
+
+  DisplayChanges changes{};
   if (command.validateDisplay || command.presentOrValidateDisplay) {
     std::vector<HwcDisplay::ChangedLayer>
         changed_layers = display->ValidateStagedComposition();
-    DisplayChanges changes{};
     for (auto [layer_id, composition_type] : changed_layers) {
-      changes.AddLayerCompositionChange(command.display,
-                                        Hwc2LayerToHwc3(layer_id),
+      changes.AddLayerCompositionChange(command.display, layer_id,
                                         static_cast<Composition>(
                                             composition_type));
     }
@@ -710,21 +733,23 @@
     hwc3_display->must_validate = false;
 
     // TODO: DisplayRequests are not implemented.
+  }
 
-    /* TODO: Add check if it's possible to skip display validation for
-     * presentOrValidateDisplay */
-    if (command.presentOrValidateDisplay) {
-      cmd_result_writer_
-          ->AddPresentOrValidateResult(display_id,
-                                       PresentOrValidate::Result::Validated);
+  if (command.presentOrValidateDisplay) {
+    auto result = PresentOrValidate::Result::Validated;
+    if (!display->NeedsClientLayerUpdate() && !changes.HasAnyChanges()) {
+      ALOGV("Skipping SF roundtrip for display %" PRId64, display_id);
+      result = PresentOrValidate::Result::Presented;
+      shall_present_now = true;
     }
+    cmd_result_writer_->AddPresentOrValidateResult(display_id, result);
   }
 
   if (command.acceptDisplayChanges) {
     display->AcceptValidatedComposition();
   }
 
-  if (command.presentDisplay) {
+  if (command.presentDisplay || shall_present_now) {
     auto hwc3_display = DrmHwcThree::GetHwc3Display(*display);
     if (hwc3_display->must_validate) {
       cmd_result_writer_->AddError(hwc3::Error::kNotValidated);
@@ -745,8 +770,7 @@
 
     std::unordered_map<int64_t, unique_fd> hal_release_fences;
     for (const auto& [layer_id, release_fence] : release_fences) {
-      hal_release_fences[Hwc2LayerToHwc3(layer_id)] =  //
-          unique_fd(::android::DupFd(release_fence));
+      hal_release_fences[layer_id] = unique_fd(::android::DupFd(release_fence));
     }
     cmd_result_writer_->AddReleaseFence(display_id, hal_release_fences);
   }
@@ -844,9 +868,11 @@
     return ToBinderStatus(hwc3::Error::kBadConfig);
   }
 
-  DisplayConfiguration
-      aidl_configuration = HwcDisplayConfigToAidlConfiguration(configs,
-                                                               config->second);
+  const auto bounds = display->GetDisplayBoundsMm();
+  DisplayConfiguration aidl_configuration =
+      HwcDisplayConfigToAidlConfiguration(/*width =*/ bounds.first,
+                                          /*height =*/bounds.second,
+                                          config->second);
   // Legacy API for querying DPI uses units of dots per 1000 inches.
   static const int kLegacyDpiUnit = 1000;
   switch (attribute) {
@@ -1427,9 +1453,12 @@
   }
 
   const HwcDisplayConfigs& configs = display->GetDisplayConfigs();
+  const auto bounds = display->GetDisplayBoundsMm();
   for (const auto& [id, config] : configs.hwc_configs) {
     configurations->push_back(
-        HwcDisplayConfigToAidlConfiguration(configs, config));
+        HwcDisplayConfigToAidlConfiguration(/*width =*/ bounds.first, 
+                                            /*height =*/ bounds.second,
+                                            config));
   }
   return ndk::ScopedAStatus::ok();
 }
diff --git a/hwc3/DrmHwcThree.h b/hwc3/DrmHwcThree.h
index 44168aa..10470ea 100644
--- a/hwc3/DrmHwcThree.h
+++ b/hwc3/DrmHwcThree.h
@@ -26,6 +26,8 @@
 class Hwc3Display : public ::android::FrontendDisplayBase {
  public:
   bool must_validate = false;
+
+  int64_t next_layer_id = 1;
 };
 
 class DrmHwcThree : public ::android::DrmHwc {
diff --git a/hwc3/Utils.h b/hwc3/Utils.h
index aa956f6..89767c8 100644
--- a/hwc3/Utils.h
+++ b/hwc3/Utils.h
@@ -58,12 +58,6 @@
   return ToBinderStatus(Hwc2toHwc3Error(error));
 }
 
-// ID conversion. HWC2 uses typedef'd unsigned integer types while HWC3 uses
-// signed integer types. static_cast in between these.
-inline int64_t Hwc2LayerToHwc3(hwc2_layer_t layer) {
-  return static_cast<int64_t>(layer);
-}
-
 inline int64_t Hwc2DisplayToHwc3(hwc2_display_t display) {
   return static_cast<int64_t>(display);
 }
@@ -72,10 +66,6 @@
   return static_cast<int32_t>(config_id);
 }
 
-inline hwc2_layer_t Hwc3LayerToHwc2(int64_t layer) {
-  return static_cast<hwc2_layer_t>(layer);
-}
-
 inline hwc2_display_t Hwc3DisplayToHwc2(int64_t display) {
   return static_cast<hwc2_display_t>(display);
 }
diff --git a/utils/EdidWrapper.h b/utils/EdidWrapper.h
index 30124d7..651c284 100644
--- a/utils/EdidWrapper.h
+++ b/utils/EdidWrapper.h
@@ -18,6 +18,7 @@
 
 #if HAS_LIBDISPLAY_INFO
 extern "C" {
+#include <libdisplay-info/edid.h>
 #include <libdisplay-info/info.h>
 }
 #endif
@@ -40,14 +41,24 @@
     types.clear();
   };
   virtual void GetHdrCapabilities(std::vector<ui::Hdr> &types,
-                                  const float * /*max_luminance*/,
-                                  const float * /*max_average_luminance*/,
-                                  const float * /*min_luminance*/) {
+                                  float * /*max_luminance*/,
+                                  float * /*max_average_luminance*/,
+                                  float * /*min_luminance*/) {
     GetSupportedHdrTypes(types);
   };
   virtual void GetColorModes(std::vector<Colormode> &color_modes) {
     color_modes.clear();
   };
+  virtual int GetDpiX() {
+    return -1;
+  }
+  virtual int GetDpiY() {
+    return -1;
+  }
+
+  virtual auto GetBoundsMm() -> std::pair<int32_t, int32_t> {
+    return {-1, -1};
+  }
 };
 
 #if HAS_LIBDISPLAY_INFO
@@ -64,16 +75,23 @@
   void GetSupportedHdrTypes(std::vector<ui::Hdr> &types) override;
 
   void GetHdrCapabilities(std::vector<ui::Hdr> &types,
-                          const float *max_luminance,
-                          const float *max_average_luminance,
-                          const float *min_luminance) override;
+                          float *max_luminance,
+                          float *max_average_luminance,
+                          float *min_luminance) override;
 
   void GetColorModes(std::vector<Colormode> &color_modes) override;
 
+  auto GetDpiX() -> int override;
+  auto GetDpiY() -> int override;
+
+  auto GetBoundsMm() -> std::pair<int32_t, int32_t> override;
+
  private:
   LibdisplayEdidWrapper(di_info *info) : info_(std::move(info)) {
   }
 
+  std::pair<int32_t, int32_t> GetDpi();
+
   di_info *info_{};
 };
 #endif
diff --git a/utils/LibdisplayEdidWrapper.cpp b/utils/LibdisplayEdidWrapper.cpp
index d2d2a1c..ad737a2 100644
--- a/utils/LibdisplayEdidWrapper.cpp
+++ b/utils/LibdisplayEdidWrapper.cpp
@@ -53,15 +53,15 @@
 }
 
 void LibdisplayEdidWrapper::GetHdrCapabilities(
-    std::vector<ui::Hdr> &types, const float *max_luminance,
-    const float *max_average_luminance, const float *min_luminance) {
+    std::vector<ui::Hdr> &types, float *max_luminance,
+    float *max_average_luminance, float *min_luminance) {
   GetSupportedHdrTypes(types);
 
   const auto *hdr_static_meta = di_info_get_hdr_static_metadata(info_);
-  max_luminance = &hdr_static_meta->desired_content_max_luminance;
-  max_average_luminance = &hdr_static_meta
+  *max_luminance = hdr_static_meta->desired_content_max_luminance;
+  *max_average_luminance = hdr_static_meta
                                ->desired_content_max_frame_avg_luminance;
-  min_luminance = &hdr_static_meta->desired_content_min_luminance;
+  *min_luminance = hdr_static_meta->desired_content_min_luminance;
 }
 
 void LibdisplayEdidWrapper::GetColorModes(std::vector<Colormode> &color_modes) {
@@ -95,5 +95,51 @@
   }
 }
 
+auto LibdisplayEdidWrapper::GetDpiX() -> int {
+  return GetDpi().first;
+}
+
+auto LibdisplayEdidWrapper::GetDpiY() -> int {
+  return GetDpi().second;
+}
+
+auto LibdisplayEdidWrapper::GetBoundsMm() -> std::pair<int32_t, int32_t> {
+  const auto edid = di_info_get_edid(info_);
+  const auto detailed_timing_defs = di_edid_get_detailed_timing_defs(edid);
+  const auto dtd = detailed_timing_defs[0];
+  if (dtd == nullptr || dtd->horiz_image_mm == 0 || dtd->vert_image_mm == 0) {
+    // try to fallback on display size if no dtd.
+    // However since edid screen size are vastly unreliable only provide a valid
+    // width to avoid invalid dpi computation.
+    const auto screen_size = di_edid_get_screen_size(edid);
+    return {screen_size->width_cm * 10, -1};
+  }
+
+  return {dtd->horiz_image_mm, dtd->vert_image_mm};
+}
+
+auto LibdisplayEdidWrapper::GetDpi() -> std::pair<int32_t, int32_t> {
+  static const int32_t kUmPerInch = 25400;
+  const auto edid = di_info_get_edid(info_);
+  const auto detailed_timing_defs = di_edid_get_detailed_timing_defs(edid);
+  const auto dtd = detailed_timing_defs[0];
+  if (dtd == nullptr || dtd->horiz_image_mm == 0 || dtd->vert_image_mm == 0) {
+    // try to fallback on display size if no dtd.
+    const auto screen_size = di_edid_get_screen_size(edid);
+    const auto standard_timings = di_edid_get_standard_timings(edid);
+    if (screen_size->width_cm <= 0 || standard_timings == nullptr) {
+      return {-1, -1};
+    }
+
+    // display size is more unreliable so use only horizontal dpi.
+    int32_t horiz_video = standard_timings[0]->horiz_video;
+    int32_t dpi = horiz_video * kUmPerInch / (screen_size->width_cm * 10);
+    return {dpi, dpi};
+  }
+
+  return {dtd->horiz_video * kUmPerInch / dtd->horiz_image_mm,
+          dtd->vert_video * kUmPerInch / dtd->vert_image_mm};
+}
+
 }  // namespace android
 #endif
diff --git a/utils/thread_annotations.h b/utils/thread_annotations.h
new file mode 100644
index 0000000..f3bd2e0
--- /dev/null
+++ b/utils/thread_annotations.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 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
+
+// Enable thread safety attributes only with clang.
+#if defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x)  // no-op
+#endif
+
+#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
+
+#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
+
+#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
+
+#define ACQUIRED_BEFORE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
+
+#define ACQUIRED_AFTER(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
+
+#define REQUIRES(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
+
+#define REQUIRES_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
+
+#define ACQUIRE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
+
+#define ACQUIRE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
+
+#define RELEASE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
+
+#define RELEASE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
+
+#define RELEASE_GENERIC(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE_SHARED(...) \
+  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
+
+#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
+
+#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
+
+#define ASSERT_SHARED_CAPABILITY(x) \
+  THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
+
+#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
+
+#define NO_THREAD_SAFETY_ANALYSIS \
+  THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
\ No newline at end of file