Add UHID GET_REPORT functionality

GET_REPORT wasn't supported previously. >=4.10 hid-sony driver
requires this feature to get the calibration data for DualShock4.

To be backwards compatible with kernel 3.10, UHID_FEATURE and
UHID_FEATURE_ANSWER are used instead of UHID_GET_REPORT and
UHID_GET_REPORT_REPLY. Also, UHID_SET_REPORT functionality cannot
be supported since it's not implemented in kernel 3.10.

Test: Load >=4.10 hid-sony driver, connect a DualShock4, and check
      that the DualShock4 LED turns blue, which confirms both
      get feature report and output data report working.
Bug: 38511270
Change-Id: Ic28760f423cc09754fd32a107a499be6677ca747
diff --git a/bta/hh/bta_hh_act.cc b/bta/hh/bta_hh_act.cc
index 4d85437..05cf171 100644
--- a/bta/hh/bta_hh_act.cc
+++ b/bta/hh/bta_hh_act.cc
@@ -632,7 +632,7 @@
  *
  * Function         bta_hh_handsk_act
  *
- * Description      HID Host process a handshake acknoledgement.
+ * Description      HID Host process a handshake acknowledgement.
  *
  *
  * Returns          void
@@ -659,7 +659,9 @@
       bta_hh.hs_data.status = bta_hh_get_trans_status(p_data->hid_cback.data);
       if (bta_hh.hs_data.status == BTA_HH_OK)
         bta_hh.hs_data.status = BTA_HH_HS_TRANS_NOT_SPT;
-
+      if (p_cb->w4_evt == BTA_HH_GET_RPT_EVT)
+        bta_hh_co_get_rpt_rsp(bta_hh.dev_status.handle, bta_hh.hs_data.status,
+                              NULL, 0);
       (*bta_hh_cb.p_cback)(p_cb->w4_evt, &bta_hh);
       p_cb->w4_evt = 0;
       break;
@@ -671,6 +673,9 @@
       bta_hh.dev_status.handle = p_cb->hid_handle;
       bta_hh.dev_status.status =
           bta_hh_get_trans_status(p_data->hid_cback.data);
+      if (p_cb->w4_evt == BTA_HH_SET_RPT_EVT)
+        bta_hh_co_set_rpt_rsp(bta_hh.dev_status.handle,
+                              bta_hh.dev_status.status);
       (*bta_hh_cb.p_cback)(p_cb->w4_evt, &bta_hh);
       p_cb->w4_evt = 0;
       break;
@@ -726,6 +731,8 @@
       break;
     case BTA_HH_GET_RPT_EVT:
       hs_data.rsp_data.p_rpt_data = pdata;
+      bta_hh_co_get_rpt_rsp(hs_data.handle, hs_data.status, pdata->data,
+                            pdata->len);
       break;
     case BTA_HH_GET_PROTO_EVT:
       /* match up BTE/BTA report/boot mode def*/
diff --git a/bta/include/bta_hh_co.h b/bta/include/bta_hh_co.h
index da884bc..44e5a9f 100644
--- a/bta/include/bta_hh_co.h
+++ b/bta/include/bta_hh_co.h
@@ -77,6 +77,31 @@
  ******************************************************************************/
 extern void bta_hh_co_close(uint8_t dev_handle, uint8_t app_id);
 
+/*******************************************************************************
+ *
+ * Function         bta_hh_co_set_rpt_rsp
+ *
+ * Description      This callout function is executed by HH when Set Report
+ *                  Response is received on Control Channel.
+ *
+ * Returns          void.
+ *
+ ******************************************************************************/
+extern void bta_hh_co_set_rpt_rsp(uint8_t dev_handle, uint8_t status);
+
+/*******************************************************************************
+ *
+ * Function         bta_hh_co_get_rpt_rsp
+ *
+ * Description      This callout function is executed by HH when Get Report
+ *                  Response is received on Control Channel.
+ *
+ * Returns          void.
+ *
+ ******************************************************************************/
+extern void bta_hh_co_get_rpt_rsp(uint8_t dev_handle, uint8_t status,
+                                  uint8_t* p_rpt, uint16_t len);
+
 #if (BTA_HH_LE_INCLUDED == TRUE)
 /*******************************************************************************
  *
diff --git a/btif/co/bta_hh_co.cc b/btif/co/bta_hh_co.cc
index 9d3e119..267b8ba 100644
--- a/btif/co/bta_hh_co.cc
+++ b/btif/co/bta_hh_co.cc
@@ -42,6 +42,7 @@
 #define BTA_HH_NV_LOAD_MAX 16
 static tBTA_HH_RPT_CACHE_ENTRY sReportCache[BTA_HH_NV_LOAD_MAX];
 #endif
+#define GET_RPT_RSP_OFFSET 9
 
 void uhid_set_non_blocking(int fd) {
   int opts = fcntl(fd, F_GETFL);
@@ -128,8 +129,8 @@
         btif_hh_setreport(p_dev, BTHH_OUTPUT_REPORT, ev.u.output.size,
                           ev.u.output.data);
       else
-        btif_hh_setreport(p_dev, BTHH_INPUT_REPORT, ev.u.output.size,
-                          ev.u.output.data);
+        APPL_TRACE_ERROR("%s: UHID_OUTPUT: Invalid report type = %d", __func__,
+                         ev.u.output.rtype);
       break;
     case UHID_OUTPUT_EV:
       if (ret < (ssize_t)(sizeof(ev.type) + sizeof(ev.u.output_ev))) {
@@ -141,10 +142,24 @@
       APPL_TRACE_DEBUG("UHID_OUTPUT_EV from uhid-dev\n");
       break;
     case UHID_FEATURE:
-      APPL_TRACE_DEBUG("UHID_FEATURE from uhid-dev\n");
-      break;
-    case UHID_FEATURE_ANSWER:
-      APPL_TRACE_DEBUG("UHID_FEATURE_ANSWER from uhid-dev\n");
+      if (ret < (ssize_t)(sizeof(ev.type) + sizeof(ev.u.feature))) {
+        APPL_TRACE_ERROR(
+            "%s: UHID_FEATURE: Invalid size read from uhid-dev: %zd < %zu",
+            __func__, ret, sizeof(ev.type) + sizeof(ev.u.feature));
+        return -EFAULT;
+      }
+      APPL_TRACE_DEBUG("UHID_FEATURE: Report type = %d", ev.u.feature.rtype);
+      p_dev->get_rpt_snt++;
+      if (p_dev->get_rpt_id_queue) {
+        uint32_t* get_rpt_id = (uint32_t*)osi_malloc(sizeof(uint32_t));
+        *get_rpt_id = ev.u.feature.id;
+        fixed_queue_enqueue(p_dev->get_rpt_id_queue, (void*)get_rpt_id);
+      }
+      if (ev.u.feature.rtype == UHID_FEATURE_REPORT)
+        btif_hh_getreport(p_dev, BTHH_FEATURE_REPORT, ev.u.feature.rnum, 0);
+      else
+        APPL_TRACE_ERROR("%s: UHID_FEATURE: Invalid report type = %d", __func__,
+                         ev.u.feature.rtype);
       break;
 
     default:
@@ -337,6 +352,9 @@
   }
 
   p_dev->dev_status = BTHH_CONN_STATE_CONNECTED;
+  p_dev->get_rpt_id_queue = fixed_queue_new(SIZE_MAX);
+  CHECK(p_dev->get_rpt_id_queue);
+
   APPL_TRACE_DEBUG("%s: Return device status %d", __func__, p_dev->dev_status);
 }
 
@@ -366,6 +384,9 @@
 
   for (i = 0; i < BTIF_HH_MAX_HID; i++) {
     p_dev = &btif_hh_cb.devices[i];
+    fixed_queue_flush(p_dev->get_rpt_id_queue, osi_free);
+    fixed_queue_free(p_dev->get_rpt_id_queue, NULL);
+    p_dev->get_rpt_id_queue = NULL;
     if (p_dev->dev_status != BTHH_CONN_STATE_UNKNOWN &&
         p_dev->dev_handle == dev_handle) {
       APPL_TRACE_WARNING(
@@ -493,6 +514,71 @@
   }
 }
 
+/*******************************************************************************
+ *
+ * Function         bta_hh_co_set_rpt_rsp
+ *
+ * Description      This callout function is executed by HH when Set Report
+ *                  Response is received on Control Channel.
+ *
+ * Returns          void.
+ *
+ ******************************************************************************/
+void bta_hh_co_set_rpt_rsp(uint8_t dev_handle, uint8_t status) {
+  APPL_TRACE_ERROR("%s: Error: UHID_SET_REPORT_REPLY not supported", __func__);
+}
+
+/*******************************************************************************
+ *
+ * Function         bta_hh_co_get_rpt_rsp
+ *
+ * Description      This callout function is executed by HH when Get Report
+ *                  Response is received on Control Channel.
+ *
+ * Returns          void.
+ *
+ ******************************************************************************/
+void bta_hh_co_get_rpt_rsp(uint8_t dev_handle, uint8_t status, uint8_t* p_rpt,
+                           uint16_t len) {
+  struct uhid_event ev;
+  btif_hh_device_t* p_dev;
+
+  APPL_TRACE_VERBOSE("%s: dev_handle = %d", __func__, dev_handle);
+
+  p_dev = btif_hh_find_connected_dev_by_handle(dev_handle);
+  if (p_dev == NULL) {
+    APPL_TRACE_WARNING("%s: Error: unknown HID device handle %d", __func__,
+                       dev_handle);
+    return;
+  }
+
+  if (!p_dev->get_rpt_id_queue) {
+    APPL_TRACE_WARNING("%s: Error: missing UHID_GET_REPORT id queue", __func__);
+    return;
+  }
+
+  // Send the HID report to the kernel.
+  if (p_dev->fd >= 0 && p_dev->get_rpt_snt--) {
+    uint32_t* get_rpt_id =
+        (uint32_t*)fixed_queue_dequeue(p_dev->get_rpt_id_queue);
+    memset(&ev, 0, sizeof(ev));
+    ev.type = UHID_FEATURE_ANSWER;
+    ev.u.feature_answer.id = *get_rpt_id;
+    ev.u.feature_answer.err = status;
+    ev.u.feature_answer.size = len;
+    osi_free(get_rpt_id);
+    if (len > 0) {
+      if (len > UHID_DATA_MAX) {
+        APPL_TRACE_WARNING("%s: Report size greater than allowed size",
+                           __func__);
+        return;
+      }
+      memcpy(ev.u.feature_answer.data, p_rpt + GET_RPT_RSP_OFFSET, len);
+      uhid_write(p_dev->fd, &ev);
+    }
+  }
+}
+
 #if (BTA_HH_LE_INCLUDED == TRUE)
 /*******************************************************************************
  *
diff --git a/btif/include/btif_hh.h b/btif/include/btif_hh.h
index 2364544..b71d347 100644
--- a/btif/include/btif_hh.h
+++ b/btif/include/btif_hh.h
@@ -25,6 +25,7 @@
 #include <stdint.h>
 #include "bta_hh_api.h"
 #include "btu.h"
+#include "osi/include/fixed_queue.h"
 
 /*******************************************************************************
  *  Constants & Macros
@@ -67,6 +68,8 @@
   pthread_t hh_poll_thread_id;
   uint8_t hh_keep_polling;
   alarm_t* vup_timer;
+  fixed_queue_t* get_rpt_id_queue;
+  uint8_t get_rpt_snt;
   bool local_vup;  // Indicated locally initiated VUP
 } btif_hh_device_t;
 
@@ -106,6 +109,9 @@
 extern void btif_hh_setreport(btif_hh_device_t* p_dev,
                               bthh_report_type_t r_type, uint16_t size,
                               uint8_t* report);
+extern void btif_hh_getreport(btif_hh_device_t* p_dev,
+                              bthh_report_type_t r_type, uint8_t reportId,
+                              uint16_t bufferSize);
 extern void btif_hh_service_registration(bool enable);
 
 #endif
diff --git a/btif/src/btif_hh.cc b/btif/src/btif_hh.cc
index c5a9021..b2c1c6f 100644
--- a/btif/src/btif_hh.cc
+++ b/btif/src/btif_hh.cc
@@ -682,6 +682,21 @@
   }
 }
 
+/*******************************************************************************
+ *
+ *
+ * Function         btif_hh_getreport
+ *
+ * Description      getreport initiated from the BTIF thread context
+ *
+ * Returns          void
+ *
+ ******************************************************************************/
+void btif_hh_getreport(btif_hh_device_t* p_dev, bthh_report_type_t r_type,
+                       uint8_t reportId, uint16_t bufferSize) {
+  BTA_HhGetReport(p_dev->dev_handle, r_type, reportId, bufferSize);
+}
+
 /*****************************************************************************
  *   Section name (Group of functions)
  ****************************************************************************/