Merge tag 'android-15.0.0_r32' of https://android.googlesource.com/platform/external/drm_hwcomposer into HEAD

Android 15.0.0 Release 32 (BP1A.250505.005)

Change-Id: I4b7ca4cb134497f392f28410fea6fbb212194ec0

# -----BEGIN PGP SIGNATURE-----
#
# iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCaBqG7QAKCRDorT+BmrEO
# eIM/AJ9hTsTPfAT3SjgH5yf/SIqA9tBUywCfS6Io6h+smBvOxnriH8E6fi7otfc=
# =7dlh
# -----END PGP SIGNATURE-----
# gpg: Signature faite le mar 06 mai 2025 18:02:21 EDT
# gpg:                avec la clef DSA 4340D13570EF945E83810964E8AD3F819AB10E78
# gpg: Impossible de vérifier la signature : Pas de clef publique
diff --git a/.ci/.gitlab-ci-checkcommit.sh b/.ci/.gitlab-ci-checkcommit.sh
index f854999..3f597c3 100755
--- a/.ci/.gitlab-ci-checkcommit.sh
+++ b/.ci/.gitlab-ci-checkcommit.sh
@@ -36,13 +36,13 @@
 }
 
 check_tool_installed bpfmt
-check_tool_installed clang-format-diff-15
+check_tool_installed clang-format-diff-19
 
 git fetch https://gitlab.freedesktop.org/drm-hwcomposer/drm-hwcomposer.git
 
 git log --pretty='%h' FETCH_HEAD..HEAD | while read h; do
 	subject=$(git show -s --pretty='%s' "$h")
-	if [[ $subject != drm_hwcomposer:* ]]; then
+	if [[ $subject != drm_hwcomposer:* ]] && [[ $subject != Revert* ]]; then
 		echoerr "Invalid subject prefix: $subject"
 		exit 1
 	fi
@@ -61,7 +61,7 @@
 		exit 1
 	fi
 
-	git show "$h" -- | clang-format-diff-15 -p 1 -style=file > /tmp/format-fixup.patch
+	git show "$h" -- | clang-format-diff-19 -p 1 -style=file > /tmp/format-fixup.patch
 	if [ -s  /tmp/format-fixup.patch ]; then
 		cat /tmp/format-fixup.patch >&2
 		exit 1
diff --git a/.ci/Dockerfile b/.ci/Dockerfile
index c42c8b6..9dfe3d3 100644
--- a/.ci/Dockerfile
+++ b/.ci/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:23.04
+FROM ubuntu:24.10
 
 ENV DEBIAN_FRONTEND=noninteractive
 
@@ -6,7 +6,7 @@
 
 # Taking into account layer structure, everything should be done within one layer.
 RUN apt-get update && apt-get upgrade -y && \
-    apt-get install -y clang-15 clang-tidy-15 clang-format-15 git libdrm-dev blueprint-tools libgtest-dev clang \
+    apt-get install -y clang-19 clang-tidy-19 clang-format-19 git libdrm-dev blueprint-tools libgtest-dev clang \
     llvm make python3 wget sudo rsync lld pkg-config ninja-build meson \
     python3-mako python3-jinja2 python3-ply python3-yaml
 
@@ -41,10 +41,10 @@
 USER ${RUN_USER}
 
 # Install aospless package (produced by GloDroid/aospext)
-RUN wget -P ${USER_HOME} https://gitlab.freedesktop.org/-/project/5/uploads/d66764aa71f9f1235b92d44a652cd3c3/aospless_drm_hwcomposer_arm64.tar.xz && \
+RUN wget -P ${USER_HOME} https://gitlab.freedesktop.org/-/project/5/uploads/70643bd1c4d419015b9930b7aadc9cfd/aospless_drm_hwcomposer_arm64.tar.xz && \
     cd ${USER_HOME} && \
     sha256sum aospless_drm_hwcomposer_arm64.tar.xz && \
-    (echo 00ff288f184111dd35143c462e82fd5f8f31a1417d5eb9a11e8798695abcc141 aospless_drm_hwcomposer_arm64.tar.xz | sha256sum --check) && \
+    (echo 640e0d0e04761c804db69f880e0c0957699babdbe82c72c507177b860d878569 aospless_drm_hwcomposer_arm64.tar.xz | sha256sum --check) && \
     tar xf aospless_drm_hwcomposer_arm64.tar.xz && ln -s ../drm_hwcomposer/ ${USER_HOME}/aospless/src
 
 # Create project path
diff --git a/.ci/Makefile b/.ci/Makefile
index 325e0b5..051a437 100644
--- a/.ci/Makefile
+++ b/.ci/Makefile
@@ -1,18 +1,18 @@
 
-BASE_DIR ?=../aospless
+BASE_DIR:=../aospless
 
 SYSTEM_INCLUDE_DIRS := /usr/include/libdrm
 
-CLANG := clang++-15
-CLANG_TIDY := clang-tidy-15
+CLANG := clang++-19
+CLANG_TIDY := clang-tidy-19
 OUT_DIR := /tmp/drm_hwcomposer/build
 SRC_DIR := .
 
-CXXFLAGS := -Wall -Wextra -Werror
+CXXFLAGS := -Wall -Wextra -Werror -Wno-missing-designated-field-initializers
 CXXFLAGS += -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
 CXXFLAGS += -fvisibility-inlines-hidden -std=gnu++17 -DHWC2_USE_CPP11 -DHWC2_INCLUDE_STRINGIFICATION -fno-rtti
 
-CXXARGS := $(shell cat $(BASE_DIR)/build_flags/sharedlib.cppflags)
+CXXARGS := $(shell cat $(BASE_DIR)/toolchain_wrapper/sharedlib.cppflags)
 CXXARGS := $(subst [BASE_DIR],$(BASE_DIR),$(CXXARGS))
 # clang-tidy doesn't like -mcpu=xxx flag
 CXXARGS := $(patsubst -mcpu=%,,$(CXXARGS))
@@ -45,6 +45,24 @@
     -hicpp-signed-bitwise                               \
     -misc-const-correctness                             \
     -readability-identifier-length                      \
+    -misc-include-cleaner                               \
+    -performance-enum-size                              \
+    -misc-use-anonymous-namespace                       \
+    -boost-use-ranges                                   \
+    -cppcoreguidelines-avoid-do-while                   \
+    -modernize-min-max-use-initializer-list             \
+    -cppcoreguidelines-owning-memory                    \
+    -readability-redundant-member-init                  \
+    -cppcoreguidelines-avoid-const-or-ref-data-members  \
+    -cert-err33-c                                       \
+    -readability-math-missing-parentheses               \
+    -readability-avoid-unconditional-preprocessor-if    \
+    -modernize-type-traits                              \
+    -clang-analyzer-optin.core.EnumCastOutOfRange       \
+    -performance-inefficient-vector-operation           \
+    -readability-static-accessed-through-instance       \
+    -misc-use-internal-linkage                          \
+    -performance-avoid-endl                             \
 
 TIDY_CHECKS_NORMAL :=                                   \
     $(TIDY_CHECKS_FINE)                                 \
diff --git a/.clang-format b/.clang-format
index 984fa5e..caadedf 100644
--- a/.clang-format
+++ b/.clang-format
@@ -3,5 +3,6 @@
 AllowShortIfStatementsOnASingleLine: false
 AllowShortLoopsOnASingleLine: false
 AllowShortFunctionsOnASingleLine: None
+IncludeBlocks: Preserve
 PenaltyBreakAssignment: 10000000
 PenaltyBreakBeforeFirstCallParameter: 1000
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f58f7a3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.out/
+.vscode/
+.idea/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index da052c3..b045da8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,5 @@
+image: ubuntu:24.10
+
 workflow:
   rules:
     - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
@@ -5,38 +7,31 @@
 
 variables:
   DEBIAN_FRONTEND: noninteractive
-  DOCKER_IMAGE_TAG: $CI_REGISTRY_IMAGE:latest
+
+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
 
 stages:
-  - build-container
   - build
   - tidy
   - style
 
-build-container:
-  stage: build-container
-  image: docker:27.0.3
-  services:
-    - docker:27.0.3-dind
-  variables:
-    DOCKER_TLS_CERTDIR: ""
-  before_script:
-    - echo "$CI_JOB_TOKEN" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
-  script:
-    - docker build -t $DOCKER_IMAGE_TAG -f .ci/Dockerfile .
-    - docker push $DOCKER_IMAGE_TAG
-  after_script:
-    - docker logout
-
 build:
   stage: build
-  image: $DOCKER_IMAGE_TAG
   script:
     - mkdir -p install/arm64
-    - rm ${HOME}/aospless/src
-    - ln -s ${PWD} ${HOME}/aospless/src
-    - make -C ${HOME}/aospless all
-    - cp -r ${HOME}/aospless/install/* install/arm64
+    - cd ..
+    - rm -f aospless_drm_hwcomposer_arm64.tar.xz
+    - rm -rf aospless/*
+    - wget https://gitlab.freedesktop.org/-/project/5/uploads/70643bd1c4d419015b9930b7aadc9cfd/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
 
   artifacts:
     paths:
@@ -45,16 +40,17 @@
 
 tidy:
   stage: tidy
-  image: $DOCKER_IMAGE_TAG
   script:
-    - rm ${HOME}/aospless/src
-    - ln -s ${PWD} ${HOME}/aospless/src
-    - BASE_DIR=${HOME}/aospless make -j$(nproc) -k -f .ci/Makefile
-  timeout: 2h
+    - cd ..
+    - rm -f aospless_drm_hwcomposer_arm64.tar.xz
+    - rm -rf aospless/*
+    - wget https://gitlab.freedesktop.org/-/project/5/uploads/70643bd1c4d419015b9930b7aadc9cfd/aospless_drm_hwcomposer_arm64.tar.xz
+    - tar xf aospless_drm_hwcomposer_arm64.tar.xz
+    - cd -
+    - make -j$(nproc) -k -f .ci/Makefile
 
 checkstyle:
   stage: style
-  image: $DOCKER_IMAGE_TAG
   script: "./.ci/.gitlab-ci-checkcommit.sh"
   artifacts:
     when: on_failure
diff --git a/Android.bp b/Android.bp
index 7d014dc..24d4d99 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,6 +72,8 @@
         "-DHWC2_USE_CPP11",
     ],
 
+    cpp_std: "gnu++17",
+
     relative_install_path: "hw",
     vendor: true,
 }
@@ -97,13 +99,13 @@
         "drm/DrmDisplayPipeline.cpp",
         "drm/DrmEncoder.cpp",
         "drm/DrmFbImporter.cpp",
+        "drm/DrmHwc.cpp",
         "drm/DrmMode.cpp",
         "drm/DrmPlane.cpp",
         "drm/DrmProperty.cpp",
         "drm/ResourceManager.cpp",
         "drm/UEventListener.cpp",
         "drm/VSyncWorker.cpp",
-        "drm/DrmHwc.cpp",
 
         "backend/Backend.cpp",
         "backend/BackendClient.cpp",
@@ -116,6 +118,7 @@
         "hwc2_device/hwc2_device.cpp",
 
         "utils/fd.cpp",
+        "utils/properties.cpp",
     ],
 }
 
@@ -181,9 +184,9 @@
     name: "android.hardware.composer.hwc3-service.drm",
 
     srcs: [
+        ":drm_hwcomposer_common",
         ":drm_hwcomposer_hwc3",
         ":drm_hwcomposer_service",
-        ":drm_hwcomposer_common",
         "bufferinfo/legacy/BufferInfoLibdrm.cpp",
     ],
 
@@ -192,9 +195,8 @@
     ],
 
     shared_libs: [
-        "android.hardware.graphics.composer3-V3-ndk",
+        "android.hardware.graphics.composer3-V4-ndk",
         "libbase",
-        "libbinder",
         "libbinder_ndk",
         "liblog",
         "libutils",
@@ -242,3 +244,60 @@
     name: "drm_hwcomposer_platformmediatek",
     srcs: ["bufferinfo/legacy/BufferInfoMaliMediatek.cpp"],
 }
+
+prebuilt_etc {
+    name: "drm_hwcomposer_hwc3_apex_vintf",
+    src: "hwc3/hwc3-drm.xml",
+    sub_dir: "vintf",
+    vendor: true,
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "drm_hwcomposer_hwc3_apex_init_rc",
+    filename_from_src: true,
+    vendor: true,
+    src: ":gen-drm_hwcomposer_hwc3_apex_init_rc",
+}
+
+genrule {
+    name: "gen-drm_hwcomposer_hwc3_apex_init_rc",
+    srcs: ["hwc3/hwc3-drm.rc"],
+    out: ["hwc3-drm.apex.rc"],
+    cmd: "sed " +
+        // Update the binary location to inside the Apex:
+        "-e 's%/vendor/bin/%/apex/com.android.hardware.graphics.composer/bin/%' " +
+        "$(in) > $(out)",
+}
+
+filegroup {
+    name: "drm_hwcomposer_hwc3_apex_file_contexts",
+    srcs: [
+        "hwc3/hwc3-apex-file-contexts",
+    ],
+}
+
+filegroup {
+    name: "drm_hwcomposer_hwc3_apex_manifest",
+    srcs: [
+        "hwc3/hwc3-apex-manifest.json",
+    ],
+}
+
+apex {
+    name: "com.android.hardware.graphics.composer.drm_hwcomposer",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    file_contexts: ":drm_hwcomposer_hwc3_apex_file_contexts",
+    manifest: ":drm_hwcomposer_hwc3_apex_manifest",
+    vendor: true,
+    updatable: false,
+    soc_specific: true,
+    binaries: [
+        "android.hardware.composer.hwc3-service.drm",
+    ],
+    prebuilts: [
+        "drm_hwcomposer_hwc3_apex_init_rc",
+        "drm_hwcomposer_hwc3_apex_vintf",
+    ],
+}
diff --git a/Makefile b/Makefile
index 8a88083..aa8062b 100644
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,7 @@
 	@echo "Run native build:"
 	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -f .ci/Makefile -j$(NPROCS)"
 	@echo "Run meson cross-build for Android:"
-	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -C ~/aospless all"
+	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -C ~/aospless install"
 	@echo "Run style check:"
 	$(if $(GIT_IS_SYMLINK), \
 		./.ci/.gitlab-ci-checkcommit.sh, \
@@ -59,7 +59,7 @@
 	@echo "\n\e[32m --- SUCCESS ---\n"
 
 ci_cleanup: ## Cleanup after 'make ci'
-	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -f .ci/Makefile clean	"
+	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -f .ci/Makefile clean"
 	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "rm -rf ~/aospless/build"
 	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "rm -rf ~/aospless/install"
 	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "rm -rf ~/aospless/out_src"
@@ -69,14 +69,11 @@
 	$(if $(filter $(shell adb shell getprop ro.bionic.arch),arm64),,$(error arm64 only is supported at the moment))
 	adb root && adb remount vendor
 	mkdir -p .out/arm64
-	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -C ~/aospless all"
+	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "make -C ~/aospless install"
 	$(DOCKER_BIN) exec -it $(IMAGE_NAME) bash -c "cp -r ~/aospless/install/* ~/drm_hwcomposer/.out/arm64"
-	adb push .out/arm64/vendor/lib64/hw/hwcomposer.drm.so /vendor/lib64/hw/hwcomposer.drm.so
+	adb push .out/arm64/vendor/bin/hw/android.hardware.composer.hwc3-service.drm /vendor/bin/hw/android.hardware.composer.hwc3-service.drm
 	adb shell stop
-	adb shell stop vendor.hwcomposer-2-1 && adb shell start vendor.hwcomposer-2-1 || true
-	adb shell stop vendor.hwcomposer-2-2 && adb shell start vendor.hwcomposer-2-2 || true
-	adb shell stop vendor.hwcomposer-2-3 && adb shell start vendor.hwcomposer-2-3 || true
-	adb shell stop vendor.hwcomposer-2-4 && adb shell start vendor.hwcomposer-2-4 || true
+	adb shell stop vendor.hwcomposer-3 && adb shell start vendor.hwcomposer-3 || true
 	bash -c '[[ "$$HWCLOG" -eq "1" ]] && adb logcat -c || true'
 	adb shell start
 	bash -c '[[ "$$HWCLOG" -eq "1" ]] && adb logcat | grep -i hwc || true'
diff --git a/OWNERS b/OWNERS
index 39f2084..a4f788d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
 adelva@google.com
+ddavenport@google.com
 jstultz@google.com
-marcheu@google.com
 seanpaul@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..1f413f7
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,3 @@
+[Hook Scripts]
+
+check hooks installed = hooks/check-hooks-installed
\ No newline at end of file
diff --git a/README.md b/README.md
index 5724b36..76cfd6c 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
   you with formatting of your patches:
 
     ```
-    git diff | clang-format-diff-15 -p 1 -style=file
+    git diff | clang-format-diff-19 -p 1 -style=file
     ```
 
 * Hardware specific changes should be tested on relevant platforms before
diff --git a/compositor/DisplayInfo.h b/compositor/DisplayInfo.h
new file mode 100644
index 0000000..6ddc66f
--- /dev/null
+++ b/compositor/DisplayInfo.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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 <cstdint>
+
+enum class Colorspace : int32_t {
+  kDefault,
+  kSmpte170MYcc,
+  kBt709Ycc,
+  kXvycc601,
+  kXvycc709,
+  kSycc601,
+  kOpycc601,
+  kOprgb,
+  kBt2020Cycc,
+  kBt2020Rgb,
+  kBt2020Ycc,
+  kDciP3RgbD65,
+  kDciP3RgbTheater,
+  kRgbWideFixed,
+  kRgbWideFloat,
+  kBt601Ycc,
+};
+
+/**
+ * Display panel orientation property values.
+ */
+enum PanelOrientation {
+  kModePanelOrientationNormal = 0,
+  kModePanelOrientationBottomUp,
+  kModePanelOrientationLeftUp,
+  kModePanelOrientationRightUp
+};
+
+struct QueuedConfigTiming {
+  // In order for the new config to be applied, the client must send a new frame
+  // at this time.
+  int64_t refresh_time_ns;
+
+  // The time when the display will start to refresh at the new vsync period.
+  int64_t new_vsync_time_ns;
+};
diff --git a/drm/DrmAtomicStateManager.cpp b/drm/DrmAtomicStateManager.cpp
index 537f819..9a8769a 100644
--- a/drm/DrmAtomicStateManager.cpp
+++ b/drm/DrmAtomicStateManager.cpp
@@ -101,7 +101,7 @@
     }
   }
 
-  bool nonblock = true;
+  bool nonblock = !args.blocking;
 
   if (args.active) {
     nonblock = false;
@@ -139,6 +139,17 @@
       return -EINVAL;
   }
 
+  if (args.colorspace && connector->GetColorspaceProperty()) {
+    if (!connector->GetColorspaceProperty()
+             .AtomicSet(*pset, connector->GetColorspacePropertyValue(*args.colorspace)))
+      return -EINVAL;
+  }
+
+  if (args.content_type && connector->GetContentTypeProperty()) {
+    if (!connector->GetContentTypeProperty().AtomicSet(*pset, *args.content_type))
+      return -EINVAL;
+  }
+
   auto unused_planes = new_frame_state.used_planes;
 
   if (args.composition) {
diff --git a/drm/DrmAtomicStateManager.h b/drm/DrmAtomicStateManager.h
index 923927d..8d22b99 100644
--- a/drm/DrmAtomicStateManager.h
+++ b/drm/DrmAtomicStateManager.h
@@ -21,6 +21,7 @@
 #include <memory>
 #include <optional>
 
+#include "compositor/DisplayInfo.h"
 #include "compositor/DrmKmsPlan.h"
 #include "compositor/LayerData.h"
 #include "drm/DrmPlane.h"
@@ -32,10 +33,13 @@
 struct AtomicCommitArgs {
   /* inputs. All fields are optional, but at least one has to be specified */
   bool test_only = false;
+  bool blocking = false;
   std::optional<DrmMode> display_mode;
   std::optional<bool> active;
   std::shared_ptr<DrmKmsPlan> composition;
   std::shared_ptr<drm_color_ctm> color_matrix;
+  std::optional<Colorspace> colorspace;
+  std::optional<int32_t> content_type;
 
   std::shared_ptr<DrmFbIdHandle> writeback_fb;
   SharedFd writeback_release_fence;
diff --git a/drm/DrmConnector.cpp b/drm/DrmConnector.cpp
index e459fe7..eeec3b1 100644
--- a/drm/DrmConnector.cpp
+++ b/drm/DrmConnector.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <drm/drm_mode.h>
 #define LOG_TAG "drmhwc"
 
 #include "DrmConnector.h"
@@ -22,10 +23,12 @@
 
 #include <array>
 #include <cerrno>
+#include <cinttypes>
 #include <cstdint>
 #include <sstream>
 
 #include "DrmDevice.h"
+#include "compositor/DisplayInfo.h"
 #include "utils/log.h"
 
 #ifndef DRM_MODE_CONNECTOR_SPI
@@ -42,22 +45,21 @@
 
 constexpr size_t kTypesCount = 21;
 
-static bool GetOptionalConnectorProperty(const DrmDevice &dev,
-                                         const DrmConnector &connector,
-                                         const char *prop_name,
-                                         DrmProperty *property) {
-  return dev.GetProperty(connector.GetId(), DRM_MODE_OBJECT_CONNECTOR,
-                         prop_name, property) == 0;
-}
+auto DrmConnector::GetConnectorProperty(const char *prop_name,
+                                        DrmProperty *property,
+                                        bool is_optional) -> bool {
+  auto err = drm_->GetProperty(GetId(), DRM_MODE_OBJECT_CONNECTOR, prop_name,
+                               property);
+  if (err == 0)
+    return true;
 
-static bool GetConnectorProperty(const DrmDevice &dev,
-                                 const DrmConnector &connector,
-                                 const char *prop_name, DrmProperty *property) {
-  if (!GetOptionalConnectorProperty(dev, connector, prop_name, property)) {
-    ALOGE("Could not get %s property\n", prop_name);
-    return false;
+  if (is_optional) {
+    ALOGV("Could not get optional %s property from connector %d", prop_name,
+          GetId());
+  } else {
+    ALOGE("Could not get %s property from connector %d", prop_name, GetId());
   }
-  return true;
+  return false;
 }
 
 auto DrmConnector::CreateInstance(DrmDevice &dev, uint32_t connector_id,
@@ -72,28 +74,95 @@
   auto c = std::unique_ptr<DrmConnector>(
       new DrmConnector(std::move(conn), &dev, index));
 
-  if (!GetConnectorProperty(dev, *c, "DPMS", &c->dpms_property_) ||
-      !GetConnectorProperty(dev, *c, "CRTC_ID", &c->crtc_id_property_)) {
-    return {};
-  }
-
-  c->UpdateEdidProperty();
-
-  if (c->IsWriteback() &&
-      (!GetConnectorProperty(dev, *c, "WRITEBACK_PIXEL_FORMATS",
-                             &c->writeback_pixel_formats_) ||
-       !GetConnectorProperty(dev, *c, "WRITEBACK_FB_ID",
-                             &c->writeback_fb_id_) ||
-       !GetConnectorProperty(dev, *c, "WRITEBACK_OUT_FENCE_PTR",
-                             &c->writeback_out_fence_))) {
+  if (!c->Init()) {
+    ALOGE("Failed to initialize connector %d", connector_id);
     return {};
   }
 
   return c;
 }
 
+auto DrmConnector::Init()-> bool {
+  if (!GetConnectorProperty("DPMS", &dpms_property_) ||
+      !GetConnectorProperty("CRTC_ID", &crtc_id_property_)) {
+    return false;
+  }
+
+  UpdateEdidProperty();
+
+  if (IsWriteback() &&
+      (!GetConnectorProperty("WRITEBACK_PIXEL_FORMATS",
+                             &writeback_pixel_formats_) ||
+       !GetConnectorProperty("WRITEBACK_FB_ID", &writeback_fb_id_) ||
+       !GetConnectorProperty("WRITEBACK_OUT_FENCE_PTR",
+                             &writeback_out_fence_))) {
+    return false;
+  }
+
+  if (GetConnectorProperty("Colorspace", &colorspace_property_,
+                           /*is_optional=*/true)) {
+    colorspace_property_.AddEnumToMap("Default", Colorspace::kDefault,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("SMPTE_170M_YCC", Colorspace::kSmpte170MYcc,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("BT709_YCC", Colorspace::kBt709Ycc,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("XVYCC_601", Colorspace::kXvycc601,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("XVYCC_709", Colorspace::kXvycc709,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("SYCC_601", Colorspace::kSycc601,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("opYCC_601", Colorspace::kOpycc601,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("opRGB", Colorspace::kOprgb,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("BT2020_CYCC", Colorspace::kBt2020Cycc,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("BT2020_RGB", Colorspace::kBt2020Rgb,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("BT2020_YCC", Colorspace::kBt2020Ycc,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("DCI-P3_RGB_D65", Colorspace::kDciP3RgbD65,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("DCI-P3_RGB_Theater", Colorspace::kDciP3RgbTheater,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("RGB_WIDE_FIXED", Colorspace::kRgbWideFixed,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("RGB_WIDE_FLOAT", Colorspace::kRgbWideFloat,
+                                      colorspace_enum_map_);
+    colorspace_property_.AddEnumToMap("BT601_YCC", Colorspace::kBt601Ycc,
+                                      colorspace_enum_map_);
+  }
+
+  GetConnectorProperty("content type", &content_type_property_,
+                       /*is_optional=*/true);
+
+  if (GetConnectorProperty("panel orientation", &panel_orientation_,
+                           /*is_optional=*/true)) {
+    panel_orientation_
+        .AddEnumToMapReverse("Normal",
+                             PanelOrientation::kModePanelOrientationNormal,
+                             panel_orientation_enum_map_);
+    panel_orientation_
+        .AddEnumToMapReverse("Upside Down",
+                             PanelOrientation::kModePanelOrientationBottomUp,
+                             panel_orientation_enum_map_);
+    panel_orientation_
+        .AddEnumToMapReverse("Left Side Up",
+                             PanelOrientation::kModePanelOrientationLeftUp,
+                             panel_orientation_enum_map_);
+    panel_orientation_
+        .AddEnumToMapReverse("Right Side Up",
+                             PanelOrientation::kModePanelOrientationRightUp,
+                             panel_orientation_enum_map_);
+  }
+
+  return true;
+}
+
 int DrmConnector::UpdateEdidProperty() {
-  return GetOptionalConnectorProperty(*drm_, *this, "EDID", &edid_property_)
+  return GetConnectorProperty("EDID", &edid_property_, /*is_optional=*/true)
              ? 0
              : -EINVAL;
 }
@@ -184,4 +253,36 @@
   return 0;
 }
 
+bool DrmConnector::IsLinkStatusGood() {
+  if (GetConnectorProperty("link-status", &link_status_property_, false)) {
+    auto link_status_property_value = link_status_property_.GetValue();
+    if (link_status_property_value &&
+        (link_status_property_value == DRM_MODE_LINK_STATUS_BAD))
+      return false;
+  }
+
+  return true;
+}
+
+std::optional<PanelOrientation> DrmConnector::GetPanelOrientation() {
+  if (!panel_orientation_.GetValue().has_value()) {
+    ALOGW("No panel orientation property available.");
+    return {};
+  }
+
+  /* The value_or(0) satisfies the compiler warning. However,
+   * panel_orientation_.GetValue() is guaranteed to have a value since we check
+   * has_value() and return early otherwise.
+   */
+  uint64_t panel_orientation_value = panel_orientation_.GetValue().value_or(0);
+
+  if (panel_orientation_enum_map_.count(panel_orientation_value) == 1) {
+    return panel_orientation_enum_map_[panel_orientation_value];
+  }
+
+  ALOGE("Unknown panel orientation: panel_orientation = %" PRIu64,
+        panel_orientation_value);
+  return {};
+}
+
 }  // namespace android
diff --git a/drm/DrmConnector.h b/drm/DrmConnector.h
index 018c615..be84ae3 100644
--- a/drm/DrmConnector.h
+++ b/drm/DrmConnector.h
@@ -26,6 +26,7 @@
 #include "DrmMode.h"
 #include "DrmProperty.h"
 #include "DrmUnique.h"
+#include "compositor/DisplayInfo.h"
 
 namespace android {
 
@@ -78,6 +79,8 @@
 
   int UpdateModes();
 
+  bool IsLinkStatusGood();
+
   auto &GetModes() const {
     return modes_;
   }
@@ -94,6 +97,18 @@
     return edid_property_;
   }
 
+  auto &GetColorspaceProperty() const {
+    return colorspace_property_;
+  }
+
+  auto GetColorspacePropertyValue(Colorspace c) {
+    return colorspace_enum_map_[c];
+  }
+
+  auto &GetContentTypeProperty() const {
+    return content_type_property_;
+  }
+
   auto &GetWritebackFbIdProperty() const {
     return writeback_fb_id_;
   }
@@ -102,6 +117,10 @@
     return writeback_out_fence_;
   }
 
+  auto &GetPanelOrientationProperty() const {
+    return panel_orientation_;
+  }
+
   auto IsConnected() const {
     return connector_->connection == DRM_MODE_CONNECTED;
   }
@@ -114,15 +133,21 @@
     return connector_->mmHeight;
   };
 
+  auto GetPanelOrientation() -> std::optional<PanelOrientation>;
+
  private:
   DrmConnector(DrmModeConnectorUnique connector, DrmDevice *drm, uint32_t index)
       : connector_(std::move(connector)),
         drm_(drm),
-        index_in_res_array_(index){};
+        index_in_res_array_(index) {};
 
   DrmModeConnectorUnique connector_;
   DrmDevice *const drm_;
 
+  auto Init() -> bool;
+  auto GetConnectorProperty(const char *prop_name, DrmProperty *property,
+                            bool is_optional = false) -> bool;
+
   const uint32_t index_in_res_array_;
 
   std::vector<DrmMode> modes_;
@@ -130,8 +155,16 @@
   DrmProperty dpms_property_;
   DrmProperty crtc_id_property_;
   DrmProperty edid_property_;
+  DrmProperty colorspace_property_;
+  DrmProperty content_type_property_;
+
+  DrmProperty link_status_property_;
   DrmProperty writeback_pixel_formats_;
   DrmProperty writeback_fb_id_;
   DrmProperty writeback_out_fence_;
+  DrmProperty panel_orientation_;
+
+  std::map<Colorspace, uint64_t> colorspace_enum_map_;
+  std::map<uint64_t, PanelOrientation> panel_orientation_enum_map_;
 };
 }  // namespace android
diff --git a/drm/DrmHwc.cpp b/drm/DrmHwc.cpp
index df3eb56..aaba506 100644
--- a/drm/DrmHwc.cpp
+++ b/drm/DrmHwc.cpp
@@ -22,6 +22,7 @@
 
 #include "backend/Backend.h"
 #include "utils/log.h"
+#include "utils/properties.h"
 
 namespace android {
 
@@ -124,6 +125,16 @@
   return true;
 }
 
+void DrmHwc::NotifyDisplayLinkStatus(
+    std::shared_ptr<DrmDisplayPipeline> pipeline) {
+  if (display_handles_.count(pipeline) == 0) {
+    ALOGE("%s, can't find the display, pipeline: %p", __func__, pipeline.get());
+    return;
+  }
+  ScheduleHotplugEvent(display_handles_[pipeline],
+                       DisplayStatus::kLinkTrainingFailed);
+}
+
 HWC2::Error DrmHwc::CreateVirtualDisplay(
     uint32_t width, uint32_t height,
     int32_t *format,  // NOLINT(readability-non-const-parameter)
@@ -186,6 +197,13 @@
 }
 
 uint32_t DrmHwc::GetMaxVirtualDisplayCount() {
+  /* Virtual display is an experimental feature.
+   * Unless explicitly set to true, return 0 for no support.
+   */
+  if (0 == property_get_bool("vendor.hwc.drm.enable_virtual_display", 0)) {
+    return 0;
+  }
+
   auto writeback_count = resource_manager_.GetWritebackConnectorsCount();
   writeback_count = std::min(writeback_count, 1U);
   /* Currently, only 1 virtual display is supported. Other cases need testing */
diff --git a/drm/DrmHwc.h b/drm/DrmHwc.h
index 44dc276..ac97717 100644
--- a/drm/DrmHwc.h
+++ b/drm/DrmHwc.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "drm/DrmDisplayPipeline.h"
 #include "drm/ResourceManager.h"
 #include "hwc2_device/HwcDisplay.h"
 
@@ -26,6 +27,13 @@
   DrmHwc();
   ~DrmHwc() override = default;
 
+  // Enum for Display status: Connected, Disconnected, Link Training Failed
+  enum DisplayStatus {
+    kDisconnected,
+    kConnected,
+    kLinkTrainingFailed,
+  };
+
   // Client Callback functions.:
   virtual void SendVsyncEventToClient(hwc2_display_t displayid,
                                       int64_t timestamp,
@@ -34,7 +42,7 @@
       hwc2_display_t displayid, int64_t timestamp) const = 0;
   virtual void SendRefreshEventToClient(uint64_t displayid) = 0;
   virtual void SendHotplugEventToClient(hwc2_display_t displayid,
-                                        bool connected) = 0;
+                                        enum DisplayStatus display_status) = 0;
 
   // Device functions
   HWC2::Error CreateVirtualDisplay(uint32_t width, uint32_t height,
@@ -53,8 +61,9 @@
     return resource_manager_;
   }
 
-  void ScheduleHotplugEvent(hwc2_display_t displayid, bool connected) {
-    deferred_hotplug_events_[displayid] = connected;
+  void ScheduleHotplugEvent(hwc2_display_t displayid,
+                            enum DisplayStatus display_status) {
+    deferred_hotplug_events_[displayid] = display_status;
   }
 
   void DeinitDisplays();
@@ -64,8 +73,14 @@
   bool UnbindDisplay(std::shared_ptr<DrmDisplayPipeline> pipeline) override;
   void FinalizeDisplayBinding() override;
 
+  // Notify Display Link Status
+  void NotifyDisplayLinkStatus(
+      std::shared_ptr<DrmDisplayPipeline> pipeline) override;
+
  protected:
-  auto& Displays() { return displays_; }
+  auto &Displays() {
+    return displays_;
+  }
 
  private:
   ResourceManager resource_manager_;
@@ -75,7 +90,7 @@
 
   std::string dump_string_;
 
-  std::map<hwc2_display_t, bool> deferred_hotplug_events_;
+  std::map<hwc2_display_t, enum DisplayStatus> deferred_hotplug_events_;
   std::vector<hwc2_display_t> displays_for_removal_list_;
 
   uint32_t last_display_handle_ = kPrimaryDisplay;
diff --git a/drm/DrmMode.h b/drm/DrmMode.h
index c5790a5..5450daf 100644
--- a/drm/DrmMode.h
+++ b/drm/DrmMode.h
@@ -48,6 +48,12 @@
            (float)(mode_.vtotal * mode_.htotal) * 1000.0F;
   }
 
+  auto GetVSyncPeriodNs() const {
+    static const int kNanosecondsPerSecond = 1E9;
+    return static_cast<int32_t>(kNanosecondsPerSecond *
+                                double(1 / GetVRefresh()));
+  }
+
   auto GetName() const {
     return std::string(mode_.name) + "@" + std::to_string(GetVRefresh());
   }
diff --git a/drm/DrmPlane.cpp b/drm/DrmPlane.cpp
index 310e67b..dbb5ad6 100644
--- a/drm/DrmPlane.cpp
+++ b/drm/DrmPlane.cpp
@@ -155,14 +155,11 @@
   if (crtc_prop_val != 0 && crtc_prop_val != crtc.GetId() &&
       GetType() == DRM_PLANE_TYPE_PRIMARY) {
     // Some DRM driver such as omap_drm allows sharing primary plane between
-    // CRTCs, but the primay plane could not be shared if it has been used by
+    // CRTCs, but the primary plane could not be shared if it has been used by
     // any CRTC already, which is protected by the plane_switching_crtc function
     // in the kernel drivers/gpu/drm/drm_atomic.c file.
     // The current drm_hwc design is not ready to support such scenario yet,
     // so adding the CRTC status check here to workaorund for now.
-    ALOGW("%s: This Plane(id=%d) is activated for Crtc(id=%" PRIu64
-          "), could not be used for Crtc (id=%d)",
-          __FUNCTION__, GetId(), crtc_prop_val, crtc.GetId());
     return false;
   }
 
diff --git a/drm/DrmProperty.cpp b/drm/DrmProperty.cpp
index 031918a..dbd307e 100644
--- a/drm/DrmProperty.cpp
+++ b/drm/DrmProperty.cpp
@@ -22,6 +22,7 @@
 #include <xf86drmMode.h>
 
 #include <cerrno>
+#include <cinttypes>
 #include <cstdint>
 #include <string>
 
@@ -125,4 +126,22 @@
   return true;
 }
 
+std::optional<std::string> DrmProperty::GetEnumNameFromValue(
+    uint64_t value) const {
+  if (enums_.empty()) {
+    ALOGE("No enum values for property: %s", name_.c_str());
+    return {};
+  }
+
+  for (const auto &it : enums_) {
+    if (it.value == value) {
+      return it.name;
+    }
+  }
+
+  ALOGE("Property '%s' has no matching enum for value: %" PRIu64, name_.c_str(),
+        value);
+  return {};
+}
+
 }  // namespace android
diff --git a/drm/DrmProperty.h b/drm/DrmProperty.h
index 516518b..2683ad8 100644
--- a/drm/DrmProperty.h
+++ b/drm/DrmProperty.h
@@ -64,10 +64,16 @@
   auto AddEnumToMap(const std::string &name, E key, std::map<E, uint64_t> &map)
       -> bool;
 
+  template <class E>
+  auto AddEnumToMapReverse(const std::string &name, E value,
+                           std::map<uint64_t, E> &map) -> bool;
+
   explicit operator bool() const {
     return id_ != 0;
   }
 
+  auto GetEnumNameFromValue(uint64_t value) const -> std::optional<std::string>;
+
  private:
   class DrmPropertyEnum {
    public:
@@ -104,4 +110,18 @@
   return false;
 }
 
+template <class E>
+auto DrmProperty::AddEnumToMapReverse(const std::string &name, E value,
+                                      std::map<uint64_t, E> &map) -> bool {
+  uint64_t enum_value = UINT64_MAX;
+  int err = 0;
+  std::tie(enum_value, err) = GetEnumValueWithName(name);
+  if (err == 0) {
+    map[enum_value] = value;
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace android
diff --git a/drm/ResourceManager.cpp b/drm/ResourceManager.cpp
index 5ac80c4..0c23734 100644
--- a/drm/ResourceManager.cpp
+++ b/drm/ResourceManager.cpp
@@ -154,6 +154,10 @@
         attached_pipelines_.erase(conn);
       }
     }
+    if (connected) {
+      if (!conn->IsLinkStatusGood())
+        frontend_interface_->NotifyDisplayLinkStatus(attached_pipelines_[conn]);
+    }
   }
   frontend_interface_->FinalizeDisplayBinding();
 }
diff --git a/drm/ResourceManager.h b/drm/ResourceManager.h
index 9a2652c..3c4d7ae 100644
--- a/drm/ResourceManager.h
+++ b/drm/ResourceManager.h
@@ -22,6 +22,7 @@
 #include "DrmDevice.h"
 #include "DrmDisplayPipeline.h"
 #include "DrmFbImporter.h"
+#include "DrmProperty.h"
 #include "UEventListener.h"
 
 namespace android {
@@ -37,6 +38,8 @@
   virtual bool BindDisplay(std::shared_ptr<DrmDisplayPipeline>) = 0;
   virtual bool UnbindDisplay(std::shared_ptr<DrmDisplayPipeline>) = 0;
   virtual void FinalizeDisplayBinding() = 0;
+  virtual void NotifyDisplayLinkStatus(
+      std::shared_ptr<DrmDisplayPipeline> pipeline) = 0;
 };
 
 class ResourceManager {
diff --git a/hooks/check-hooks-installed b/hooks/check-hooks-installed
new file mode 100755
index 0000000..45e5c73
--- /dev/null
+++ b/hooks/check-hooks-installed
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Gerrit hook that runs on repo upload. Checks to ensure that the pre-upload hook
+# has been installed.
+
+cmd=$(git config hookcmd.check-non-public-commits.command)
+if [ -z "$cmd" ]; then
+  echo "Please install hooks by running: hooks/install-hooks.sh"
+  exit 1
+fi
\ No newline at end of file
diff --git a/hooks/check-non-public-commits b/hooks/check-non-public-commits
new file mode 100755
index 0000000..fc20795
--- /dev/null
+++ b/hooks/check-non-public-commits
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# git pre-push hook to detect whether a developer is attempting to push
+# non-public commits to a public repository.
+
+remote="$1"
+url="$2"
+
+# Don't bother checking if this is being pushed to gerrit.
+if [[ "$url" = "sso://googleplex-android/platform/external/drm_hwcomposer" ]] ||
+   [[ "$url" = "sso://android.googlesource.com/platform/external/drm_hwcomposer" ]]
+then
+  exit 0
+fi
+
+while read local_ref local_sha remote_ref remote_sha
+do
+  # Gather a list of all commits that are to be pushed to the remote.
+  # remote_sha will be 000000 if there is no corresponding remote branch.
+  if [[ "$remote_sha" =~ "0000000000" ]]; then
+    commits=$(git rev-list $local_sha --not --remotes=$remote)
+  else
+    commits=$(git rev-list $remote_sha..$local_sha)
+  fi
+
+  # Check each commit message for the prohibited prefix.
+  for commit in $commits; do
+    # Get the commit message.
+    message=$(git log -1 --pretty=%B $commit)
+
+    # Check if the commit message starts with "ANDROID:"
+    if [[ "$message" == "ANDROID"* ]] ||
+       [[ "$message" == "INTERNAL"* ]] ||
+       [[ "$message" == "DO NOT MERGE"* ]]; then
+      echo "Error: Commit message starts with downstream tag:"
+      echo "$message"
+      echo "It looks like you're trying to push internal changes to an externally "
+      echo "visible repository: $url"
+      exit 1
+    fi
+  done
+done
+
+exit 0
diff --git a/hooks/install-hooks.sh b/hooks/install-hooks.sh
new file mode 100755
index 0000000..cc5d967
--- /dev/null
+++ b/hooks/install-hooks.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# Install hooks.
+git config --add hookcmd.check-non-public-commits.command "[ ! -d hooks ] || hooks/check-non-public-commits"
+git config --add hook.pre-push.command check-non-public-commits
\ No newline at end of file
diff --git a/hwc2_device/DrmHwcTwo.cpp b/hwc2_device/DrmHwcTwo.cpp
index c120b63..b243199 100644
--- a/hwc2_device/DrmHwcTwo.cpp
+++ b/hwc2_device/DrmHwcTwo.cpp
@@ -69,15 +69,16 @@
 }
 
 void DrmHwcTwo::SendHotplugEventToClient(hwc2_display_t displayid,
-                                         bool connected) {
+                                         DisplayStatus display_status) {
   auto hc = hotplug_callback_;
+
   if (hc.first != nullptr && hc.second != nullptr) {
     /* For some reason HWC Service will call HWC2 API in hotplug callback
      * handler. This is the reason we're using recursive mutex.
      */
     hc.first(hc.second, displayid,
-             connected ? HWC2_CONNECTION_CONNECTED
-                       : HWC2_CONNECTION_DISCONNECTED);
+             display_status ? HWC2_CONNECTION_CONNECTED
+                            : HWC2_CONNECTION_DISCONNECTED);
   }
 }
 
diff --git a/hwc2_device/DrmHwcTwo.h b/hwc2_device/DrmHwcTwo.h
index b3ca0f8..ac87153 100644
--- a/hwc2_device/DrmHwcTwo.h
+++ b/hwc2_device/DrmHwcTwo.h
@@ -37,7 +37,7 @@
       hwc2_display_t displayid, int64_t timestamp) const override;
   void SendRefreshEventToClient(uint64_t displayid) override;
   void SendHotplugEventToClient(hwc2_display_t displayid,
-                                bool connected) override;
+                                DisplayStatus display_status) override;
 
  private:
   std::pair<HWC2_PFN_HOTPLUG, hwc2_callback_data_t> hotplug_callback_{};
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index 36333d2..0503f39 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -19,15 +19,92 @@
 
 #include "HwcDisplay.h"
 
+#include <cinttypes>
+
+#include <hardware/gralloc.h>
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+#include <ui/PixelFormat.h>
+
 #include "backend/Backend.h"
 #include "backend/BackendManager.h"
 #include "bufferinfo/BufferInfoGetter.h"
+#include "compositor/DisplayInfo.h"
+#include "drm/DrmConnector.h"
+#include "drm/DrmDisplayPipeline.h"
 #include "drm/DrmHwc.h"
 #include "utils/log.h"
 #include "utils/properties.h"
 
+using ::android::DrmDisplayPipeline;
+
 namespace android {
 
+namespace {
+// Allocate a black buffer that can be used for an initial modeset when there.
+// is no appropriate client buffer available to be used.
+// Caller must free the returned buffer with GraphicBufferAllocator::free.
+auto GetModesetBuffer(uint32_t width, uint32_t height) -> buffer_handle_t {
+  constexpr PixelFormat format = PIXEL_FORMAT_RGBA_8888;
+  constexpr uint64_t usage = GRALLOC_USAGE_SW_READ_OFTEN |
+                             GRALLOC_USAGE_SW_WRITE_OFTEN |
+                             GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_FB;
+
+  constexpr uint32_t layer_count = 1;
+  const std::string name = "drm-hwcomposer";
+
+  buffer_handle_t handle = nullptr;
+  uint32_t stride = 0;
+  status_t status = GraphicBufferAllocator::get().allocate(width, height,
+                                                           format, layer_count,
+                                                           usage, &handle,
+                                                           &stride, name);
+  if (status != OK) {
+    ALOGE("Failed to allocate modeset buffer.");
+    return nullptr;
+  }
+
+  void *data = nullptr;
+  Rect bounds = {0, 0, static_cast<int32_t>(width),
+                 static_cast<int32_t>(height)};
+  status = GraphicBufferMapper::get().lock(handle, usage, bounds, &data);
+  if (status != OK) {
+    ALOGE("Failed to map modeset buffer.");
+    GraphicBufferAllocator::get().free(handle);
+    return nullptr;
+  }
+
+  // Cast one of the multiplicands to ensure that the multiplication happens
+  // in a wider type (size_t).
+  const size_t buffer_size = static_cast<size_t>(height) * stride *
+                             bytesPerPixel(format);
+  memset(data, 0, buffer_size);
+  status = GraphicBufferMapper::get().unlock(handle);
+  ALOGW_IF(status != OK, "Failed to unmap buffer.");
+  return handle;
+}
+
+auto GetModesetLayerProperties(buffer_handle_t buffer, uint32_t width,
+                               uint32_t height) -> HwcLayer::LayerProperties {
+  HwcLayer::LayerProperties properties;
+  properties.buffer = {.buffer_handle = buffer, .acquire_fence = {}};
+  properties.display_frame = {
+      .left = 0,
+      .top = 0,
+      .right = int(width),
+      .bottom = int(height),
+  };
+  properties.source_crop = (hwc_frect_t){
+      .left = 0.0F,
+      .top = 0.0F,
+      .right = static_cast<float>(width),
+      .bottom = static_cast<float>(height),
+  };
+  properties.blend_mode = BufferBlendMode::kNone;
+  return properties;
+}
+}  // namespace
+
 std::string HwcDisplay::DumpDelta(HwcDisplay::Stats delta) {
   if (delta.total_pixops_ == 0)
     return "No stats yet";
@@ -73,7 +150,7 @@
   }
 }
 
-void HwcDisplay::SetColorMarixToIdentity() {
+void HwcDisplay::SetColorMatrixToIdentity() {
   color_matrix_ = std::make_shared<drm_color_ctm>();
   for (int i = 0; i < kCtmCols; i++) {
     for (int j = 0; j < kCtmRows; j++) {
@@ -89,6 +166,110 @@
   Deinit();
 };
 
+auto HwcDisplay::GetConfig(hwc2_config_t config_id) const
+    -> const HwcDisplayConfig * {
+  auto config_iter = configs_.hwc_configs.find(config_id);
+  if (config_iter == configs_.hwc_configs.end()) {
+    return nullptr;
+  }
+  return &config_iter->second;
+}
+
+auto HwcDisplay::GetCurrentConfig() const -> const HwcDisplayConfig * {
+  return GetConfig(configs_.active_config_id);
+}
+
+auto HwcDisplay::GetLastRequestedConfig() const -> const HwcDisplayConfig * {
+  return GetConfig(staged_mode_config_id_.value_or(configs_.active_config_id));
+}
+
+HwcDisplay::ConfigError HwcDisplay::SetConfig(hwc2_config_t config) {
+  const HwcDisplayConfig *new_config = GetConfig(config);
+  if (new_config == nullptr) {
+    ALOGE("Could not find active mode for %u", config);
+    return ConfigError::kBadConfig;
+  }
+
+  const HwcDisplayConfig *current_config = GetCurrentConfig();
+
+  const uint32_t width = new_config->mode.GetRawMode().hdisplay;
+  const uint32_t height = new_config->mode.GetRawMode().vdisplay;
+
+  std::optional<LayerData> modeset_layer_data;
+  // If a client layer has already been provided, and its size matches the
+  // new config, use it for the modeset.
+  if (client_layer_.IsLayerUsableAsDevice() && current_config &&
+      current_config->mode.GetRawMode().hdisplay == width &&
+      current_config->mode.GetRawMode().vdisplay == height) {
+    ALOGV("Use existing client_layer for blocking config.");
+    modeset_layer_data = client_layer_.GetLayerData();
+  } else {
+    ALOGV("Allocate modeset buffer.");
+    buffer_handle_t modeset_buffer = GetModesetBuffer(width, height);
+    if (modeset_buffer != nullptr) {
+      auto modeset_layer = std::make_unique<HwcLayer>(this);
+      modeset_layer->SetLayerProperties(
+          GetModesetLayerProperties(modeset_buffer, width, height));
+      modeset_layer->PopulateLayerData();
+      modeset_layer_data = modeset_layer->GetLayerData();
+      GraphicBufferAllocator::get().free(modeset_buffer);
+    }
+  }
+
+  ALOGV("Create modeset commit.");
+  // Create atomic commit args for a blocking modeset. There's no need to do a
+  // separate test commit, since the commit does a test anyways.
+  AtomicCommitArgs commit_args = CreateModesetCommit(new_config,
+                                                     modeset_layer_data);
+  commit_args.blocking = true;
+  int ret = GetPipe().atomic_state_manager->ExecuteAtomicCommit(commit_args);
+
+  if (ret) {
+    ALOGE("Blocking config failed: %d", ret);
+    return HwcDisplay::ConfigError::kBadConfig;
+  }
+
+  ALOGV("Blocking config succeeded.");
+  configs_.active_config_id = config;
+  staged_mode_config_id_.reset();
+  return ConfigError::kNone;
+}
+
+auto HwcDisplay::QueueConfig(hwc2_config_t config, int64_t desired_time,
+                             bool seamless, QueuedConfigTiming *out_timing)
+    -> ConfigError {
+  if (configs_.hwc_configs.count(config) == 0) {
+    ALOGE("Could not find active mode for %u", config);
+    return ConfigError::kBadConfig;
+  }
+
+  // TODO: Add support for seamless configuration changes.
+  if (seamless) {
+    return ConfigError::kSeamlessNotAllowed;
+  }
+
+  // Request a refresh from the client one vsync period before the desired
+  // time, or simply at the desired time if there is no active configuration.
+  const HwcDisplayConfig *current_config = GetCurrentConfig();
+  out_timing->refresh_time_ns = desired_time -
+                                (current_config
+                                     ? current_config->mode.GetVSyncPeriodNs()
+                                     : 0);
+  out_timing->new_vsync_time_ns = desired_time;
+
+  // Queue the config change timing to be consistent with the requested
+  // refresh time.
+  staged_mode_change_time_ = out_timing->refresh_time_ns;
+  staged_mode_config_id_ = config;
+
+  // Enable vsync events until the mode has been applied.
+  last_vsync_ts_ = 0;
+  vsync_tracking_en_ = true;
+  vsync_worker_->VSyncControl(true);
+
+  return ConfigError::kNone;
+}
+
 void HwcDisplay::SetPipeline(std::shared_ptr<DrmDisplayPipeline> pipeline) {
   Deinit();
 
@@ -96,9 +277,9 @@
 
   if (pipeline_ != nullptr || handle_ == kPrimaryDisplay) {
     Init();
-    hwc_->ScheduleHotplugEvent(handle_, /*connected = */ true);
+    hwc_->ScheduleHotplugEvent(handle_, DrmHwc::kConnected);
   } else {
-    hwc_->ScheduleHotplugEvent(handle_, /*connected = */ false);
+    hwc_->ScheduleHotplugEvent(handle_, DrmHwc::kDisconnected);
   }
 }
 
@@ -107,22 +288,9 @@
     AtomicCommitArgs a_args{};
     a_args.composition = std::make_shared<DrmKmsPlan>();
     GetPipe().atomic_state_manager->ExecuteAtomicCommit(a_args);
-/*
- *  TODO:
- *  Unfortunately the following causes regressions on db845c
- *  with VtsHalGraphicsComposerV2_3TargetTest due to the display
- *  never coming back. Patches to avoiding that issue on the
- *  the kernel side unfortunately causes further crashes in
- *  drm_hwcomposer, because the client detach takes longer then the
- *  1 second max VTS expects. So for now as a workaround, lets skip
- *  deactivating the display on deinit, which matches previous
- *  behavior prior to commit d0494d9b8097
- */
-#if 0
     a_args.composition = {};
     a_args.active = false;
     GetPipe().atomic_state_manager->ExecuteAtomicCommit(a_args);
-#endif
 
     current_plan_.reset();
     backend_.reset();
@@ -189,11 +357,28 @@
 
   client_layer_.SetLayerBlendMode(HWC2_BLEND_MODE_PREMULTIPLIED);
 
-  SetColorMarixToIdentity();
+  SetColorMatrixToIdentity();
 
   return HWC2::Error::None;
 }
 
+std::optional<PanelOrientation> HwcDisplay::getDisplayPhysicalOrientation() {
+  if (IsInHeadlessMode()) {
+    // The pipeline can be nullptr in headless mode, so return the default
+    // "normal" mode.
+    return PanelOrientation::kModePanelOrientationNormal;
+  }
+
+  DrmDisplayPipeline &pipeline = GetPipe();
+  if (pipeline.connector == nullptr || pipeline.connector->Get() == nullptr) {
+    ALOGW(
+        "No display pipeline present to query the panel orientation property.");
+    return {};
+  }
+
+  return pipeline.connector->Get()->GetPanelOrientation();
+}
+
 HWC2::Error HwcDisplay::ChosePreferredConfig() {
   HWC2::Error err{};
   if (type_ == HWC2::DisplayType::Virtual) {
@@ -233,10 +418,12 @@
 }
 
 HWC2::Error HwcDisplay::GetActiveConfig(hwc2_config_t *config) const {
-  if (configs_.hwc_configs.count(staged_mode_config_id_) == 0)
+  // If a config has been queued, it is considered the "active" config.
+  const HwcDisplayConfig *hwc_config = GetLastRequestedConfig();
+  if (hwc_config == nullptr)
     return HWC2::Error::BadConfig;
 
-  *config = staged_mode_config_id_;
+  *config = hwc_config->id;
   return HWC2::Error::None;
 }
 
@@ -320,7 +507,7 @@
       break;
     case HWC2::Attribute::VsyncPeriod:
       // in nanoseconds
-      *value = static_cast<int>(1E9 / hwc_config.mode.GetVRefresh());
+      *value = hwc_config.mode.GetVSyncPeriodNs();
       break;
     case HWC2::Attribute::DpiY:
       // ideally this should be vdisplay/mm_heigth, however mm_height
@@ -455,6 +642,34 @@
   return HWC2::Error::None;
 }
 
+AtomicCommitArgs HwcDisplay::CreateModesetCommit(
+    const HwcDisplayConfig *config,
+    const std::optional<LayerData> &modeset_layer) {
+  AtomicCommitArgs args{};
+
+  args.color_matrix = color_matrix_;
+  args.content_type = content_type_;
+  args.colorspace = colorspace_;
+
+  std::vector<LayerData> composition_layers;
+  if (modeset_layer) {
+    composition_layers.emplace_back(modeset_layer.value());
+  }
+
+  if (composition_layers.empty()) {
+    ALOGW("Attempting to create a modeset commit without a layer.");
+  }
+
+  args.display_mode = config->mode;
+  args.active = true;
+  args.composition = DrmKmsPlan::CreateDrmKmsPlan(GetPipe(),
+                                                  std::move(
+                                                      composition_layers));
+  ALOGW_IF(!args.composition, "No composition for blocking modeset");
+
+  return args;
+}
+
 HWC2::Error HwcDisplay::CreateComposition(AtomicCommitArgs &a_args) {
   if (IsInHeadlessMode()) {
     ALOGE("%s: Display is in headless mode, should never reach here", __func__);
@@ -462,22 +677,29 @@
   }
 
   a_args.color_matrix = color_matrix_;
+  a_args.content_type = content_type_;
+  a_args.colorspace = colorspace_;
 
   uint32_t prev_vperiod_ns = 0;
   GetDisplayVsyncPeriod(&prev_vperiod_ns);
 
   auto mode_update_commited_ = false;
-  if (staged_mode_ &&
+  if (staged_mode_config_id_ &&
       staged_mode_change_time_ <= ResourceManager::GetTimeMonotonicNs()) {
+    const HwcDisplayConfig *staged_config = GetConfig(
+        staged_mode_config_id_.value());
+    if (staged_config == nullptr) {
+      return HWC2::Error::BadConfig;
+    }
     client_layer_.SetLayerDisplayFrame(
         (hwc_rect_t){.left = 0,
                      .top = 0,
-                     .right = int(staged_mode_->GetRawMode().hdisplay),
-                     .bottom = int(staged_mode_->GetRawMode().vdisplay)});
+                     .right = int(staged_config->mode.GetRawMode().hdisplay),
+                     .bottom = int(staged_config->mode.GetRawMode().vdisplay)});
 
-    configs_.active_config_id = staged_mode_config_id_;
+    configs_.active_config_id = staged_mode_config_id_.value();
 
-    a_args.display_mode = *staged_mode_;
+    a_args.display_mode = staged_config->mode;
     if (!a_args.test_only) {
       mode_update_commited_ = true;
     }
@@ -542,9 +764,7 @@
   }
 
   if (!current_plan_) {
-    if (!a_args.test_only) {
-      ALOGE("Failed to create DrmKmsPlan");
-    }
+    ALOGE_IF(!a_args.test_only, "Failed to create DrmKmsPlan");
     return HWC2::Error::BadConfig;
   }
 
@@ -553,13 +773,12 @@
   auto ret = GetPipe().atomic_state_manager->ExecuteAtomicCommit(a_args);
 
   if (ret) {
-    if (!a_args.test_only)
-      ALOGE("Failed to apply the frame composition ret=%d", ret);
+    ALOGE_IF(!a_args.test_only, "Failed to apply the frame composition ret=%d", ret);
     return HWC2::Error::BadParameter;
   }
 
   if (mode_update_commited_) {
-    staged_mode_.reset();
+    staged_mode_config_id_.reset();
     vsync_tracking_en_ = false;
     if (last_vsync_ts_ != 0) {
       hwc_->SendVsyncPeriodTimingChangedEventToClient(handle_,
@@ -604,6 +823,7 @@
   color_matrix_ = {};
 
   ++frame_no_;
+
   return HWC2::Error::None;
 }
 
@@ -614,7 +834,6 @@
     return HWC2::Error::BadConfig;
   }
 
-  staged_mode_ = configs_.hwc_configs[config].mode;
   staged_mode_change_time_ = change_time;
   staged_mode_config_id_ = config;
 
@@ -671,11 +890,35 @@
 }
 
 HWC2::Error HwcDisplay::SetColorMode(int32_t mode) {
-  if (mode < HAL_COLOR_MODE_NATIVE || mode > HAL_COLOR_MODE_BT2100_HLG)
+  /* Maps to the Colorspace DRM connector property:
+   * https://elixir.bootlin.com/linux/v6.11/source/include/drm/drm_connector.h#L538
+   */
+  if (mode < HAL_COLOR_MODE_NATIVE || mode > HAL_COLOR_MODE_DISPLAY_P3)
     return HWC2::Error::BadParameter;
 
-  if (mode != HAL_COLOR_MODE_NATIVE)
-    return HWC2::Error::Unsupported;
+  switch (mode) {
+    case HAL_COLOR_MODE_NATIVE:
+      colorspace_ = Colorspace::kDefault;
+      break;
+    case HAL_COLOR_MODE_STANDARD_BT601_625:
+    case HAL_COLOR_MODE_STANDARD_BT601_625_UNADJUSTED:
+    case HAL_COLOR_MODE_STANDARD_BT601_525:
+    case HAL_COLOR_MODE_STANDARD_BT601_525_UNADJUSTED:
+      // The DP spec does not say whether this is the 525 or the 625 line version.
+      colorspace_ = Colorspace::kBt601Ycc;
+      break;
+    case HAL_COLOR_MODE_STANDARD_BT709:
+    case HAL_COLOR_MODE_SRGB:
+      colorspace_ = Colorspace::kBt709Ycc;
+      break;
+    case HAL_COLOR_MODE_DCI_P3:
+    case HAL_COLOR_MODE_DISPLAY_P3:
+      colorspace_ = Colorspace::kDciP3RgbD65;
+      break;
+    case HAL_COLOR_MODE_ADOBE_RGB:
+    default:
+      return HWC2::Error::Unsupported;
+  }
 
   color_mode_ = mode;
   return HWC2::Error::None;
@@ -683,6 +926,15 @@
 
 #include <xf86drmMode.h>
 
+static uint64_t To3132FixPt(float in) {
+  constexpr uint64_t kSignMask = (1ULL << 63);
+  constexpr uint64_t kValueMask = ~(1ULL << 63);
+  constexpr auto kValueScale = static_cast<float>(1ULL << 32);
+  if (in < 0)
+    return (static_cast<uint64_t>(-in * kValueScale) & kValueMask) | kSignMask;
+  return static_cast<uint64_t>(in * kValueScale) & kValueMask;
+}
+
 HWC2::Error HwcDisplay::SetColorTransform(const float *matrix, int32_t hint) {
   if (hint < HAL_COLOR_TRANSFORM_IDENTITY ||
       hint > HAL_COLOR_TRANSFORM_CORRECT_TRITANOPIA)
@@ -701,17 +953,40 @@
 
   switch (color_transform_hint_) {
     case HAL_COLOR_TRANSFORM_IDENTITY:
-      SetColorMarixToIdentity();
+      SetColorMatrixToIdentity();
       break;
     case HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX:
+      // Without HW support, we cannot correctly process matrices with an offset.
+      for (int i = 12; i < 14; i++) {
+        if (matrix[i] != 0.F)
+          return HWC2::Error::Unsupported;
+      }
+
+      /* HAL provides a 4x4 float type matrix:
+       * | 0  1  2  3|
+       * | 4  5  6  7|
+       * | 8  9 10 11|
+       * |12 13 14 15|
+       *
+       * R_out = R*0 + G*4 + B*8 + 12
+       * G_out = R*1 + G*5 + B*9 + 13
+       * B_out = R*2 + G*6 + B*10 + 14
+       *
+       * DRM expects a 3x3 s31.32 fixed point matrix:
+       * out   matrix    in
+       * |R|   |0 1 2|   |R|
+       * |G| = |3 4 5| x |G|
+       * |B|   |6 7 8|   |B|
+       *
+       * R_out = R*0 + G*1 + B*2
+       * G_out = R*3 + G*4 + B*5
+       * B_out = R*6 + G*7 + B*8
+       */
       color_matrix_ = std::make_shared<drm_color_ctm>();
-      /* DRM expects a 3x3 matrix, but the HAL provides a 4x4 matrix. */
       for (int i = 0; i < kCtmCols; i++) {
         for (int j = 0; j < kCtmRows; j++) {
           constexpr int kInCtmRows = 4;
-          /* HAL matrix type is float, but DRM expects a s31.32 fix point */
-          auto value = uint64_t(matrix[i * kInCtmRows + j] * float(1ULL << 32));
-          color_matrix_->matrix[i * kCtmRows + j] = value;
+          color_matrix_->matrix[i * kCtmRows + j] = To3132FixPt(matrix[j * kInCtmRows + i]);
         }
       }
       break;
@@ -916,12 +1191,13 @@
 }
 
 HWC2::Error HwcDisplay::SetContentType(int32_t contentType) {
-  if (contentType != HWC2_CONTENT_TYPE_NONE)
-    return HWC2::Error::Unsupported;
-
-  /* TODO: Map to the DRM Connector property:
-   * https://elixir.bootlin.com/linux/v5.4-rc5/source/drivers/gpu/drm/drm_connector.c#L809
+  /* Maps exactly to the content_type DRM connector property:
+   * https://elixir.bootlin.com/linux/v6.11/source/include/uapi/drm/drm_mode.h#L107
    */
+  if (contentType < HWC2_CONTENT_TYPE_NONE || contentType > HWC2_CONTENT_TYPE_GAME)
+    return HWC2::Error::BadParameter;
+
+  content_type_ = contentType;
 
   return HWC2::Error::None;
 }
@@ -1016,16 +1292,12 @@
       intent > HAL_RENDER_INTENT_TONE_MAP_ENHANCE)
     return HWC2::Error::BadParameter;
 
-  if (mode < HAL_COLOR_MODE_NATIVE || mode > HAL_COLOR_MODE_BT2100_HLG)
-    return HWC2::Error::BadParameter;
-
-  if (mode != HAL_COLOR_MODE_NATIVE)
-    return HWC2::Error::Unsupported;
-
   if (intent != HAL_RENDER_INTENT_COLORIMETRIC)
     return HWC2::Error::Unsupported;
 
-  color_mode_ = mode;
+  auto err = SetColorMode(mode);
+  if (err != HWC2::Error::None) return err;
+
   return HWC2::Error::None;
 }
 
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index 87d2da7..ecca514 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -23,6 +23,7 @@
 #include <sstream>
 
 #include "HwcDisplayConfigs.h"
+#include "compositor/DisplayInfo.h"
 #include "compositor/FlatteningController.h"
 #include "compositor/LayerData.h"
 #include "drm/DrmAtomicStateManager.h"
@@ -40,6 +41,13 @@
 // NOLINTNEXTLINE
 class HwcDisplay {
  public:
+  enum ConfigError {
+    kNone,
+    kBadConfig,
+    kSeamlessNotAllowed,
+    kSeamlessNotPossible
+  };
+
   HwcDisplay(hwc2_display_t handle, HWC2::DisplayType type, DrmHwc *hwc);
   HwcDisplay(const HwcDisplay &) = delete;
   ~HwcDisplay();
@@ -58,7 +66,27 @@
     return configs_;
   }
 
-  // HWC Hooks
+  // Get the config representing the mode that has been committed to KMS.
+  auto GetCurrentConfig() const -> const HwcDisplayConfig *;
+
+  // Get the config that was last requested through SetActiveConfig and similar
+  // functions. This may differ from the GetCurrentConfig if the config change
+  // is queued up to take effect in the future.
+  auto GetLastRequestedConfig() const -> const HwcDisplayConfig *;
+
+  // Set a config synchronously. If the requested config fails to be committed,
+  // this will return with an error. Otherwise, the config will have been
+  // committed to the kernel on successful return.
+  ConfigError SetConfig(hwc2_config_t config);
+
+  // Queue a configuration change to take effect in the future.
+  auto QueueConfig(hwc2_config_t config, int64_t desired_time, bool seamless,
+                   QueuedConfigTiming *out_timing) -> ConfigError;
+
+  // Get the HwcDisplayConfig, or nullptor if none.
+  auto GetConfig(hwc2_config_t config_id) const -> const HwcDisplayConfig *;
+
+  // HWC2 Hooks - these should not be used outside of the hwc2 device.
   HWC2::Error AcceptDisplayChanges();
   HWC2::Error CreateLayer(hwc2_layer_t *layer);
   HWC2::Error DestroyLayer(hwc2_layer_t layer);
@@ -197,16 +225,21 @@
     virtual_disp_height_ = height;
   }
 
+  auto getDisplayPhysicalOrientation() -> std::optional<PanelOrientation>;
+
  private:
+  AtomicCommitArgs CreateModesetCommit(
+      const HwcDisplayConfig *config,
+      const std::optional<LayerData> &modeset_layer);
+
   HwcDisplayConfigs configs_;
 
   DrmHwc *const hwc_;
 
   SharedFd present_fence_;
 
-  std::optional<DrmMode> staged_mode_;
   int64_t staged_mode_change_time_{};
-  uint32_t staged_mode_config_id_{};
+  std::optional<uint32_t> staged_mode_config_id_{};
 
   std::shared_ptr<DrmDisplayPipeline> pipeline_;
 
@@ -233,6 +266,8 @@
   static constexpr int kCtmCols = 3;
   std::shared_ptr<drm_color_ctm> color_matrix_;
   android_color_transform_t color_transform_hint_{};
+  int32_t content_type_{};
+  Colorspace colorspace_{};
 
   std::shared_ptr<DrmKmsPlan> current_plan_;
 
@@ -241,7 +276,7 @@
   Stats prev_stats_;
   std::string DumpDelta(HwcDisplay::Stats delta);
 
-  void SetColorMarixToIdentity();
+  void SetColorMatrixToIdentity();
 
   HWC2::Error Init();
 
diff --git a/hwc2_device/HwcDisplayConfigs.cpp b/hwc2_device/HwcDisplayConfigs.cpp
index 3645356..fa1d2a9 100644
--- a/hwc2_device/HwcDisplayConfigs.cpp
+++ b/hwc2_device/HwcDisplayConfigs.cpp
@@ -23,6 +23,7 @@
 
 #include "drm/DrmConnector.h"
 #include "utils/log.h"
+#include "utils/properties.h"
 
 constexpr uint32_t kHeadlessModeDisplayWidthMm = 163;
 constexpr uint32_t kHeadlessModeDisplayHeightMm = 122;
@@ -113,17 +114,20 @@
 
   auto first_config_id = last_config_id;
   uint32_t last_group_id = 1;
+  const bool use_config_groups = Properties::UseConfigGroups();
 
   /* Group modes */
   for (const auto &mode : connector.GetModes()) {
     /* Find group for the new mode or create new group */
     uint32_t group_found = 0;
-    for (auto &hwc_config : hwc_configs) {
-      if (mode.GetRawMode().hdisplay ==
-              hwc_config.second.mode.GetRawMode().hdisplay &&
-          mode.GetRawMode().vdisplay ==
-              hwc_config.second.mode.GetRawMode().vdisplay) {
-        group_found = hwc_config.second.group_id;
+    if (use_config_groups) {
+      for (auto &hwc_config : hwc_configs) {
+        if (mode.GetRawMode().hdisplay ==
+                hwc_config.second.mode.GetRawMode().hdisplay &&
+            mode.GetRawMode().vdisplay ==
+                hwc_config.second.mode.GetRawMode().vdisplay) {
+          group_found = hwc_config.second.group_id;
+        }
       }
     }
     if (group_found == 0) {
diff --git a/hwc2_device/HwcLayer.cpp b/hwc2_device/HwcLayer.cpp
index da4ce7c..cb18fdd 100644
--- a/hwc2_device/HwcLayer.cpp
+++ b/hwc2_device/HwcLayer.cpp
@@ -24,6 +24,42 @@
 
 namespace android {
 
+void HwcLayer::SetLayerProperties(const LayerProperties& layer_properties) {
+  if (layer_properties.buffer) {
+    layer_data_.acquire_fence = layer_properties.buffer->acquire_fence;
+    buffer_handle_ = layer_properties.buffer->buffer_handle;
+    buffer_handle_updated_ = true;
+  }
+  if (layer_properties.blend_mode) {
+    blend_mode_ = layer_properties.blend_mode.value();
+  }
+  if (layer_properties.color_space) {
+    color_space_ = layer_properties.color_space.value();
+  }
+  if (layer_properties.sample_range) {
+    sample_range_ = layer_properties.sample_range.value();
+  }
+  if (layer_properties.composition_type) {
+    sf_type_ = layer_properties.composition_type.value();
+  }
+  if (layer_properties.display_frame) {
+    layer_data_.pi.display_frame = layer_properties.display_frame.value();
+  }
+  if (layer_properties.alpha) {
+    layer_data_.pi.alpha = std::lround(layer_properties.alpha.value() *
+                                       UINT16_MAX);
+  }
+  if (layer_properties.source_crop) {
+    layer_data_.pi.source_crop = layer_properties.source_crop.value();
+  }
+  if (layer_properties.transform) {
+    layer_data_.pi.transform = layer_properties.transform.value();
+  }
+  if (layer_properties.z_order) {
+    z_order_ = layer_properties.z_order.value();
+  }
+}
+
 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
 HWC2::Error HwcLayer::SetCursorPosition(int32_t /*x*/, int32_t /*y*/) {
   return HWC2::Error::None;
diff --git a/hwc2_device/HwcLayer.h b/hwc2_device/HwcLayer.h
index b69ce5b..e1d62b7 100644
--- a/hwc2_device/HwcLayer.h
+++ b/hwc2_device/HwcLayer.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <aidl/android/hardware/graphics/common/Transform.h>
 #include <hardware/hwcomposer2.h>
 
 #include "bufferinfo/BufferInfoGetter.h"
@@ -27,6 +28,24 @@
 
 class HwcLayer {
  public:
+  struct Buffer {
+    buffer_handle_t buffer_handle;
+    SharedFd acquire_fence;
+  };
+  // A set of properties to be validated.
+  struct LayerProperties {
+    std::optional<Buffer> buffer;
+    std::optional<BufferBlendMode> blend_mode;
+    std::optional<BufferColorSpace> color_space;
+    std::optional<BufferSampleRange> sample_range;
+    std::optional<HWC2::Composition> composition_type;
+    std::optional<hwc_rect_t> display_frame;
+    std::optional<float> alpha;
+    std::optional<hwc_frect_t> source_crop;
+    std::optional<LayerTransform> transform;
+    std::optional<uint32_t> z_order;
+  };
+
   explicit HwcLayer(HwcDisplay *parent_display) : parent_(parent_display){};
 
   HWC2::Composition GetSfType() const {
@@ -61,7 +80,9 @@
     return layer_data_;
   }
 
-  // Layer hooks
+  void SetLayerProperties(const LayerProperties &layer_properties);
+
+  // HWC2 Layer hooks
   HWC2::Error SetCursorPosition(int32_t /*x*/, int32_t /*y*/);
   HWC2::Error SetLayerBlendMode(int32_t mode);
   HWC2::Error SetLayerBuffer(buffer_handle_t buffer, int32_t acquire_fence);
diff --git a/hwc3/CommandResultWriter.h b/hwc3/CommandResultWriter.h
index fa96f33..aae6817 100644
--- a/hwc3/CommandResultWriter.h
+++ b/hwc3/CommandResultWriter.h
@@ -17,6 +17,8 @@
 #include <unordered_map>
 #include <vector>
 
+#include <android-base/unique_fd.h>
+
 #include "Utils.h"
 #include "aidl/android/hardware/graphics/composer3/CommandError.h"
 #include "aidl/android/hardware/graphics/composer3/CommandResultPayload.h"
diff --git a/hwc3/Composer.cpp b/hwc3/Composer.cpp
index 4977a14..124380d 100644
--- a/hwc3/Composer.cpp
+++ b/hwc3/Composer.cpp
@@ -25,6 +25,7 @@
 #include "hwc3/ComposerClient.h"
 #include "hwc3/Utils.h"
 #include "utils/log.h"
+#include "utils/properties.h"
 
 namespace aidl::android::hardware::graphics::composer3::impl {
 
@@ -71,6 +72,11 @@
   DEBUG_FUNC();
   /* No capabilities advertised */
   caps->clear();
+
+  if (Properties::IsPresentFenceNotReliable()) {
+    caps->emplace_back(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
+  }
+
   return ndk::ScopedAStatus::ok();
 }
 
diff --git a/hwc3/ComposerClient.cpp b/hwc3/ComposerClient.cpp
index f353abb..b8128f0 100644
--- a/hwc3/ComposerClient.cpp
+++ b/hwc3/ComposerClient.cpp
@@ -19,28 +19,31 @@
 
 #include "ComposerClient.h"
 
-#include <aidlcommonsupport/NativeHandle.h>
-#include <android-base/logging.h>
-#include <android/binder_ibinder_platform.h>
-#include <hardware/hwcomposer2.h>
-
 #include <cinttypes>
 #include <cmath>
 #include <memory>
 #include <unordered_map>
 #include <vector>
 
-#include "aidl/android/hardware/graphics/common/Transform.h"
-#include "aidl/android/hardware/graphics/composer3/ClientTarget.h"
-#include "aidl/android/hardware/graphics/composer3/Composition.h"
-#include "aidl/android/hardware/graphics/composer3/DisplayRequest.h"
-#include "aidl/android/hardware/graphics/composer3/IComposerClient.h"
-#include "aidl/android/hardware/graphics/composer3/PowerMode.h"
-#include "aidl/android/hardware/graphics/composer3/PresentOrValidate.h"
-#include "aidl/android/hardware/graphics/composer3/RenderIntent.h"
-#include "android/binder_auto_utils.h"
-#include "cutils/native_handle.h"
-#include "hardware/hwcomposer_defs.h"
+#include <aidl/android/hardware/graphics/common/Transform.h>
+#include <aidl/android/hardware/graphics/composer3/ClientTarget.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayRequest.h>
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
+#include <aidl/android/hardware/graphics/composer3/Luts.h>
+#include <aidl/android/hardware/graphics/composer3/PowerMode.h>
+#include <aidl/android/hardware/graphics/composer3/PresentOrValidate.h>
+#include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_ibinder_platform.h>
+#include <cutils/native_handle.h>
+#include <hardware/hwcomposer2.h>
+#include <hardware/hwcomposer_defs.h>
+
+#include "bufferinfo/BufferInfo.h"
+#include "compositor/DisplayInfo.h"
 #include "hwc2_device/HwcDisplay.h"
 #include "hwc2_device/HwcDisplayConfigs.h"
 #include "hwc2_device/HwcLayer.h"
@@ -48,7 +51,10 @@
 #include "hwc3/Utils.h"
 
 using ::android::HwcDisplay;
+using ::android::HwcDisplayConfig;
 using ::android::HwcDisplayConfigs;
+using ::android::HwcLayer;
+using ::android::LayerTransform;
 
 #include "utils/log.h"
 
@@ -64,6 +70,239 @@
 };
 // clang-format on
 
+std::optional<BufferBlendMode> AidlToBlendMode(
+    const std::optional<ParcelableBlendMode>& aidl_blend_mode) {
+  if (!aidl_blend_mode) {
+    return std::nullopt;
+  }
+
+  switch (aidl_blend_mode->blendMode) {
+    case common::BlendMode::NONE:
+      return BufferBlendMode::kNone;
+    case common::BlendMode::PREMULTIPLIED:
+      return BufferBlendMode::kPreMult;
+    case common::BlendMode::COVERAGE:
+      return BufferBlendMode::kCoverage;
+    case common::BlendMode::INVALID:
+      ALOGE("Invalid BlendMode");
+      return std::nullopt;
+  }
+}
+
+std::optional<BufferColorSpace> AidlToColorSpace(
+    const std::optional<ParcelableDataspace>& dataspace) {
+  if (!dataspace) {
+    return std::nullopt;
+  }
+
+  int32_t standard = static_cast<int32_t>(dataspace->dataspace) &
+                     static_cast<int32_t>(common::Dataspace::STANDARD_MASK);
+  switch (standard) {
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT709):
+      return BufferColorSpace::kItuRec709;
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT601_625):
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT601_625_UNADJUSTED):
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT601_525):
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT601_525_UNADJUSTED):
+      return BufferColorSpace::kItuRec601;
+    case static_cast<int32_t>(common::Dataspace::STANDARD_BT2020):
+    case static_cast<int32_t>(
+        common::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE):
+      return BufferColorSpace::kItuRec2020;
+    case static_cast<int32_t>(common::Dataspace::UNKNOWN):
+      return BufferColorSpace::kUndefined;
+    default:
+      ALOGE("Unsupported standard: %d", standard);
+      return std::nullopt;
+  }
+}
+
+std::optional<BufferSampleRange> AidlToSampleRange(
+    const std::optional<ParcelableDataspace>& dataspace) {
+  if (!dataspace) {
+    return std::nullopt;
+  }
+
+  int32_t sample_range = static_cast<int32_t>(dataspace->dataspace) &
+                         static_cast<int32_t>(common::Dataspace::RANGE_MASK);
+  switch (sample_range) {
+    case static_cast<int32_t>(common::Dataspace::RANGE_FULL):
+      return BufferSampleRange::kFullRange;
+    case static_cast<int32_t>(common::Dataspace::RANGE_LIMITED):
+      return BufferSampleRange::kLimitedRange;
+    case static_cast<int32_t>(common::Dataspace::UNKNOWN):
+      return BufferSampleRange::kUndefined;
+    default:
+      ALOGE("Unsupported sample range: %d", sample_range);
+      return std::nullopt;
+  }
+}
+
+bool IsSupportedCompositionType(
+    const std::optional<ParcelableComposition> composition) {
+  if (!composition) {
+    return true;
+  }
+  switch (composition->composition) {
+    case Composition::INVALID:
+    case Composition::CLIENT:
+    case Composition::DEVICE:
+    case Composition::SOLID_COLOR:
+    case Composition::CURSOR:
+      return true;
+
+    // Unsupported composition types. Set an error for the current
+    // DisplayCommand and return.
+    case Composition::DISPLAY_DECORATION:
+    case Composition::SIDEBAND:
+#if __ANDROID_API__ >= 34
+    case Composition::REFRESH_RATE_INDICATOR:
+#endif
+      return false;
+  }
+}
+
+bool ValidateLayerBrightness(const std::optional<LayerBrightness>& brightness) {
+  if (!brightness) {
+    return true;
+  }
+  return !(std::signbit(brightness->brightness) ||
+           std::isnan(brightness->brightness));
+}
+
+std::optional<HWC2::Composition> AidlToCompositionType(
+    const std::optional<ParcelableComposition> composition) {
+  if (!composition) {
+    return std::nullopt;
+  }
+
+  switch (composition->composition) {
+    case Composition::INVALID:
+      return HWC2::Composition::Invalid;
+    case Composition::CLIENT:
+      return HWC2::Composition::Client;
+    case Composition::DEVICE:
+      return HWC2::Composition::Device;
+    case Composition::SOLID_COLOR:
+      return HWC2::Composition::SolidColor;
+    case Composition::CURSOR:
+      return HWC2::Composition::Cursor;
+
+    // Unsupported composition types.
+    case Composition::DISPLAY_DECORATION:
+    case Composition::SIDEBAND:
+#if __ANDROID_API__ >= 34
+    case Composition::REFRESH_RATE_INDICATOR:
+#endif
+      ALOGE("Unsupported composition type: %s",
+            toString(composition->composition).c_str());
+      return std::nullopt;
+  }
+}
+
+#if __ANDROID_API__ < 35
+
+class DisplayConfiguration {
+ public:
+  class Dpi {
+   public:
+    float x = 0.000000F;
+    float y = 0.000000F;
+  };
+  // NOLINTNEXTLINE(readability-identifier-naming)
+  int32_t configId = 0;
+  int32_t width = 0;
+  int32_t height = 0;
+  std::optional<Dpi> dpi;
+  // NOLINTNEXTLINE(readability-identifier-naming)
+  int32_t configGroup = 0;
+  // NOLINTNEXTLINE(readability-identifier-naming)
+  int32_t vsyncPeriod = 0;
+};
+
+#endif
+
+DisplayConfiguration HwcDisplayConfigToAidlConfiguration(
+    const HwcDisplayConfigs& configs, const HwcDisplayConfig& config) {
+  DisplayConfiguration aidl_configuration =
+      {.configId = static_cast<int32_t>(config.id),
+       .width = config.mode.GetRawMode().hdisplay,
+       .height = config.mode.GetRawMode().vdisplay,
+       .configGroup = static_cast<int32_t>(config.group_id),
+       .vsyncPeriod = config.mode.GetVSyncPeriodNs()};
+
+  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.
+    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};
+  }
+  // TODO: Populate vrrConfig.
+  return aidl_configuration;
+}
+
+std::optional<hwc_rect> AidlToRect(const std::optional<common::Rect>& rect) {
+  if (!rect) {
+    return std::nullopt;
+  }
+  return hwc_rect{rect->left, rect->top, rect->right, rect->bottom};
+}
+
+std::optional<hwc_frect> AidlToFRect(const std::optional<common::FRect>& rect) {
+  if (!rect) {
+    return std::nullopt;
+  }
+  return hwc_frect{rect->left, rect->top, rect->right, rect->bottom};
+}
+
+std::optional<float> AidlToAlpha(const std::optional<PlaneAlpha>& alpha) {
+  if (!alpha) {
+    return std::nullopt;
+  }
+  return alpha->alpha;
+}
+
+std::optional<uint32_t> AidlToZOrder(const std::optional<ZOrder>& z_order) {
+  if (!z_order) {
+    return std::nullopt;
+  }
+  return z_order->z;
+}
+
+std::optional<LayerTransform> AidlToLayerTransform(
+    const std::optional<ParcelableTransform>& aidl_transform) {
+  if (!aidl_transform) {
+    return std::nullopt;
+  }
+
+  uint32_t transform = LayerTransform::kIdentity;
+  // 270* and 180* cannot be combined with flips. More specifically, they
+  // already contain both horizontal and vertical flips, so those fields are
+  // redundant in this case. 90* rotation can be combined with either horizontal
+  // flip or vertical flip, so treat it differently
+  if (aidl_transform->transform == common::Transform::ROT_270) {
+    transform = LayerTransform::kRotate270;
+  } else if (aidl_transform->transform == common::Transform::ROT_180) {
+    transform = LayerTransform::kRotate180;
+  } else {
+    auto aidl_transform_bits = static_cast<uint32_t>(aidl_transform->transform);
+    if ((aidl_transform_bits &
+         static_cast<uint32_t>(common::Transform::FLIP_H)) != 0)
+      transform |= LayerTransform::kFlipH;
+    if ((aidl_transform_bits &
+         static_cast<uint32_t>(common::Transform::FLIP_V)) != 0)
+      transform |= LayerTransform::kFlipV;
+    if ((aidl_transform_bits &
+         static_cast<uint32_t>(common::Transform::ROT_90)) != 0)
+      transform |= LayerTransform::kRotate90;
+  }
+  return static_cast<LayerTransform>(transform);
+}
+
 }  // namespace
 
 ComposerClient::ComposerClient() {
@@ -202,9 +441,15 @@
     return Hwc2toHwc3Error(hwc2_error);
   }
 
+  hwc3::Error error = Hwc2toHwc3Error(
+      display.GetChangedCompositionTypes(&num_types, nullptr, nullptr));
+  if (error != hwc3::Error::kNone) {
+    return error;
+  }
+
   std::vector<hwc2_layer_t> hwc_changed_layers(num_types);
   std::vector<int32_t> hwc_composition_types(num_types);
-  hwc3::Error error = Hwc2toHwc3Error(
+  error = Hwc2toHwc3Error(
       display.GetChangedCompositionTypes(&num_types, hwc_changed_layers.data(),
                                          hwc_composition_types.data()));
   if (error != hwc3::Error::kNone) {
@@ -300,38 +545,45 @@
     return;
   }
 
-  HwcLayerWrapper layer_wrapper{command.layer, layer};
+  // If the requested composition type is not supported, the HWC should return
+  // an error and not process any further commands.
+  if (!IsSupportedCompositionType(command.composition)) {
+    cmd_result_writer_->AddError(hwc3::Error::kUnsupported);
+    return;
+  }
+
+  // For some invalid parameters, the HWC should return an error and not process
+  // any further commands.
+  if (!ValidateLayerBrightness(command.brightness)) {
+    cmd_result_writer_->AddError(hwc3::Error::kBadParameter);
+    return;
+  }
+
+  HwcLayer::LayerProperties properties;
   if (command.buffer) {
-    ExecuteSetLayerBuffer(display_id, layer_wrapper, *command.buffer);
+    HwcLayer::Buffer buffer;
+    auto err = ImportLayerBuffer(display_id, command.layer, *command.buffer,
+                                 &buffer.buffer_handle);
+    if (err != hwc3::Error::kNone) {
+      cmd_result_writer_->AddError(err);
+      return;
+    }
+    buffer.acquire_fence = ::android::MakeSharedFd(
+        command.buffer->fence.dup().release());
+    properties.buffer.emplace(buffer);
   }
-  if (command.blendMode) {
-    ExecuteSetLayerBlendMode(display_id, layer_wrapper, *command.blendMode);
-  }
-  if (command.composition) {
-    ExecuteSetLayerComposition(display_id, layer_wrapper, *command.composition);
-  }
-  if (command.dataspace) {
-    ExecuteSetLayerDataspace(display_id, layer_wrapper, *command.dataspace);
-  }
-  if (command.displayFrame) {
-    ExecuteSetLayerDisplayFrame(display_id, layer_wrapper,
-                                *command.displayFrame);
-  }
-  if (command.planeAlpha) {
-    ExecuteSetLayerPlaneAlpha(display_id, layer_wrapper, *command.planeAlpha);
-  }
-  if (command.sourceCrop) {
-    ExecuteSetLayerSourceCrop(display_id, layer_wrapper, *command.sourceCrop);
-  }
-  if (command.transform) {
-    ExecuteSetLayerTransform(display_id, layer_wrapper, *command.transform);
-  }
-  if (command.z) {
-    ExecuteSetLayerZOrder(display_id, layer_wrapper, *command.z);
-  }
-  if (command.brightness) {
-    ExecuteSetLayerBrightness(display_id, layer_wrapper, *command.brightness);
-  }
+
+  properties.blend_mode = AidlToBlendMode(command.blendMode);
+  properties.color_space = AidlToColorSpace(command.dataspace);
+  properties.sample_range = AidlToSampleRange(command.dataspace);
+  properties.composition_type = AidlToCompositionType(command.composition);
+  properties.display_frame = AidlToRect(command.displayFrame);
+  properties.alpha = AidlToAlpha(command.planeAlpha);
+  properties.source_crop = AidlToFRect(command.sourceCrop);
+  properties.transform = AidlToLayerTransform(command.transform);
+  properties.z_order = AidlToZOrder(command.z);
+
+  layer->SetLayerProperties(properties);
 
   // Some unsupported functionality returns kUnsupported, and others
   // are just a no-op.
@@ -340,6 +592,9 @@
   if (command.sidebandStream) {
     cmd_result_writer_->AddError(hwc3::Error::kUnsupported);
   }
+  if (command.luts) {
+    cmd_result_writer_->AddError(hwc3::Error::kUnsupported);
+  }
   // TODO: Blocking region handling missing.
   // TODO: Layer surface damage.
   // TODO: Layer visible region.
@@ -356,13 +611,16 @@
     return;
   }
 
+  if (command.brightness) {
+    // TODO: Implement support for display brightness.
+    cmd_result_writer_->AddError(hwc3::Error::kUnsupported);
+    return;
+  }
+
   for (const auto& layer_cmd : command.layers) {
     DispatchLayerCommand(command.display, layer_cmd);
   }
 
-  if (command.brightness) {
-    ExecuteSetDisplayBrightness(command.display, *command.brightness);
-  }
   if (command.colorTransformMatrix) {
     ExecuteSetDisplayColorTransform(command.display,
                                     *command.colorTransformMatrix);
@@ -405,7 +663,7 @@
 }
 
 ndk::ScopedAStatus ComposerClient::getActiveConfig(int64_t display_id,
-                                                   int32_t* config) {
+                                                   int32_t* config_id) {
   DEBUG_FUNC();
   const std::unique_lock lock(hwc_->GetResMan().GetMainLock());
   HwcDisplay* display = GetDisplay(display_id);
@@ -413,13 +671,12 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  uint32_t hwc2_config = 0;
-  const hwc3::Error error = Hwc2toHwc3Error(
-      display->GetActiveConfig(&hwc2_config));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
+  const HwcDisplayConfig* config = display->GetLastRequestedConfig();
+  if (config == nullptr) {
+    return ToBinderStatus(hwc3::Error::kBadConfig);
   }
-  *config = Hwc2ConfigIdToHwc3(hwc2_config);
+
+  *config_id = Hwc2ConfigIdToHwc3(config->id);
   return ndk::ScopedAStatus::ok();
 }
 
@@ -467,7 +724,7 @@
 }
 
 ndk::ScopedAStatus ComposerClient::getDisplayAttribute(
-    int64_t display_id, int32_t config, DisplayAttribute attribute,
+    int64_t display_id, int32_t config_id, DisplayAttribute attribute,
     int32_t* value) {
   DEBUG_FUNC();
   const std::unique_lock lock(hwc_->GetResMan().GetMainLock());
@@ -476,11 +733,46 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  const hwc3::Error error = Hwc2toHwc3Error(
-      display->GetDisplayAttribute(Hwc3ConfigIdToHwc2(config),
-                                   Hwc3DisplayAttributeToHwc2(attribute),
-                                   value));
-  return ToBinderStatus(error);
+  const HwcDisplayConfigs& configs = display->GetDisplayConfigs();
+  auto config = configs.hwc_configs.find(config_id);
+  if (config == configs.hwc_configs.end()) {
+    return ToBinderStatus(hwc3::Error::kBadConfig);
+  }
+
+  DisplayConfiguration
+      aidl_configuration = HwcDisplayConfigToAidlConfiguration(configs,
+                                                               config->second);
+  // Legacy API for querying DPI uses units of dots per 1000 inches.
+  static const int kLegacyDpiUnit = 1000;
+  switch (attribute) {
+    case DisplayAttribute::WIDTH:
+      *value = aidl_configuration.width;
+      break;
+    case DisplayAttribute::HEIGHT:
+      *value = aidl_configuration.height;
+      break;
+    case DisplayAttribute::VSYNC_PERIOD:
+      *value = aidl_configuration.vsyncPeriod;
+      break;
+    case DisplayAttribute::DPI_X:
+      *value = aidl_configuration.dpi
+                   ? static_cast<int>(aidl_configuration.dpi->x *
+                                      kLegacyDpiUnit)
+                   : -1;
+      break;
+    case DisplayAttribute::DPI_Y:
+      *value = aidl_configuration.dpi
+                   ? static_cast<int>(aidl_configuration.dpi->y *
+                                      kLegacyDpiUnit)
+                   : -1;
+      break;
+    case DisplayAttribute::CONFIG_GROUP:
+      *value = aidl_configuration.configGroup;
+      break;
+    case DisplayAttribute::INVALID:
+      return ToBinderStatus(hwc3::Error::kUnsupported);
+  }
+  return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus ComposerClient::getDisplayCapabilities(
@@ -514,7 +806,7 @@
 }
 
 ndk::ScopedAStatus ComposerClient::getDisplayConfigs(
-    int64_t display_id, std::vector<int32_t>* configs) {
+    int64_t display_id, std::vector<int32_t>* out_configs) {
   DEBUG_FUNC();
   const std::unique_lock lock(hwc_->GetResMan().GetMainLock());
   HwcDisplay* display = GetDisplay(display_id);
@@ -522,23 +814,9 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  uint32_t num_configs = 0;
-  hwc3::Error error = Hwc2toHwc3Error(
-      display->LegacyGetDisplayConfigs(&num_configs, nullptr));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
-  }
-
-  std::vector<hwc2_config_t> out_configs(num_configs);
-  error = Hwc2toHwc3Error(
-      display->LegacyGetDisplayConfigs(&num_configs, out_configs.data()));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
-  }
-
-  configs->reserve(num_configs);
-  for (const auto config : out_configs) {
-    configs->emplace_back(Hwc2ConfigIdToHwc3(config));
+  const HwcDisplayConfigs& configs = display->GetDisplayConfigs();
+  for (const auto& [id, config] : configs.hwc_configs) {
+    out_configs->push_back(static_cast<int32_t>(id));
   }
   return ndk::ScopedAStatus::ok();
 }
@@ -621,14 +899,15 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  uint32_t hwc2_vsync_period = 0;
-  auto error = Hwc2toHwc3Error(
-      display->GetDisplayVsyncPeriod(&hwc2_vsync_period));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
+  // getDisplayVsyncPeriod should return the vsync period of the config that
+  // is currently committed to the kernel. If a config change is pending due to
+  // setActiveConfigWithConstraints, return the pre-change vsync period.
+  const HwcDisplayConfig* config = display->GetCurrentConfig();
+  if (config == nullptr) {
+    return ToBinderStatus(hwc3::Error::kBadConfig);
   }
 
-  *vsync_period = static_cast<int32_t>(hwc2_vsync_period);
+  *vsync_period = config->mode.GetVSyncPeriodNs();
   return ndk::ScopedAStatus::ok();
 }
 
@@ -648,13 +927,40 @@
 ndk::ScopedAStatus ComposerClient::getDisplayPhysicalOrientation(
     int64_t display_id, common::Transform* orientation) {
   DEBUG_FUNC();
+
+  if (orientation == nullptr) {
+    ALOGE("Invalid 'orientation' pointer.");
+    return ToBinderStatus(hwc3::Error::kBadParameter);
+  }
+
   const std::unique_lock lock(hwc_->GetResMan().GetMainLock());
   HwcDisplay* display = GetDisplay(display_id);
   if (display == nullptr) {
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  *orientation = common::Transform::NONE;
+  PanelOrientation
+      drm_orientation = display->getDisplayPhysicalOrientation().value_or(
+          PanelOrientation::kModePanelOrientationNormal);
+
+  switch (drm_orientation) {
+    case PanelOrientation::kModePanelOrientationNormal:
+      *orientation = common::Transform::NONE;
+      break;
+    case PanelOrientation::kModePanelOrientationBottomUp:
+      *orientation = common::Transform::ROT_180;
+      break;
+    case PanelOrientation::kModePanelOrientationLeftUp:
+      *orientation = common::Transform::ROT_270;
+      break;
+    case PanelOrientation::kModePanelOrientationRightUp:
+      *orientation = common::Transform::ROT_90;
+      break;
+    default:
+      ALOGE("Unknown panel orientation value: %d", drm_orientation);
+      return ToBinderStatus(hwc3::Error::kBadDisplay);
+  }
+
   return ndk::ScopedAStatus::ok();
 }
 
@@ -738,24 +1044,8 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  uint32_t out_num_supported_types = 0;
-  auto error = Hwc2toHwc3Error(
-      display->GetSupportedContentTypes(&out_num_supported_types, nullptr));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
-  }
-
-  std::vector<uint32_t> out_supported_types(out_num_supported_types);
-  error = Hwc2toHwc3Error(
-      display->GetSupportedContentTypes(&out_num_supported_types,
-                                        out_supported_types.data()));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
-  }
-
-  for (const auto type : out_supported_types) {
-    types->push_back(Hwc2ContentTypeToHwc3(type));
-  }
+  // Support for ContentType is not implemented.
+  types->clear();
   return ndk::ScopedAStatus::ok();
 }
 
@@ -778,13 +1068,14 @@
 ndk::ScopedAStatus ComposerClient::setActiveConfig(int64_t display_id,
                                                    int32_t config) {
   DEBUG_FUNC();
-  const std::unique_lock lock(hwc_->GetResMan().GetMainLock());
-  HwcDisplay* display = GetDisplay(display_id);
-  if (display == nullptr) {
-    return ToBinderStatus(hwc3::Error::kBadDisplay);
-  }
 
-  return ToBinderStatus(Hwc2toHwc3Error(display->SetActiveConfig(config)));
+  VsyncPeriodChangeTimeline timeline;
+  VsyncPeriodChangeConstraints constraints = {
+      .desiredTimeNanos = ::android::ResourceManager::GetTimeMonotonicNs(),
+      .seamlessRequired = false,
+  };
+  return setActiveConfigWithConstraints(display_id, config, constraints,
+                                        &timeline);
 }
 
 ndk::ScopedAStatus ComposerClient::setActiveConfigWithConstraints(
@@ -798,23 +1089,47 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  hwc_vsync_period_change_constraints_t hwc2_constraints;
-  hwc2_constraints.desiredTimeNanos = constraints.desiredTimeNanos;
-  hwc2_constraints.seamlessRequired = static_cast<uint8_t>(
-      constraints.seamlessRequired);
-
-  hwc_vsync_period_change_timeline_t hwc2_timeline{};
-  auto error = Hwc2toHwc3Error(
-      display->SetActiveConfigWithConstraints(config, &hwc2_constraints,
-                                              &hwc2_timeline));
-  if (error != hwc3::Error::kNone) {
-    return ToBinderStatus(error);
+  if (constraints.seamlessRequired) {
+    return ToBinderStatus(hwc3::Error::kSeamlessNotAllowed);
   }
 
-  timeline->refreshTimeNanos = hwc2_timeline.refreshTimeNanos;
-  timeline->newVsyncAppliedTimeNanos = hwc2_timeline.newVsyncAppliedTimeNanos;
-  timeline->refreshRequired = static_cast<bool>(hwc2_timeline.refreshRequired);
-  return ndk::ScopedAStatus::ok();
+  const bool future_config = constraints.desiredTimeNanos >
+                             ::android::ResourceManager::GetTimeMonotonicNs();
+  const HwcDisplayConfig* current_config = display->GetCurrentConfig();
+  const HwcDisplayConfig* next_config = display->GetConfig(config);
+  const bool same_config_group = current_config != nullptr &&
+                                 next_config != nullptr &&
+                                 current_config->group_id ==
+                                     next_config->group_id;
+  // If the contraints dictate that this is to be applied in the future, it
+  // must be queued. If the new config is in the same config group as the
+  // current one, then queue it to reduce jank.
+  HwcDisplay::ConfigError result{};
+  if (future_config || same_config_group) {
+    QueuedConfigTiming timing = {};
+    result = display->QueueConfig(config, constraints.desiredTimeNanos,
+                                  constraints.seamlessRequired, &timing);
+    timeline->newVsyncAppliedTimeNanos = timing.new_vsync_time_ns;
+    timeline->refreshTimeNanos = timing.refresh_time_ns;
+    timeline->refreshRequired = true;
+  } else {
+    // Fall back to a blocking commit, which may modeset.
+    result = display->SetConfig(config);
+    timeline->newVsyncAppliedTimeNanos = ::android::ResourceManager::
+        GetTimeMonotonicNs();
+    timeline->refreshRequired = false;
+  }
+
+  switch (result) {
+    case HwcDisplay::ConfigError::kBadConfig:
+      return ToBinderStatus(hwc3::Error::kBadConfig);
+    case HwcDisplay::ConfigError::kSeamlessNotAllowed:
+      return ToBinderStatus(hwc3::Error::kSeamlessNotAllowed);
+    case HwcDisplay::ConfigError::kSeamlessNotPossible:
+      return ToBinderStatus(hwc3::Error::kSeamlessNotPossible);
+    case HwcDisplay::ConfigError::kNone:
+      return ndk::ScopedAStatus::ok();
+  }
 }
 
 ndk::ScopedAStatus ComposerClient::setBootDisplayConfig(int64_t /*display_id*/,
@@ -879,8 +1194,10 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  auto error = display->SetContentType(Hwc3ContentTypeToHwc2(type));
-  return ToBinderStatus(Hwc2toHwc3Error(error));
+  if (type == ContentType::NONE) {
+    return ndk::ScopedAStatus::ok();
+  }
+  return ToBinderStatus(hwc3::Error::kUnsupported);
 }
 
 ndk::ScopedAStatus ComposerClient::setDisplayedContentSamplingEnabled(
@@ -933,6 +1250,8 @@
   return ToBinderStatus(hwc3::Error::kUnsupported);
 }
 
+#if __ANDROID_API__ >= 34
+
 ndk::ScopedAStatus ComposerClient::getOverlaySupport(
     OverlayProperties* /*out_overlay_properties*/) {
   return ToBinderStatus(hwc3::Error::kUnsupported);
@@ -954,6 +1273,10 @@
   return ToBinderStatus(hwc3::Error::kUnsupported);
 }
 
+#endif
+
+#if __ANDROID_API__ >= 35
+
 ndk::ScopedAStatus ComposerClient::getDisplayConfigurations(
     int64_t display_id, int32_t /*max_frame_interval_ns*/,
     std::vector<DisplayConfiguration>* configurations) {
@@ -966,28 +1289,8 @@
 
   const HwcDisplayConfigs& configs = display->GetDisplayConfigs();
   for (const auto& [id, config] : configs.hwc_configs) {
-    static const int kNanosecondsPerSecond = 1E9;
-    configurations->emplace_back(
-        DisplayConfiguration{.configId = static_cast<int32_t>(config.id),
-                             .width = config.mode.GetRawMode().hdisplay,
-                             .height = config.mode.GetRawMode().vdisplay,
-                             .configGroup = static_cast<int32_t>(
-                                 config.group_id),
-                             .vsyncPeriod = static_cast<int>(kNanosecondsPerSecond * double(
-                                 1 / config.mode.GetVRefresh()))});
-
-    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.
-      static const float kMmPerInch = 25.4;
-      float dpi = float(config.mode.GetRawMode().hdisplay) * kMmPerInch /
-                  float(configs.mm_width);
-      configurations->back().dpi = {.x = dpi, .y = dpi};
-    }
-
-    // TODO: Populate vrrConfig.
+    configurations->push_back(
+        HwcDisplayConfigToAidlConfiguration(configs, config));
   }
   return ndk::ScopedAStatus::ok();
 }
@@ -998,6 +1301,22 @@
   return ToBinderStatus(hwc3::Error::kUnsupported);
 }
 
+ndk::ScopedAStatus ComposerClient::startHdcpNegotiation(
+    int64_t /*display*/, const AidlHdcpLevels& /*levels*/) {
+  return ToBinderStatus(hwc3::Error::kUnsupported);
+}
+
+#endif
+
+ndk::ScopedAStatus ComposerClient::getMaxLayerPictureProfiles(int64_t, int32_t*) {
+  return ToBinderStatus(hwc3::Error::kUnsupported);
+}
+
+ndk::ScopedAStatus ComposerClient::getLuts(int64_t, const std::vector<Buffer>&,
+    std::vector<Luts>*) {
+  return ToBinderStatus(hwc3::Error::kUnsupported);
+}
+
 std::string ComposerClient::Dump() {
   uint32_t size = 0;
   hwc_->Dump(&size, nullptr);
@@ -1013,139 +1332,18 @@
   return binder;
 }
 
-void ComposerClient::ExecuteSetLayerBuffer(int64_t display_id,
-                                           HwcLayerWrapper& layer,
-                                           const Buffer& buffer) {
-  buffer_handle_t imported_buffer = nullptr;
+hwc3::Error ComposerClient::ImportLayerBuffer(
+    int64_t display_id, int64_t layer_id, const Buffer& buffer,
+    buffer_handle_t* out_imported_buffer) {
+  *out_imported_buffer = nullptr;
 
   auto releaser = composer_resources_->CreateResourceReleaser(true);
-  auto err = composer_resources_->GetLayerBuffer(display_id, layer.layer_id,
-                                                 buffer, &imported_buffer,
+  auto err = composer_resources_->GetLayerBuffer(display_id, layer_id, buffer,
+                                                 out_imported_buffer,
                                                  releaser.get());
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-    return;
-  }
-
-  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
-  auto fence_fd = const_cast<ndk::ScopedFileDescriptor&>(buffer.fence)
-                      .release();
-  err = Hwc2toHwc3Error(layer.layer->SetLayerBuffer(imported_buffer, fence_fd));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
+  return err;
 }
 
-void ComposerClient::ExecuteSetLayerBlendMode(
-    int64_t /*display_id*/, HwcLayerWrapper& layer,
-    const ParcelableBlendMode& blend_mode) {
-  auto err = Hwc2toHwc3Error(layer.layer->SetLayerBlendMode(
-      Hwc3BlendModeToHwc2(blend_mode.blendMode)));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-
-void ComposerClient::ExecuteSetLayerComposition(
-    int64_t /*display_id*/, HwcLayerWrapper& layer,
-    const ParcelableComposition& composition) {
-  hwc3::Error error = hwc3::Error::kNone;
-  switch (composition.composition) {
-      // Unsupported composition types should set an error for the current
-      // DisplayCommand.
-    case Composition::DISPLAY_DECORATION:
-    case Composition::SIDEBAND:
-      error = hwc3::Error::kUnsupported;
-      break;
-    default:
-      error = Hwc2toHwc3Error(layer.layer->SetLayerCompositionType(
-          Hwc3CompositionToHwc2(composition.composition)));
-  }
-  if (error != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(error);
-  }
-}
-
-void ComposerClient::ExecuteSetLayerDataspace(
-    int64_t /*display_id*/, HwcLayerWrapper& layer,
-    const ParcelableDataspace& dataspace) {
-  auto err = Hwc2toHwc3Error(
-      layer.layer->SetLayerDataspace(Hwc3DataspaceToHwc2(dataspace.dataspace)));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-
-void ComposerClient::ExecuteSetLayerDisplayFrame(int64_t /*display_id*/,
-                                                 HwcLayerWrapper& layer,
-                                                 const common::Rect& rect) {
-  const hwc_rect_t hwc2_rect{rect.left, rect.top, rect.right, rect.bottom};
-  auto err = Hwc2toHwc3Error(layer.layer->SetLayerDisplayFrame(hwc2_rect));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-void ComposerClient::ExecuteSetLayerPlaneAlpha(int64_t /*display_id*/,
-                                               HwcLayerWrapper& layer,
-                                               const PlaneAlpha& plane_alpha) {
-  auto err = Hwc2toHwc3Error(
-      layer.layer->SetLayerPlaneAlpha(plane_alpha.alpha));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-
-void ComposerClient::ExecuteSetLayerSourceCrop(
-    int64_t /*display_id*/, HwcLayerWrapper& layer,
-    const common::FRect& source_crop) {
-  const hwc_frect_t rect{source_crop.left, source_crop.top, source_crop.right,
-                         source_crop.bottom};
-  auto err = Hwc2toHwc3Error(layer.layer->SetLayerSourceCrop(rect));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-void ComposerClient::ExecuteSetLayerTransform(
-    int64_t /*display_id*/, HwcLayerWrapper& layer,
-    const ParcelableTransform& transform) {
-  auto err = Hwc2toHwc3Error(
-      layer.layer->SetLayerTransform(Hwc3TransformToHwc2(transform.transform)));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-void ComposerClient::ExecuteSetLayerZOrder(int64_t /*display_id*/,
-                                           HwcLayerWrapper& layer,
-                                           const ZOrder& z_order) {
-  auto err = Hwc2toHwc3Error(layer.layer->SetLayerZOrder(z_order.z));
-  if (err != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(err);
-  }
-}
-
-void ComposerClient::ExecuteSetLayerBrightness(
-    int64_t /*display_id*/, HwcLayerWrapper& /*layer*/,
-    const LayerBrightness& brightness) {
-  if (std::signbit(brightness.brightness) ||
-      std::isnan(brightness.brightness)) {
-    cmd_result_writer_->AddError(hwc3::Error::kBadParameter);
-  }
-}
-
-void ComposerClient::ExecuteSetDisplayBrightness(
-    uint64_t display_id, const DisplayBrightness& command) {
-  auto* display = GetDisplay(display_id);
-  if (display == nullptr) {
-    cmd_result_writer_->AddError(hwc3::Error::kBadDisplay);
-    return;
-  }
-
-  auto error = Hwc2toHwc3Error(
-      display->SetDisplayBrightness(command.brightness));
-  if (error != hwc3::Error::kNone) {
-    cmd_result_writer_->AddError(error);
-  }
-}
 void ComposerClient::ExecuteSetDisplayColorTransform(
     uint64_t display_id, const std::vector<float>& matrix) {
   auto* display = GetDisplay(display_id);
diff --git a/hwc3/ComposerClient.h b/hwc3/ComposerClient.h
index f6362ad..2595203 100644
--- a/hwc3/ComposerClient.h
+++ b/hwc3/ComposerClient.h
@@ -25,6 +25,7 @@
 #include "hwc3/Utils.h"
 #include "utils/Mutex.h"
 
+using AidlHdcpLevels = aidl::android::hardware::drm::HdcpLevels;
 using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
 using AidlNativeHandle = aidl::android::hardware::common::NativeHandle;
 
@@ -39,11 +40,6 @@
 
 class DrmHwcThree;
 
-struct HwcLayerWrapper {
-  int64_t layer_id;
-  ::android::HwcLayer* layer;
-};
-
 class ComposerClient : public BnComposerClient {
  public:
   ComposerClient();
@@ -136,6 +132,9 @@
   ndk::ScopedAStatus setVsyncEnabled(int64_t display, bool enabled) override;
   ndk::ScopedAStatus setIdleTimerEnabled(int64_t display,
                                          int32_t timeout) override;
+
+#if __ANDROID_API__ >= 34
+
   ndk::ScopedAStatus getOverlaySupport(
       OverlayProperties* out_overlay_properties) override;
   ndk::ScopedAStatus getHdrConversionCapabilities(
@@ -145,44 +144,40 @@
       common::Hdr* out_hdr) override;
   ndk::ScopedAStatus setRefreshRateChangedCallbackDebugEnabled(
       int64_t display, bool enabled) override;
+
+#endif
+
+#if __ANDROID_API__ >= 35
+
   ndk::ScopedAStatus getDisplayConfigurations(
       int64_t display, int32_t max_frame_interval_ns,
       std::vector<DisplayConfiguration>* configurations) override;
   ndk::ScopedAStatus notifyExpectedPresent(
       int64_t display, const ClockMonotonicTimestamp& expected_present_time,
       int32_t frame_interval_ns) override;
+  ndk::ScopedAStatus startHdcpNegotiation(int64_t display,
+      const AidlHdcpLevels& levels) override;
+
+#endif
+
+  ndk::ScopedAStatus getMaxLayerPictureProfiles(
+      int64_t display, int32_t* maxProfiles) override;
+  ndk::ScopedAStatus getLuts(int64_t, const std::vector<Buffer>&,
+                             std::vector<Luts>* out_luts) override;
 
  protected:
   ::ndk::SpAIBinder createBinder() override;
 
  private:
+  hwc3::Error ImportLayerBuffer(int64_t display_id, int64_t layer_id,
+                                const Buffer& buffer,
+                                buffer_handle_t* out_imported_buffer);
+
   // Layer commands
   void DispatchLayerCommand(int64_t display_id, const LayerCommand& command);
-  void ExecuteSetLayerBuffer(int64_t display_id, HwcLayerWrapper& layer_id,
-                             const Buffer& buffer);
-  void ExecuteSetLayerBlendMode(int64_t display_id, HwcLayerWrapper& layer,
-                                const ParcelableBlendMode& blend_mode);
-  void ExecuteSetLayerComposition(int64_t display_id, HwcLayerWrapper& layer,
-                                  const ParcelableComposition& composition);
-  void ExecuteSetLayerDataspace(int64_t display_id, HwcLayerWrapper& layer,
-                                const ParcelableDataspace& dataspace);
-  void ExecuteSetLayerDisplayFrame(int64_t display_id, HwcLayerWrapper& layer,
-                                   const common::Rect& rect);
-  void ExecuteSetLayerPlaneAlpha(int64_t display_id, HwcLayerWrapper& layer,
-                                 const PlaneAlpha& plane_alpha);
-  void ExecuteSetLayerSourceCrop(int64_t display_id, HwcLayerWrapper& layer,
-                                 const common::FRect& source_crop);
-  void ExecuteSetLayerTransform(int64_t display_id, HwcLayerWrapper& layer,
-                                const ParcelableTransform& transform);
-  void ExecuteSetLayerZOrder(int64_t display_id, HwcLayerWrapper& layer,
-                             const ZOrder& z_order);
-  void ExecuteSetLayerBrightness(int64_t display_id, HwcLayerWrapper& layer,
-                                 const LayerBrightness& brightness);
 
   // Display commands
   void ExecuteDisplayCommand(const DisplayCommand& command);
-  void ExecuteSetDisplayBrightness(uint64_t display_id,
-                                   const DisplayBrightness& command);
   void ExecuteSetDisplayColorTransform(uint64_t display_id,
                                        const std::vector<float>& matrix);
   void ExecuteSetDisplayClientTarget(uint64_t display_id,
diff --git a/hwc3/DrmHwcThree.cpp b/hwc3/DrmHwcThree.cpp
index d758865..fb14bc9 100644
--- a/hwc3/DrmHwcThree.cpp
+++ b/hwc3/DrmHwcThree.cpp
@@ -14,13 +14,17 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "drmhwc"
+
 #include "DrmHwcThree.h"
 
 #include <cinttypes>
 
 #include "Utils.h"
 #include "aidl/android/hardware/graphics/common/Dataspace.h"
+#if __ANDROID_API__ >= 35
 #include "aidl/android/hardware/graphics/common/DisplayHotplugEvent.h"
+#endif
 
 namespace aidl::android::hardware::graphics::composer3::impl {
 
@@ -59,13 +63,38 @@
                               static_cast<int32_t>(vsync_period));
 }
 
-void DrmHwcThree::SendHotplugEventToClient(hwc2_display_t display_id,
-                                           bool connected) {
-  HandleDisplayHotplugEvent(static_cast<uint64_t>(display_id), connected);
-  common::DisplayHotplugEvent event = connected ? common::DisplayHotplugEvent::CONNECTED : common::DisplayHotplugEvent::DISCONNECTED;
+#if __ANDROID_API__ >= 35
+
+void DrmHwcThree::SendHotplugEventToClient(
+    hwc2_display_t display_id, DrmHwc::DisplayStatus display_status) {
+  common::DisplayHotplugEvent event = common::DisplayHotplugEvent::DISCONNECTED;
+  switch (display_status) {
+    case DrmHwc::kDisconnected:
+      event = common::DisplayHotplugEvent::DISCONNECTED;
+      HandleDisplayHotplugEvent(static_cast<uint64_t>(display_id), false);
+      break;
+    case DrmHwc::kConnected:
+      event = common::DisplayHotplugEvent::CONNECTED;
+      HandleDisplayHotplugEvent(static_cast<uint64_t>(display_id), true);
+      break;
+    case DrmHwc::kLinkTrainingFailed:
+      event = common::DisplayHotplugEvent::ERROR_INCOMPATIBLE_CABLE;
+      break;
+  }
   composer_callback_->onHotplugEvent(static_cast<int64_t>(display_id), event);
 }
 
+#else
+
+void DrmHwcThree::SendHotplugEventToClient(
+    hwc2_display_t display_id, DrmHwc::DisplayStatus display_status) {
+  bool connected = display_status != DrmHwc::kDisconnected;
+  HandleDisplayHotplugEvent(static_cast<uint64_t>(display_id), connected);
+  composer_callback_->onHotplug(static_cast<int64_t>(display_id), connected);
+}
+
+#endif
+
 void DrmHwcThree::CleanDisplayResources(uint64_t display_id) {
   DEBUG_FUNC();
   HwcDisplay* display = GetDisplay(display_id);
diff --git a/hwc3/DrmHwcThree.h b/hwc3/DrmHwcThree.h
index 8ba96c6..f020634 100644
--- a/hwc3/DrmHwcThree.h
+++ b/hwc3/DrmHwcThree.h
@@ -39,7 +39,7 @@
       hwc2_display_t display_id, int64_t timestamp) const override;
   void SendRefreshEventToClient(uint64_t display_id) override;
   void SendHotplugEventToClient(hwc2_display_t display_id,
-                                bool connected) override;
+                                DrmHwc::DisplayStatus display_status) override;
 
  private:
   void CleanDisplayResources(uint64_t display_id);
diff --git a/hwc3/Utils.h b/hwc3/Utils.h
index 23d6bdc..b322f5d 100644
--- a/hwc3/Utils.h
+++ b/hwc3/Utils.h
@@ -18,6 +18,7 @@
 
 #include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
 #include <hardware/hwcomposer2.h>
+#include <log/log.h>
 
 #include <cstdint>
 
@@ -93,13 +94,6 @@
   return static_cast<Composition>(composition_type);
 }
 
-inline int32_t Hwc3CompositionToHwc2(Composition composition_type) {
-  if (composition_type > Composition::SIDEBAND) {
-    return HWC2_COMPOSITION_INVALID;
-  }
-  return static_cast<int32_t>(composition_type);
-}
-
 // Values for color modes match across HWC versions, so static cast is safe:
 // https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/graphics/composer/aidl/android/hardware/graphics/composer3/ColorMode.aidl
 // https://cs.android.com/android/platform/superproject/main/+/main:system/core/libsystem/include/system/graphics-base-v1.0.h;drc=7d940ae4afa450696afa25e07982f3a95e17e9b2;l=118
@@ -152,27 +146,6 @@
   return static_cast<int32_t>(render_intent);
 }
 
-// Content type matches, so static_cast is safe.
-// https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/graphics/composer/aidl/android/hardware/graphics/composer3/ContentType.aidl
-// https://cs.android.com/android/platform/superproject/main/+/main:hardware/libhardware/include_all/hardware/hwcomposer2.h;l=350;drc=1a0e4a1698c7b080d6763cef9e16592bce75967e
-inline ContentType Hwc2ContentTypeToHwc3(uint32_t content_type) {
-  if (content_type > HWC2_CONTENT_TYPE_GAME) {
-    ALOGE("Unknown HWC2 content type. Could not translate: %d", content_type);
-    return ContentType::NONE;
-  }
-  return static_cast<ContentType>(content_type);
-}
-inline int32_t Hwc3ContentTypeToHwc2(ContentType content_type) {
-  return static_cast<int32_t>(content_type);
-}
-
-// Values match, so it's safe to do static_cast.
-// https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/graphics/composer/aidl/android/hardware/graphics/composer3/DisplayAttribute.aidl
-// https://cs.android.com/android/platform/superproject/main/+/main:hardware/libhardware/include_all/hardware/hwcomposer2.h;l=58;drc=d783cabd4d9bddb4b83f2dd38300b7598bb58b24
-inline int32_t Hwc3DisplayAttributeToHwc2(DisplayAttribute display_attribute) {
-  return static_cast<int32_t>(display_attribute);
-}
-
 // Values match up to DOZE_SUSPEND.
 // https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/graphics/composer/aidl/android/hardware/graphics/composer3/PowerMode.aidl
 // https://cs.android.com/android/platform/superproject/main/+/main:hardware/libhardware/include_all/hardware/hwcomposer2.h;l=348;drc=d783cabd4d9bddb4b83f2dd38300b7598bb58b24
@@ -184,13 +157,6 @@
   return static_cast<int32_t>(power_mode);
 }
 
-// Values match, so static_cast is okay.
-// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/BlendMode.aidl;drc=bab1ba54ede32520a5042d616a3af46ad4f55d5f;l=25
-// https://cs.android.com/android/platform/superproject/main/+/main:hardware/libhardware/include_all/hardware/hwcomposer2.h;l=72;drc=1a0e4a1698c7b080d6763cef9e16592bce75967e
-inline int32_t Hwc3BlendModeToHwc2(common::BlendMode blend_mode) {
-  return static_cast<int32_t>(blend_mode);
-}
-
 // Values appear to match.
 // https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Dataspace.aidl
 // https://cs.android.com/android/platform/superproject/main/+/main:system/core/libsystem/include/system/graphics-base-v1.0.h;l=43
@@ -199,11 +165,4 @@
   return static_cast<int32_t>(dataspace);
 }
 
-// Values match, so static_cast is okay.
-// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Transform.aidl
-// https://cs.android.com/android/platform/superproject/main/+/main:system/core/libsystem/include/system/graphics-base-v1.0.h;l=41
-inline int32_t Hwc3TransformToHwc2(common::Transform transform) {
-  return static_cast<int32_t>(transform);
-}
-
 };  // namespace aidl::android::hardware::graphics::composer3
\ No newline at end of file
diff --git a/hwc3/hwc3-apex-file-contexts b/hwc3/hwc3-apex-file-contexts
new file mode 100644
index 0000000..5c8de15
--- /dev/null
+++ b/hwc3/hwc3-apex-file-contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                   u:object_r:vendor_file:s0
+/etc(/.*)?                                               u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.composer\.hwc3-service\.drm   u:object_r:hal_graphics_composer_default_exec:s0
\ No newline at end of file
diff --git a/hwc3/hwc3-apex-manifest.json b/hwc3/hwc3-apex-manifest.json
new file mode 100644
index 0000000..fa9e626
--- /dev/null
+++ b/hwc3/hwc3-apex-manifest.json
@@ -0,0 +1,5 @@
+{
+    "name": "com.android.hardware.graphics.composer",
+    "version": 1,
+    "vendorBootstrap": true
+}
\ No newline at end of file
diff --git a/hwc3/hwc3-drm.xml b/hwc3/hwc3-drm.xml
index 911f7f8..7debcf9 100644
--- a/hwc3/hwc3-drm.xml
+++ b/hwc3/hwc3-drm.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.graphics.composer3</name>
-        <version>3</version>
+        <version>4</version>
         <interface>
             <name>IComposer</name>
             <instance>default</instance>
diff --git a/hwc3/meson.build b/hwc3/meson.build
index 2486212..291c71a 100644
--- a/hwc3/meson.build
+++ b/hwc3/meson.build
@@ -14,7 +14,7 @@
     cpp_args : common_cpp_flags + hwc2_cpp_flags,
     dependencies : deps,
     install : true,
-    link_with: [drmhwc_common, drmhwc_hwc2_common],
+    link_whole: [drmhwc_common, drmhwc_hwc2_common],
     install_dir : get_option('bindir') / 'hw',
     include_directories: inc_include,
 )
diff --git a/hwc3/service.cpp b/hwc3/service.cpp
index 920260a..b545a67 100644
--- a/hwc3/service.cpp
+++ b/hwc3/service.cpp
@@ -19,7 +19,6 @@
 
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
-#include <binder/ProcessState.h>
 #include <sched.h>
 
 #include "Composer.h"
@@ -47,9 +46,14 @@
   const std::string instance = std::string() + Composer::descriptor +
                                "/default";
   ALOGI("HWC3 service name %s", instance.c_str());
+#if __ANDROID_API__ >= 34
   auto status = AServiceManager_addServiceWithFlags(
       composer->asBinder().get(), instance.c_str(),
       AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED);
+#else
+  auto status = AServiceManager_addService(composer->asBinder().get(),
+                                           instance.c_str());
+#endif
   if (status != STATUS_OK) {
     ALOGE("Failed to register service. Error %d", (int)status);
     return -EINVAL;
diff --git a/meson.build b/meson.build
index e9a86ec..8cfbbc8 100644
--- a/meson.build
+++ b/meson.build
@@ -16,6 +16,7 @@
     'backend/Backend.cpp',
     'backend/BackendClient.cpp',
     'utils/fd.cpp',
+    'utils/properties.cpp',
 )
 
 srcs_hwc2_device = [
diff --git a/utils/properties.cpp b/utils/properties.cpp
new file mode 100644
index 0000000..a855c94
--- /dev/null
+++ b/utils/properties.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "properties.h"
+
+/**
+ * @brief Determine if the "Present Not Reliable" property is enabled.
+ *
+ * @return boolean
+ */
+auto Properties::IsPresentFenceNotReliable() -> bool {
+  return (property_get_bool("ro.vendor.hwc.drm.present_fence_not_reliable",
+                            0) != 0);
+}
+
+auto Properties::UseConfigGroups() -> bool {
+  return (property_get_bool("ro.vendor.hwc.drm.use_config_groups", 1) != 0);
+}
diff --git a/utils/properties.h b/utils/properties.h
index e400236..15c2fb2 100644
--- a/utils/properties.h
+++ b/utils/properties.h
@@ -39,4 +39,43 @@
   return static_cast<int>(strlen(value));
 }
 
+/**
+ * Bluntly copied from system/core/libcutils/properties.cpp,
+ * which is part of the Android Project and licensed under Apache 2.
+ * Source:
+ * https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/properties.cpp;l=27
+ */
+auto inline property_get_bool(const char *key, int8_t default_value) -> int8_t {
+  if (!key)
+    return default_value;
+
+  int8_t result = default_value;
+  char buf[PROPERTY_VALUE_MAX] = {};
+
+  int len = property_get(key, buf, "");
+  if (len == 1) {
+    char ch = buf[0];
+    if (ch == '0' || ch == 'n') {
+      result = false;
+    } else if (ch == '1' || ch == 'y') {
+      result = true;
+    }
+  } else if (len > 1) {
+    if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
+      result = false;
+    } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") ||
+               !strcmp(buf, "on")) {
+      result = true;
+    }
+  }
+
+  return result;
+}
+
 #endif
+
+class Properties {
+ public:
+  static auto IsPresentFenceNotReliable() -> bool;
+  static auto UseConfigGroups() -> bool;
+};