Injects hinge_angle sensor data using the guest sensor binary.

Bug: 181157794
Test: Use the device state buttons to inject hinge and lid swich data.
Change-Id: I3addaa747fdfcdde75a82403278529eb1e8a13de
diff --git a/guest/commands/rotate/main.cpp b/guest/commands/rotate/main.cpp
deleted file mode 100644
index 8967097..0000000
--- a/guest/commands/rotate/main.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 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 <android-base/chrono_utils.h>
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <utils/StrongPointer.h>
-#include <utils/SystemClock.h>
-
-#include <thread>
-
-#include "android/hardware/sensors/2.0/ISensors.h"
-
-using android::sp;
-using android::hardware::sensors::V1_0::Event;
-using android::hardware::sensors::V1_0::OperationMode;
-using android::hardware::sensors::V1_0::Result;
-using android::hardware::sensors::V1_0::SensorInfo;
-using android::hardware::sensors::V1_0::SensorStatus;
-using android::hardware::sensors::V1_0::SensorType;
-using android::hardware::sensors::V2_0::ISensors;
-
-void InjectOrientation(bool portrait) {
-  const sp<ISensors> sensors = ISensors::getService();
-  if (sensors == nullptr) {
-    LOG(FATAL) << "Unable to get ISensors.";
-  }
-
-  Result result;
-
-  // Place the ISensors HAL into DATA_INJECTION mode so that we can
-  // inject events.
-  result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
-               << toString(result);
-  }
-
-  // Find the first available accelerometer sensor.
-  int accel_handle = -1;
-  const auto& getSensorsList_result =
-      sensors->getSensorsList([&](const auto& list) {
-        for (const SensorInfo& sensor : list) {
-          if (sensor.type == SensorType::ACCELEROMETER) {
-            accel_handle = sensor.sensorHandle;
-            break;
-          }
-        }
-      });
-  if (!getSensorsList_result.isOk()) {
-    LOG(FATAL) << "Unable to get ISensors sensors list: "
-               << getSensorsList_result.description();
-  }
-  if (accel_handle == -1) {
-    LOG(FATAL) << "Unable to find ACCELEROMETER sensor.";
-  }
-
-  // Create a base ISensors accelerometer event.
-  Event event;
-  event.sensorHandle = accel_handle;
-  event.sensorType = SensorType::ACCELEROMETER;
-  if (portrait) {
-    event.u.vec3.x = 0;
-    event.u.vec3.y = 9.2;
-  } else {
-    event.u.vec3.x = 9.2;
-    event.u.vec3.y = 0;
-  }
-  event.u.vec3.z = 3.5;
-  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
-
-  // Repeatedly inject accelerometer events. The WindowManager orientation
-  // listener responds to sustained accelerometer data, not just a single event.
-  android::base::Timer timer;
-  while (timer.duration() < 1s) {
-    event.timestamp = android::elapsedRealtimeNano();
-    result = sensors->injectSensorData(event);
-    if (result != Result::OK) {
-      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
-                 << toString(result);
-    }
-    std::this_thread::sleep_for(10ms);
-  }
-
-  // Return the ISensors HAL back to NORMAL mode.
-  result = sensors->setOperationMode(OperationMode::NORMAL);
-  if (result != Result::OK) {
-    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
-               << toString(result);
-  }
-}
-
-int main(int argc, char** argv) {
-  if (argc == 1) {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  bool portrait = true;
-  if (!strcmp(argv[1], "portrait")) {
-    portrait = true;
-  } else if (!strcmp(argv[1], "landscape")) {
-    portrait = false;
-  } else {
-    LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
-  }
-
-  InjectOrientation(portrait);
-}
diff --git a/guest/commands/rotate/Android.bp b/guest/commands/sensor_injection/Android.bp
similarity index 79%
rename from guest/commands/rotate/Android.bp
rename to guest/commands/sensor_injection/Android.bp
index 6568c0e..a29250a 100644
--- a/guest/commands/rotate/Android.bp
+++ b/guest/commands/sensor_injection/Android.bp
@@ -3,11 +3,11 @@
 }
 
 cc_binary {
-    name: "cuttlefish_rotate",
+    name: "cuttlefish_sensor_injection",
     srcs: ["main.cpp"],
     shared_libs: [
         "[email protected]",
-        "[email protected]",
+        "[email protected]",
         "libbase",
         "libbinder",
         "libhidlbase",
diff --git a/guest/commands/sensor_injection/main.cpp b/guest/commands/sensor_injection/main.cpp
new file mode 100644
index 0000000..6eadb4a
--- /dev/null
+++ b/guest/commands/sensor_injection/main.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 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 <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/StrongPointer.h>
+#include <utils/SystemClock.h>
+
+#include <thread>
+
+#include "android/hardware/sensors/2.1/ISensors.h"
+
+using android::sp;
+using android::hardware::sensors::V1_0::OperationMode;
+using android::hardware::sensors::V1_0::Result;
+using android::hardware::sensors::V1_0::SensorStatus;
+using android::hardware::sensors::V2_1::Event;
+using android::hardware::sensors::V2_1::ISensors;
+using android::hardware::sensors::V2_1::SensorInfo;
+using android::hardware::sensors::V2_1::SensorType;
+
+sp<ISensors> startSensorInjection() {
+  const sp<ISensors> sensors = ISensors::getService();
+  if (sensors == nullptr) {
+    LOG(FATAL) << "Unable to get ISensors.";
+  }
+
+  // Place the ISensors HAL into DATA_INJECTION mode so that we can
+  // inject events.
+  Result result = sensors->setOperationMode(OperationMode::DATA_INJECTION);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set ISensors operation mode to DATA_INJECTION: "
+               << toString(result);
+  }
+
+  return sensors;
+}
+
+int getSensorHandle(SensorType type, const sp<ISensors> sensors) {
+  // Find the first available sensor of the given type.
+  int handle = -1;
+  const auto& getSensorsList_result =
+      sensors->getSensorsList_2_1([&](const auto& list) {
+        for (const SensorInfo& sensor : list) {
+          if (sensor.type == type) {
+            handle = sensor.sensorHandle;
+            break;
+          }
+        }
+      });
+  if (!getSensorsList_result.isOk()) {
+    LOG(FATAL) << "Unable to get ISensors sensors list: "
+               << getSensorsList_result.description();
+  }
+  if (handle == -1) {
+    LOG(FATAL) << "Unable to find sensor.";
+  }
+  return handle;
+}
+
+void endSensorInjection(const sp<ISensors> sensors) {
+  // Return the ISensors HAL back to NORMAL mode.
+  Result result = sensors->setOperationMode(OperationMode::NORMAL);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to set sensors operation mode to NORMAL: "
+               << toString(result);
+  }
+}
+
+// Inject ACCELEROMETER events to corresponding to a given physical
+// device orientation: portrait or landscape.
+void InjectOrientation(bool portrait) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::ACCELEROMETER, sensors);
+
+  // Create a base ISensors accelerometer event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::ACCELEROMETER;
+  if (portrait) {
+    event.u.vec3.x = 0;
+    event.u.vec3.y = 9.2;
+  } else {
+    event.u.vec3.x = 9.2;
+    event.u.vec3.y = 0;
+  }
+  event.u.vec3.z = 3.5;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+
+  // Repeatedly inject accelerometer events. The WindowManager orientation
+  // listener responds to sustained accelerometer data, not just a single event.
+  android::base::Timer timer;
+  Result result;
+  while (timer.duration() < 1s) {
+    event.timestamp = android::elapsedRealtimeNano();
+    result = sensors->injectSensorData_2_1(event);
+    if (result != Result::OK) {
+      LOG(FATAL) << "Unable to inject ISensors accelerometer event: "
+                 << toString(result);
+    }
+    std::this_thread::sleep_for(10ms);
+  }
+
+  endSensorInjection(sensors);
+}
+
+// Inject a single HINGE_ANGLE event at the given angle.
+void InjectHingeAngle(int angle) {
+  sp<ISensors> sensors = startSensorInjection();
+  int handle = getSensorHandle(SensorType::HINGE_ANGLE, sensors);
+
+  // Create a base ISensors hinge_angle event.
+  Event event;
+  event.sensorHandle = handle;
+  event.sensorType = SensorType::HINGE_ANGLE;
+  event.u.scalar = angle;
+  event.u.vec3.status = SensorStatus::ACCURACY_HIGH;
+  event.timestamp = android::elapsedRealtimeNano();
+  Result result = sensors->injectSensorData_2_1(event);
+  if (result != Result::OK) {
+    LOG(FATAL) << "Unable to inject HINGE_ANGLE data: " << toString(result);
+  }
+
+  endSensorInjection(sensors);
+}
+
+int main(int argc, char** argv) {
+  if (argc == 2) {
+    LOG(FATAL) << "Expected command line args 'rotate <portrait|landscape>' or "
+                  "'hinge_angle <value>'";
+  }
+
+  if (!strcmp(argv[1], "rotate")) {
+    bool portrait = true;
+    if (!strcmp(argv[2], "portrait")) {
+      portrait = true;
+    } else if (!strcmp(argv[2], "landscape")) {
+      portrait = false;
+    } else {
+      LOG(FATAL) << "Expected command line arg 'portrait' or 'landscape'";
+    }
+    InjectOrientation(portrait);
+  } else if (!strcmp(argv[1], "hinge_angle")) {
+    int angle = std::stoi(argv[2]);
+    if (angle < 0 || angle > 360) {
+      LOG(FATAL) << "Bad hinge_angle value: " << argv[2];
+    }
+    InjectHingeAngle(angle);
+  } else {
+    LOG(FATAL) << "Unknown arg: " << argv[1];
+  }
+}
diff --git a/host/frontend/webrtc/connection_observer.cpp b/host/frontend/webrtc/connection_observer.cpp
index a8613e6..392539c 100644
--- a/host/frontend/webrtc/connection_observer.cpp
+++ b/host/frontend/webrtc/connection_observer.cpp
@@ -258,9 +258,9 @@
         // invert the lid_switch_open value that is sent to the input device.
         OnSwitchEvent(SW_LID, !evt["lid_switch_open"].asBool());
       }
-      // TODO(b/181157794) Propagate hinge angle sensor data.
       if (evt.isMember("hinge_angle_value")) {
-        LOG(WARNING) << "Hinge angle sensor is not yet implemented.";
+        // TODO(b/181157794) Propagate hinge angle sensor data using a custom
+        // Sensor HAL.
       }
       return;
     }
diff --git a/host/frontend/webrtc_operator/assets/js/app.js b/host/frontend/webrtc_operator/assets/js/app.js
index 859b33b..61a9bf3 100644
--- a/host/frontend/webrtc_operator/assets/js/app.js
+++ b/host/frontend/webrtc_operator/assets/js/app.js
@@ -276,6 +276,13 @@
             createControlPanelButton(button.command, button.title, button.icon_name,
                 getCustomDeviceStateButtonCb(button.device_states),
                 'control-panel-custom-buttons');
+            for (const device_state of button.device_states) {
+              // hinge_angle is currently injected via an adb shell command that
+              // triggers a guest binary.
+              if ('hinge_angle_value' in device_state) {
+                buttons[button.command].adb = true;
+              }
+            }
           } else {
             // This button's command is handled by custom action server.
             createControlPanelButton(button.command, button.title, button.icon_name,
@@ -366,7 +373,7 @@
     initializeAdb();
     if (e.type == 'mousedown') {
       adbShell(
-          '/vendor/bin/cuttlefish_rotate ' +
+          '/vendor/bin/cuttlefish_sensor_injection rotate ' +
           (currentRotation == 0 ? 'landscape' : 'portrait'))
     }
   }
@@ -394,6 +401,13 @@
         };
         deviceConnection.sendControlMessage(JSON.stringify(message));
         console.log(JSON.stringify(message));
+        // TODO(b/181157794): Use a custom Sensor HAL for hinge_angle injection
+        // instead of this guest binary.
+        if ('hinge_angle_value' in states[index]) {
+          adbShell(
+              '/vendor/bin/cuttlefish_sensor_injection hinge_angle ' +
+              states[index].hinge_angle_value);
+        }
         // Cycle to the next state.
         index = (index + 1) % states.length;
       }
diff --git a/shared/config/config_foldable.json b/shared/config/config_foldable.json
index 7a1e731..dd64d17 100644
--- a/shared/config/config_foldable.json
+++ b/shared/config/config_foldable.json
@@ -7,7 +7,8 @@
                 {
                         "device_states": [
                                 {
-                                        "lid_switch_open": false
+                                        "lid_switch_open": false,
+                                        "hinge_angle_value": 0
                                 }
                         ],
                         "button":{
@@ -19,7 +20,8 @@
                 {
                         "device_states": [
                                 {
-                                        "lid_switch_open": true
+                                        "lid_switch_open": true,
+                                        "hinge_angle_value": 180
                                 }
                         ],
                         "button":{
diff --git a/shared/device.mk b/shared/device.mk
index 089c662..123cdd2 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -147,7 +147,7 @@
 #
 PRODUCT_PACKAGES += \
     CuttlefishService \
-    cuttlefish_rotate \
+    cuttlefish_sensor_injection \
     rename_netiface \
     setup_wifi \
     bt_vhci_forwarder \
diff --git a/shared/foldable/device_state_configuration.xml b/shared/foldable/device_state_configuration.xml
index b32eced..9618b11 100644
--- a/shared/foldable/device_state_configuration.xml
+++ b/shared/foldable/device_state_configuration.xml
@@ -9,13 +9,21 @@
     </conditions>
   </device-state>
   <device-state>
-    <!-- TODO(b/181583265): Use the hinge sensor for HALF_OPENED state.
-      Currently using an identifier of 3 so that this state never takes
-      precedence over the lower-identifier-value CLOSED and OPENED states,
-      until a hinge sensor is available. -->
-    <identifier>3</identifier>
+    <identifier>1</identifier>
     <name>HALF_OPENED</name>
-    <conditions></conditions>
+    <conditions>
+      <lid-switch>
+        <open>true</open>
+      </lid-switch>
+      <sensor>
+        <type>android.sensor.hinge_angle</type>
+        <name>Hinge Angle Sensor</name>
+        <value>
+          <min>0</min>
+          <max>180</max>
+        </value>
+      </sensor>
+    </conditions>
   </device-state>
   <device-state>
     <identifier>2</identifier>
diff --git a/shared/sepolicy/vendor/cuttlefish_rotate.te b/shared/sepolicy/vendor/cuttlefish_rotate.te
deleted file mode 100644
index 7d8a9a1..0000000
--- a/shared/sepolicy/vendor/cuttlefish_rotate.te
+++ /dev/null
@@ -1,15 +0,0 @@
-type cuttlefish_rotate, domain;
-type cuttlefish_rotate_exec, exec_type, vendor_file_type, file_type;
-
-# Switch to cuttlefish_rotate domain when executing from shell.
-domain_auto_trans(shell, cuttlefish_rotate_exec, cuttlefish_rotate)
-allow cuttlefish_rotate shell:fd use;
-
-# Allow cuttlefish_rotate to communicate over adb connection.
-allow cuttlefish_rotate adbd:fd use;
-allow cuttlefish_rotate adbd:unix_stream_socket { read write };
-# Needed to run the binary directly via adb socket.
-allow cuttlefish_rotate devpts:chr_file { read write };
-
-# Grant cuttlefish_rotate access to the ISensors HAL.
-hal_client_domain(cuttlefish_rotate, hal_sensors)
diff --git a/shared/sepolicy/vendor/cuttlefish_sensor_injection.te b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
new file mode 100644
index 0000000..9e7aca5
--- /dev/null
+++ b/shared/sepolicy/vendor/cuttlefish_sensor_injection.te
@@ -0,0 +1,15 @@
+type cuttlefish_sensor_injection, domain;
+type cuttlefish_sensor_injection_exec, exec_type, vendor_file_type, file_type;
+
+# Switch to cuttlefish_sensor_injection domain when executing from shell.
+domain_auto_trans(shell, cuttlefish_sensor_injection_exec, cuttlefish_sensor_injection)
+allow cuttlefish_sensor_injection shell:fd use;
+
+# Allow cuttlefish_sensor_injection to communicate over adb connection.
+allow cuttlefish_sensor_injection adbd:fd use;
+allow cuttlefish_sensor_injection adbd:unix_stream_socket { read write };
+# Needed to run the binary directly via adb socket.
+allow cuttlefish_sensor_injection devpts:chr_file { read write };
+
+# Grant cuttlefish_sensor_injection access to the ISensors HAL.
+hal_client_domain(cuttlefish_sensor_injection, hal_sensors)
diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts
index 20c3341..f41d86e 100644
--- a/shared/sepolicy/vendor/file_contexts
+++ b/shared/sepolicy/vendor/file_contexts
@@ -55,7 +55,7 @@
 #############################
 # Vendor files
 #
-/vendor/bin/cuttlefish_rotate   u:object_r:cuttlefish_rotate_exec:s0
+/vendor/bin/cuttlefish_sensor_injection   u:object_r:cuttlefish_sensor_injection_exec:s0
 /vendor/bin/socket_vsock_proxy  u:object_r:socket_vsock_proxy_exec:s0
 /vendor/bin/vsoc_input_service  u:object_r:vsoc_input_service_exec:s0
 /vendor/bin/rename_netiface  u:object_r:rename_netiface_exec:s0
diff --git a/shared/sepolicy/vendor/shell.te b/shared/sepolicy/vendor/shell.te
index 60d15fa..cc26032 100644
--- a/shared/sepolicy/vendor/shell.te
+++ b/shared/sepolicy/vendor/shell.te
@@ -1,5 +1,5 @@
 allow shell serial_device:chr_file { getattr ioctl read write };
-allow shell cuttlefish_rotate_exec:file rx_file_perms;
+allow shell cuttlefish_sensor_injection_exec:file rx_file_perms;
 
 # TODO(b/130668487): Label the vsock sockets.
 allow shell adbd:{ socket vsock_socket } rw_socket_perms_no_ioctl;