drm_hwcomposer: Add headless mode support

Headless mode required to keep SurfaceFlinger alive when all displays are
disconnected, Without headless mode Android will continuously crash.
Only single internal (primary) display is required to be in HEADLESS mode
to prevent the crash. See [1].

[1]: https://source.android.com/devices/graphics/hotplug#handling-common-scenarios
Closes: https://gitlab.freedesktop.org/drm-hwcomposer/drm-hwcomposer/-/issues/57
Signed-off-by: Roman Stratiienko <roman.o.stratiienko@globallogic.com>
diff --git a/hwc2_device/DrmHwcTwo.cpp b/hwc2_device/DrmHwcTwo.cpp
index 42e131a..a2a093f 100644
--- a/hwc2_device/DrmHwcTwo.cpp
+++ b/hwc2_device/DrmHwcTwo.cpp
@@ -165,9 +165,14 @@
 
 void DrmHwcTwo::HandleInitialHotplugState(DrmDevice *drmDevice) {
   for (const auto &conn : drmDevice->connectors()) {
-    if (conn->state() != DRM_MODE_CONNECTED)
+    int display_id = conn->display();
+    auto &display = displays_.at(display_id);
+
+    if (conn->state() != DRM_MODE_CONNECTED && !display.IsInHeadlessMode())
       continue;
-    HandleDisplayHotplug(conn->display(), conn->state());
+    HandleDisplayHotplug(conn->display(), display.IsInHeadlessMode()
+                                              ? DRM_MODE_CONNECTED
+                                              : conn->state());
   }
 }
 
@@ -187,15 +192,15 @@
             conn->display());
 
       int display_id = conn->display();
-      if (cur_state == DRM_MODE_CONNECTED) {
-        auto &display = displays_.at(display_id);
-        display.ChosePreferredConfig();
-      } else {
-        auto &display = displays_.at(display_id);
+      auto &display = displays_.at(display_id);
+      display.ChosePreferredConfig();
+      if (cur_state != DRM_MODE_CONNECTED) {
         display.ClearDisplay();
       }
 
-      HandleDisplayHotplug(display_id, cur_state);
+      HandleDisplayHotplug(display_id, display.IsInHeadlessMode()
+                                           ? DRM_MODE_CONNECTED
+                                           : cur_state);
     }
   }
 }
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index 0ceafed..8936136 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -98,6 +98,11 @@
 }
 
 void HwcDisplay::ClearDisplay() {
+  if (IsInHeadlessMode()) {
+    ALOGE("%s: Headless mode, should never reach here: ", __func__);
+    return;
+  }
+
   AtomicCommitArgs a_args = {.clear_active_composition = true};
   compositor_.ExecuteAtomicCommit(a_args);
 }
@@ -192,7 +197,7 @@
 
 HWC2::Error HwcDisplay::ChosePreferredConfig() {
   HWC2::Error err = configs_.Update(*connector_);
-  if (err != HWC2::Error::None)
+  if (!IsInHeadlessMode() && err != HWC2::Error::None)
     return HWC2::Error::BadDisplay;
 
   return SetActiveConfig(configs_.preferred_config_id);
@@ -230,6 +235,11 @@
 HWC2::Error HwcDisplay::GetChangedCompositionTypes(uint32_t *num_elements,
                                                    hwc2_layer_t *layers,
                                                    int32_t *types) {
+  if (IsInHeadlessMode()) {
+    *num_elements = 0;
+    return HWC2::Error::None;
+  }
+
   uint32_t num_changes = 0;
   for (std::pair<const hwc2_layer_t, HwcLayer> &l : layers_) {
     if (l.second.IsTypeChanged()) {
@@ -250,6 +260,9 @@
                                                int32_t dataspace) {
   std::pair<uint32_t, uint32_t> min = drm_->min_resolution();
   std::pair<uint32_t, uint32_t> max = drm_->max_resolution();
+  if (IsInHeadlessMode()) {
+    return HWC2::Error::None;
+  }
 
   if (width < min.first || height < min.second)
     return HWC2::Error::Unsupported;
@@ -287,8 +300,8 @@
   auto &hwc_config = configs_.hwc_configs[conf];
 
   static const int32_t kUmPerInch = 25400;
-  uint32_t mm_width = connector_->mm_width();
-  uint32_t mm_height = connector_->mm_height();
+  uint32_t mm_width = configs_.mm_width;
+  uint32_t mm_height = configs_.mm_height;
   auto attribute = static_cast<HWC2::Attribute>(attribute_in);
   switch (attribute) {
     case HWC2::Attribute::Width:
@@ -398,6 +411,11 @@
 HWC2::Error HwcDisplay::GetReleaseFences(uint32_t *num_elements,
                                          hwc2_layer_t *layers,
                                          int32_t *fences) {
+  if (IsInHeadlessMode()) {
+    *num_elements = 0;
+    return HWC2::Error::None;
+  }
+
   uint32_t num_layers = 0;
 
   for (std::pair<const hwc2_layer_t, HwcLayer> &l : layers_) {
@@ -418,6 +436,11 @@
 }
 
 HWC2::Error HwcDisplay::CreateComposition(AtomicCommitArgs &a_args) {
+  if (IsInHeadlessMode()) {
+    ALOGE("%s: Display is in headless mode, should never reach here", __func__);
+    return HWC2::Error::None;
+  }
+
   // order the layers by z-order
   bool use_client_layer = false;
   uint32_t client_z_order = UINT32_MAX;
@@ -497,6 +520,10 @@
  * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:hardware/libhardware/include/hardware/hwcomposer2.h;l=1805
  */
 HWC2::Error HwcDisplay::PresentDisplay(int32_t *present_fence) {
+  if (IsInHeadlessMode()) {
+    *present_fence = -1;
+    return HWC2::Error::None;
+  }
   HWC2::Error ret{};
 
   ++total_stats_.total_frames_;
@@ -611,6 +638,10 @@
 }
 
 HWC2::Error HwcDisplay::SetPowerMode(int32_t mode_in) {
+  if (IsInHeadlessMode()) {
+    return HWC2::Error::None;
+  }
+
   auto mode = static_cast<HWC2::PowerMode>(mode_in);
   AtomicCommitArgs a_args{};
 
@@ -652,6 +683,10 @@
 
 HWC2::Error HwcDisplay::ValidateDisplay(uint32_t *num_types,
                                         uint32_t *num_requests) {
+  if (IsInHeadlessMode()) {
+    *num_types = *num_requests = 0;
+    return HWC2::Error::None;
+  }
   return backend_->ValidateDisplay(this, num_types, num_requests);
 }
 
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index 6f46f5d..c3e0f6e 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -200,6 +200,16 @@
     return false;
   }
 
+  /* Headless mode required to keep SurfaceFlinger alive when all display are
+   * disconnected, Without headless mode Android will continuously crash.
+   * Only single internal (primary) display is required to be in HEADLESS mode
+   * to prevent the crash. See:
+   * https://source.android.com/devices/graphics/hotplug#handling-common-scenarios
+   */
+  bool IsInHeadlessMode() {
+    return handle_ == 0 && connector_->state() != DRM_MODE_CONNECTED;
+  }
+
  private:
   enum ClientFlattenningState : int32_t {
     Disabled = -3,
diff --git a/hwc2_device/HwcDisplayConfigs.cpp b/hwc2_device/HwcDisplayConfigs.cpp
index e3d17a2..16f1ed0 100644
--- a/hwc2_device/HwcDisplayConfigs.cpp
+++ b/hwc2_device/HwcDisplayConfigs.cpp
@@ -23,10 +23,38 @@
 #include "drm/DrmConnector.h"
 #include "utils/log.h"
 
+constexpr uint32_t kHeadlessModeDisplayWidthMm = 163;
+constexpr uint32_t kHeadlessModeDisplayHeightMm = 122;
+constexpr uint32_t kHeadlessModeDisplayWidthPx = 1024;
+constexpr uint32_t kHeadlessModeDisplayHeightPx = 768;
+constexpr uint32_t kHeadlessModeDisplayVRefresh = 60;
+
 namespace android {
 
 // NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
 HWC2::Error HwcDisplayConfigs::Update(DrmConnector &connector) {
+  /* In case UpdateModes will fail we will still have one mode for headless
+   * mode*/
+  hwc_configs.clear();
+
+  last_config_id++;
+  preferred_config_id = active_config_id = last_config_id;
+  auto headless_drm_mode_info = (drmModeModeInfo){
+      .hdisplay = kHeadlessModeDisplayWidthPx,
+      .vdisplay = kHeadlessModeDisplayHeightPx,
+      .vrefresh = kHeadlessModeDisplayVRefresh,
+      .name = "HEADLESS-MODE",
+  };
+  hwc_configs[active_config_id] = (HwcDisplayConfig){
+      .id = active_config_id,
+      .group_id = 1,
+      .mode = DrmMode(&headless_drm_mode_info),
+  };
+
+  mm_width = kHeadlessModeDisplayWidthMm;
+  mm_height = kHeadlessModeDisplayHeightMm;
+
+  /* Read real configs */
   int ret = connector.UpdateModes();
   if (ret != 0) {
     ALOGE("Failed to update display modes %d", ret);
@@ -39,6 +67,9 @@
   }
 
   hwc_configs.clear();
+  mm_width = connector.mm_width();
+  mm_height = connector.mm_height();
+
   preferred_config_id = 0;
   int preferred_config_group_id = 0;
 
@@ -159,6 +190,8 @@
     }
   }
 
+  /* Set active mode to be valid mode */
+  active_config_id = preferred_config_id;
   return HWC2::Error::None;
 }
 
diff --git a/hwc2_device/HwcDisplayConfigs.h b/hwc2_device/HwcDisplayConfigs.h
index 7450b8d..5bcf696 100644
--- a/hwc2_device/HwcDisplayConfigs.h
+++ b/hwc2_device/HwcDisplayConfigs.h
@@ -47,6 +47,9 @@
   int preferred_config_id = 0;
 
   int last_config_id = 1;
+
+  uint32_t mm_width = 0;
+  uint32_t mm_height = 0;
 };
 
 }  // namespace android