drm_hwcomposer: Add property hwc.drm.primary_display_order

There are cases when primary display lookup order should be
overriden. This could be achieved with adding system property
hwc.drm.primary_display_order.

Example of primary_display_order property usage:
hwc.drm.primary_display_order=HDMI-A-2,HDMI-A-1,...

This means that first priority goes to HDMI-A-2 connector, then
HDMI-A-1 connector, then everything else. Internal connectors have
higher priority than any other connectors not mentioned in property.

Connected connector with highest priority would be selected as a
primary display.

Signed-off-by: Roman Kovalivskyi <roman.kovalivskyi@globallogic.com>
diff --git a/drm/drmdevice.cpp b/drm/drmdevice.cpp
index 2007fdd..bcb9ddd 100644
--- a/drm/drmdevice.cpp
+++ b/drm/drmdevice.cpp
@@ -30,11 +30,91 @@
 #include <xf86drmMode.h>
 #include <cinttypes>
 
+#include <algorithm>
+#include <array>
+#include <string>
+
 #include <cutils/properties.h>
 #include <log/log.h>
 
+static void trim_left(std::string &str) {
+  str.erase(std::begin(str),
+            std::find_if(std::begin(str), std::end(str),
+                         [](int ch) { return !std::isspace(ch); }));
+}
+
+static void trim_right(std::string &str) {
+  str.erase(std::find_if(std::rbegin(str), std::rend(str),
+                         [](int ch) { return !std::isspace(ch); })
+                .base(),
+            std::end(str));
+}
+
+static void trim(std::string &str) {
+  trim_left(str);
+  trim_right(str);
+}
+
 namespace android {
 
+static std::vector<std::string> read_primary_display_order_prop() {
+  std::array<char, PROPERTY_VALUE_MAX> display_order_buf;
+  property_get("hwc.drm.primary_display_order", display_order_buf.data(),
+               "...");
+
+  std::vector<std::string> display_order;
+  std::istringstream str(display_order_buf.data());
+  for (std::string conn_name = ""; std::getline(str, conn_name, ',');) {
+    trim(conn_name);
+    display_order.push_back(std::move(conn_name));
+  }
+  return display_order;
+}
+
+static std::vector<DrmConnector *> make_primary_display_candidates(
+    std::vector<std::unique_ptr<DrmConnector>> &connectors) {
+  std::vector<DrmConnector *> primary_candidates;
+  std::transform(std::begin(connectors), std::end(connectors),
+                 std::back_inserter(primary_candidates),
+                 [](std::unique_ptr<DrmConnector> &conn) {
+                   return conn.get();
+                 });
+  primary_candidates.erase(std::remove_if(std::begin(primary_candidates),
+                                          std::end(primary_candidates),
+                                          [](const DrmConnector *conn) {
+                                            return conn->state() !=
+                                                   DRM_MODE_CONNECTED;
+                                          }),
+                           std::end(primary_candidates));
+
+  std::vector<std::string> display_order = read_primary_display_order_prop();
+  bool use_other = display_order.back() == "...";
+
+  // putting connectors from primary_display_order first
+  auto curr_connector = std::begin(primary_candidates);
+  for (const std::string &display_name : display_order) {
+    auto it = std::find_if(std::begin(primary_candidates),
+                           std::end(primary_candidates),
+                           [&display_name](const DrmConnector *conn) {
+                             return conn->name() == display_name;
+                           });
+    if (it != std::end(primary_candidates)) {
+      std::iter_swap(it, curr_connector);
+      ++curr_connector;
+    }
+  }
+
+  if (use_other) {
+    // then putting internal connectors second, everything else afterwards
+    std::partition(curr_connector, std::end(primary_candidates),
+                   [](const DrmConnector *conn) { return conn->internal(); });
+  } else {
+    primary_candidates.erase(curr_connector, std::end(primary_candidates));
+  }
+
+  return primary_candidates;
+}
+
 DrmDevice::DrmDevice() : event_listener_(this) {
 }
 
@@ -173,19 +253,26 @@
       connectors_.emplace_back(std::move(conn));
   }
 
-  // First look for primary amongst internal connectors
-  for (auto &conn : connectors_) {
-    if (conn->internal() && !found_primary) {
-      conn->set_display(num_displays);
-      displays_[num_displays] = num_displays;
-      ++num_displays;
-      found_primary = true;
-      break;
-    }
+  // Primary display priority:
+  // 1) hwc.drm.primary_display_order property
+  // 2) internal connectors
+  // 3) anything else
+  std::vector<DrmConnector *>
+      primary_candidates = make_primary_display_candidates(connectors_);
+  if (!primary_candidates.empty() && !found_primary) {
+    DrmConnector &conn = **std::begin(primary_candidates);
+    conn.set_display(num_displays);
+    displays_[num_displays] = num_displays;
+    ++num_displays;
+    found_primary = true;
+  } else {
+    ALOGE(
+        "Failed to find primary display from \"hwc.drm.primary_display_order\" "
+        "property");
   }
 
-  // Then pick first available as primary and for the others assign
-  // consecutive display_numbers.
+  // If no priority display were found then pick first available as primary and
+  // for the others assign consecutive display_numbers.
   for (auto &conn : connectors_) {
     if (conn->external() || conn->internal()) {
       if (!found_primary) {