Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49266db0 authored by Himanshu Rawat's avatar Himanshu Rawat
Browse files

Handle service changed indication in HOGP host

HOGP host implementation assumed that the HOGP and related services
would not change in the HOGP device. However this may be true for some
devices.
With this change, HOGP host generates a local disconnection to prevent
interaction with the HOGP device while the GATT rediscovers the services
on the HOGP device. On completing the service re-discovery, HOGP host
pretends to have connected to a new device. However btif_hh remains
under impression that the HOGP device reconnected.

Bug: 220105065
Test: Manual
Change-Id: I29b14d868e4325bd6a296daa795be0343e58dbb9
parent 75a13e1e
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -989,7 +989,7 @@ void bta_hh_close_act(tBTA_HH_DEV_CB* p_cb, const tBTA_HH_DATA* p_data) {
void bta_hh_get_dscp_act(tBTA_HH_DEV_CB* p_cb,
void bta_hh_get_dscp_act(tBTA_HH_DEV_CB* p_cb,
                         UNUSED_ATTR const tBTA_HH_DATA* p_data) {
                         UNUSED_ATTR const tBTA_HH_DATA* p_data) {
  if (p_cb->is_le_device) {
  if (p_cb->is_le_device) {
    if (p_cb->hid_srvc.in_use) {
    if (p_cb->hid_srvc.state >= BTA_HH_SERVICE_DISCOVERED) {
      p_cb->dscp_info.hid_handle = p_cb->hid_handle;
      p_cb->dscp_info.hid_handle = p_cb->hid_handle;
    }
    }
    bta_hh_le_get_dscp_act(p_cb);
    bta_hh_le_get_dscp_act(p_cb);
+7 −1
Original line number Original line Diff line number Diff line
@@ -147,8 +147,14 @@ typedef struct {
#define BTA_HH_LE_RPT_MAX 20
#define BTA_HH_LE_RPT_MAX 20
#endif
#endif


enum tBTA_HH_SERVICE_STATE {
  BTA_HH_SERVICE_UNKNOWN,
  BTA_HH_SERVICE_CHANGED,
  BTA_HH_SERVICE_DISCOVERED,
};

typedef struct {
typedef struct {
  bool in_use;
  tBTA_HH_SERVICE_STATE state;
  uint8_t srvc_inst_id;
  uint8_t srvc_inst_id;
  tBTA_HH_LE_RPT report[BTA_HH_LE_RPT_MAX];
  tBTA_HH_LE_RPT report[BTA_HH_LE_RPT_MAX];


+133 −33
Original line number Original line Diff line number Diff line
@@ -97,7 +97,7 @@ static const char* bta_hh_le_rpt_name[4] = {"UNKNOWN", "INPUT", "OUTPUT",
static void bta_hh_le_hid_report_dbg(tBTA_HH_DEV_CB* p_cb) {
static void bta_hh_le_hid_report_dbg(tBTA_HH_DEV_CB* p_cb) {
  APPL_TRACE_DEBUG("%s: HID Report DB", __func__);
  APPL_TRACE_DEBUG("%s: HID Report DB", __func__);


  if (!p_cb->hid_srvc.in_use) return;
  if (p_cb->hid_srvc.state < BTA_HH_SERVICE_DISCOVERED) return;


  tBTA_HH_LE_RPT* p_rpt = &p_cb->hid_srvc.report[0];
  tBTA_HH_LE_RPT* p_rpt = &p_cb->hid_srvc.report[0];


@@ -304,7 +304,8 @@ static tBTA_HH_DEV_CB* bta_hh_le_find_dev_cb_by_bda(const RawAddress& bda) {
 ******************************************************************************/
 ******************************************************************************/
static uint8_t bta_hh_le_find_service_inst_by_battery_inst_id(
static uint8_t bta_hh_le_find_service_inst_by_battery_inst_id(
    tBTA_HH_DEV_CB* p_cb, uint8_t ba_inst_id) {
    tBTA_HH_DEV_CB* p_cb, uint8_t ba_inst_id) {
  if (p_cb->hid_srvc.in_use && p_cb->hid_srvc.incl_srvc_inst == ba_inst_id) {
  if (p_cb->hid_srvc.state >= BTA_HH_SERVICE_DISCOVERED &&
      p_cb->hid_srvc.incl_srvc_inst == ba_inst_id) {
    return p_cb->hid_srvc.srvc_inst_id;
    return p_cb->hid_srvc.srvc_inst_id;
  }
  }
  return BTA_HH_IDX_INVALID;
  return BTA_HH_IDX_INVALID;
@@ -849,7 +850,8 @@ static void bta_hh_le_get_protocol_mode(tBTA_HH_DEV_CB* p_cb) {
  tBTA_HH_HSDATA hs_data;
  tBTA_HH_HSDATA hs_data;
  p_cb->w4_evt = BTA_HH_GET_PROTO_EVT;
  p_cb->w4_evt = BTA_HH_GET_PROTO_EVT;


  if (p_cb->hid_srvc.in_use && p_cb->hid_srvc.proto_mode_handle != 0) {
  if (p_cb->hid_srvc.state >= BTA_HH_SERVICE_DISCOVERED &&
      p_cb->hid_srvc.proto_mode_handle != 0) {
    BtaGattQueue::ReadCharacteristic(p_cb->conn_id,
    BtaGattQueue::ReadCharacteristic(p_cb->conn_id,
                                     p_cb->hid_srvc.proto_mode_handle,
                                     p_cb->hid_srvc.proto_mode_handle,
                                     get_protocol_mode_cb, p_cb);
                                     get_protocol_mode_cb, p_cb);
@@ -877,8 +879,18 @@ static void bta_hh_le_dis_cback(const RawAddress& addr,
                                tDIS_VALUE* p_dis_value) {
                                tDIS_VALUE* p_dis_value) {
  tBTA_HH_DEV_CB* p_cb = bta_hh_le_find_dev_cb_by_bda(addr);
  tBTA_HH_DEV_CB* p_cb = bta_hh_le_find_dev_cb_by_bda(addr);


  if (p_cb == NULL || p_dis_value == NULL) {
  if (p_cb == nullptr) {
    APPL_TRACE_ERROR("received unexpected/error DIS callback");
    LOG_WARN("Unknown address");
    return;
  }

  if (p_cb->status == BTA_HH_ERR_SDP) {
    LOG_WARN("HID service was not found");
    return;
  }

  if (p_dis_value == nullptr) {
    LOG_WARN("Invalid value");
    return;
    return;
  }
  }


@@ -965,7 +977,7 @@ void bta_hh_security_cmpl(tBTA_HH_DEV_CB* p_cb,
                          UNUSED_ATTR const tBTA_HH_DATA* p_buf) {
                          UNUSED_ATTR const tBTA_HH_DATA* p_buf) {
  APPL_TRACE_DEBUG("%s", __func__);
  APPL_TRACE_DEBUG("%s", __func__);
  if (p_cb->status == BTA_HH_OK) {
  if (p_cb->status == BTA_HH_OK) {
    if (!p_cb->hid_srvc.in_use) {
    if (p_cb->hid_srvc.state < BTA_HH_SERVICE_DISCOVERED) {
      APPL_TRACE_DEBUG("bta_hh_security_cmpl no reports loaded, try to load");
      APPL_TRACE_DEBUG("bta_hh_security_cmpl no reports loaded, try to load");


      /* start loading the cache if not in stack */
      /* start loading the cache if not in stack */
@@ -977,7 +989,8 @@ void bta_hh_security_cmpl(tBTA_HH_DEV_CB* p_cb,
      }
      }
    }
    }
    /*  discovery has been done for HID service */
    /*  discovery has been done for HID service */
    if (p_cb->app_id != 0 && p_cb->hid_srvc.in_use) {
    if (p_cb->app_id != 0 &&
        p_cb->hid_srvc.state >= BTA_HH_SERVICE_DISCOVERED) {
      APPL_TRACE_DEBUG("%s: discovery has been done for HID service", __func__);
      APPL_TRACE_DEBUG("%s: discovery has been done for HID service", __func__);
      /* configure protocol mode */
      /* configure protocol mode */
      if (!bta_hh_le_set_protocol_mode(p_cb, p_cb->mode)) {
      if (!bta_hh_le_set_protocol_mode(p_cb, p_cb->mode)) {
@@ -1148,6 +1161,14 @@ static void bta_hh_le_close(const tBTA_GATTC_CLOSE& gattc_data) {
             ADDRESS_TO_LOGGABLE_CSTR(gattc_data.remote_bda));
             ADDRESS_TO_LOGGABLE_CSTR(gattc_data.remote_bda));
    return;
    return;
  }
  }

  if (p_cb->hid_srvc.state == BTA_HH_SERVICE_CHANGED) {
    /* Service change would have already prompted a local disconnection */
    LOG_WARN("Disconnected after service changed indication:%s",
             ADDRESS_TO_LOGGABLE_CSTR(gattc_data.remote_bda));
    return;
  }

  p_cb->conn_id = GATT_INVALID_CONN_ID;
  p_cb->conn_id = GATT_INVALID_CONN_ID;
  p_cb->security_pending = false;
  p_cb->security_pending = false;


@@ -1449,14 +1470,15 @@ static void bta_hh_le_srvc_search_cmpl(tBTA_GATTC_SEARCH_CMPL* p_data) {
  if (p_dev_cb == NULL) return;
  if (p_dev_cb == NULL) return;


  if (p_data->status != GATT_SUCCESS) {
  if (p_data->status != GATT_SUCCESS) {
    LOG_ERROR("Service discovery failed %d", p_data->status);
    p_dev_cb->status = BTA_HH_ERR_SDP;
    p_dev_cb->status = BTA_HH_ERR_SDP;
    /* close the connection and report service discovery complete with error */
    bta_hh_le_api_disc_act(p_dev_cb);
    bta_hh_le_api_disc_act(p_dev_cb);
    return;
    return;
  }
  }


  const std::list<gatt::Service>* services =
  const std::list<gatt::Service>* services = BTA_GATTC_GetServices(p_data->conn_id);
      BTA_GATTC_GetServices(p_data->conn_id);
  const gatt::Service* gap_service = nullptr;
  const gatt::Service* scp_service = nullptr;


  bool have_hid = false;
  bool have_hid = false;
  for (const gatt::Service& service : *services) {
  for (const gatt::Service& service : *services) {
@@ -1465,7 +1487,7 @@ static void bta_hh_le_srvc_search_cmpl(tBTA_GATTC_SEARCH_CMPL* p_data) {
      have_hid = true;
      have_hid = true;


      /* found HID primamry service */
      /* found HID primamry service */
      p_dev_cb->hid_srvc.in_use = true;
      p_dev_cb->hid_srvc.state = BTA_HH_SERVICE_DISCOVERED;
      p_dev_cb->hid_srvc.srvc_inst_id = service.handle;
      p_dev_cb->hid_srvc.srvc_inst_id = service.handle;
      p_dev_cb->hid_srvc.proto_mode_handle = 0;
      p_dev_cb->hid_srvc.proto_mode_handle = 0;
      p_dev_cb->hid_srvc.control_point_handle = 0;
      p_dev_cb->hid_srvc.control_point_handle = 0;
@@ -1475,31 +1497,42 @@ static void bta_hh_le_srvc_search_cmpl(tBTA_GATTC_SEARCH_CMPL* p_data) {
      APPL_TRACE_DEBUG("%s: have HID service inst_id= %d", __func__,
      APPL_TRACE_DEBUG("%s: have HID service inst_id= %d", __func__,
                       p_dev_cb->hid_srvc.srvc_inst_id);
                       p_dev_cb->hid_srvc.srvc_inst_id);
    } else if (service.uuid == Uuid::From16Bit(UUID_SERVCLASS_SCAN_PARAM)) {
    } else if (service.uuid == Uuid::From16Bit(UUID_SERVCLASS_SCAN_PARAM)) {

      scp_service = &service;
      for (const gatt::Characteristic& charac : service.characteristics) {
    } else if (service.uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) {
        if (charac.uuid == Uuid::From16Bit(GATT_UUID_SCAN_REFRESH)) {
      gap_service = &service;

          if (charac.properties & GATT_CHAR_PROP_BIT_NOTIFY)
            p_dev_cb->scps_notify |= BTA_HH_LE_SCPS_NOTIFY_SPT;
          else
            p_dev_cb->scps_notify = BTA_HH_LE_SCPS_NOTIFY_NONE;

          break;
    }
    }
  }
  }
    } else if (service.uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) {

      // TODO(jpawlowski): this should be done by GAP profile, remove when GAP
  if (!have_hid) {
      // is fixed.
    LOG_ERROR("HID service not found");
      for (const gatt::Characteristic& charac : service.characteristics) {
    p_dev_cb->status = BTA_HH_ERR_SDP;
    bta_hh_le_api_disc_act(p_dev_cb);
    return;
  }

  if (gap_service != nullptr) {
    // TODO: This should be done by GAP profile, remove when GAP is fixed.
    for (const gatt::Characteristic& charac : gap_service->characteristics) {
      if (charac.uuid == Uuid::From16Bit(GATT_UUID_GAP_PREF_CONN_PARAM)) {
      if (charac.uuid == Uuid::From16Bit(GATT_UUID_GAP_PREF_CONN_PARAM)) {
        /* read the char value */
        /* read the char value */
          BtaGattQueue::ReadCharacteristic(p_dev_cb->conn_id,
        BtaGattQueue::ReadCharacteristic(p_dev_cb->conn_id, charac.value_handle,
                                           charac.value_handle,
                                         read_pref_conn_params_cb, p_dev_cb);
                                         read_pref_conn_params_cb, p_dev_cb);
        break;
        break;
      }
      }
    }
    }
  }
  }

  if (scp_service != nullptr) {
    for (const gatt::Characteristic& charac : scp_service->characteristics) {
      if (charac.uuid == Uuid::From16Bit(GATT_UUID_SCAN_REFRESH)) {
        if (charac.properties & GATT_CHAR_PROP_BIT_NOTIFY) {
          p_dev_cb->scps_notify |= BTA_HH_LE_SCPS_NOTIFY_SPT;
        } else {
          p_dev_cb->scps_notify = BTA_HH_LE_SCPS_NOTIFY_NONE;
        }
        break;
      }
    }
  }
  }


  bta_hh_le_gatt_disc_cmpl(p_dev_cb, p_dev_cb->status);
  bta_hh_le_gatt_disc_cmpl(p_dev_cb, p_dev_cb->status);
@@ -1600,7 +1633,7 @@ void bta_hh_le_open_fail(tBTA_HH_DEV_CB* p_cb, const tBTA_HH_DATA* p_data) {
    bta_hh_clear_service_cache(p_cb);
    bta_hh_clear_service_cache(p_cb);
  }
  }


  if (p_cb->is_le_device) {
  if (p_cb->is_le_device && p_cb->status != BTA_HH_ERR_SDP) {
    LOG_DEBUG("gd_acl: Re-adding HID device to acceptlist");
    LOG_DEBUG("gd_acl: Re-adding HID device to acceptlist");
    // gd removes from bg list after failed connection
    // gd removes from bg list after failed connection
    // Correct the cached state to allow re-add to acceptlist.
    // Correct the cached state to allow re-add to acceptlist.
@@ -1965,7 +1998,7 @@ void bta_hh_le_write_dev_act(tBTA_HH_DEV_CB* p_cb, const tBTA_HH_DATA* p_data) {
 *
 *
 ******************************************************************************/
 ******************************************************************************/
void bta_hh_le_get_dscp_act(tBTA_HH_DEV_CB* p_cb) {
void bta_hh_le_get_dscp_act(tBTA_HH_DEV_CB* p_cb) {
  if (p_cb->hid_srvc.in_use) {
  if (p_cb->hid_srvc.state >= BTA_HH_SERVICE_DISCOVERED) {
    p_cb->dscp_info.descriptor.dl_len = p_cb->hid_srvc.descriptor.dl_len;
    p_cb->dscp_info.descriptor.dl_len = p_cb->hid_srvc.descriptor.dl_len;
    p_cb->dscp_info.descriptor.dsc_list = p_cb->hid_srvc.descriptor.dsc_list;
    p_cb->dscp_info.descriptor.dsc_list = p_cb->hid_srvc.descriptor.dsc_list;


@@ -2063,6 +2096,65 @@ void bta_hh_le_remove_dev_bg_conn(tBTA_HH_DEV_CB* p_dev_cb) {
  bta_hh_le_deregister_input_notif(p_dev_cb);
  bta_hh_le_deregister_input_notif(p_dev_cb);
}
}


static void bta_hh_le_service_changed(RawAddress remote_bda) {
  tBTA_HH_DEV_CB* p_cb = bta_hh_le_find_dev_cb_by_bda(remote_bda);
  if (p_cb == nullptr) {
    LOG_WARN("Received close event with unknown device:%s",
             ADDRESS_TO_LOGGABLE_CSTR(remote_bda));
    return;
  }

  /* Forget the cached reports */
  bta_hh_le_co_reset_rpt_cache(p_cb->addr, p_cb->app_id);
  p_cb->dscp_info.descriptor.dsc_list = NULL;
  osi_free_and_reset((void**)&p_cb->hid_srvc.rpt_map);
  p_cb->hid_srvc = {};
  p_cb->hid_srvc.state = BTA_HH_SERVICE_CHANGED;
  p_cb->status = BTA_HH_HS_SERVICE_CHANGED;

  /* Pretend that the HOGP device disconnected so that higher layers don't
     try to communicate with it while the GATT database is rediscovered. */
  const tBTA_HH_DATA data = {
      .le_close =
          {
              .conn_id = p_cb->conn_id,
              .reason = GATT_CONN_OK,
              .hdr =
                  {
                      .event = BTA_HH_GATT_CLOSE_EVT,
                      .layer_specific = static_cast<uint16_t>(p_cb->hid_handle),
                  },
          },
  };
  bta_hh_sm_execute(p_cb, BTA_HH_GATT_CLOSE_EVT, &data);
}

static void bta_hh_le_service_discovery_done(RawAddress remote_bda) {
  tBTA_HH_DEV_CB* p_cb = bta_hh_le_find_dev_cb_by_bda(remote_bda);
  if (p_cb == nullptr) {
    LOG_WARN("Received service discovery done event for unknown device:%s",
             ADDRESS_TO_LOGGABLE_CSTR(remote_bda));
    return;
  }

  if (p_cb->hid_srvc.state == BTA_HH_SERVICE_CHANGED) {
    /* Service rediscovery completed after service change.
       Pretend to have connected with a new HOGP device. */
    p_cb->hid_srvc.state = BTA_HH_SERVICE_UNKNOWN;
    const tBTA_GATTC_OPEN open = {
        .status = GATT_SUCCESS,
        .conn_id = p_cb->conn_id,
        .client_if = bta_hh_cb.gatt_if,
        .remote_bda = remote_bda,
        .transport = BT_TRANSPORT_LE,
        .mtu = 0,
    };
    bta_hh_sm_execute(p_cb, BTA_HH_GATT_OPEN_EVT, (tBTA_HH_DATA*)&open);
  } else {
    LOG_INFO("Discovery done, service state: %d", p_cb->hid_srvc.state);
  }
}

/*******************************************************************************
/*******************************************************************************
 *
 *
 * Function         bta_hh_gattc_callback
 * Function         bta_hh_gattc_callback
@@ -2104,6 +2196,14 @@ static void bta_hh_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
      bta_hh_le_input_rpt_notify(&p_data->notify);
      bta_hh_le_input_rpt_notify(&p_data->notify);
      break;
      break;


    case BTA_GATTC_SRVC_CHG_EVT:
      bta_hh_le_service_changed(p_data->remote_bda);
      break;

    case BTA_GATTC_SRVC_DISC_DONE_EVT:
      bta_hh_le_service_discovery_done(p_data->remote_bda);
      break;

    case BTA_GATTC_ENC_CMPL_CB_EVT: /* 17 */
    case BTA_GATTC_ENC_CMPL_CB_EVT: /* 17 */
      p_dev_cb = bta_hh_le_find_dev_cb_by_bda(p_data->enc_cmpl.remote_bda);
      p_dev_cb = bta_hh_le_find_dev_cb_by_bda(p_data->enc_cmpl.remote_bda);
      if (p_dev_cb) {
      if (p_dev_cb) {
@@ -2134,7 +2234,7 @@ static void bta_hh_process_cache_rpt(tBTA_HH_DEV_CB* p_cb,


  if (num_rpt != 0) /* no cache is found */
  if (num_rpt != 0) /* no cache is found */
  {
  {
    p_cb->hid_srvc.in_use = true;
    p_cb->hid_srvc.state = BTA_HH_SERVICE_UNKNOWN;


    /* set the descriptor info */
    /* set the descriptor info */
    p_cb->hid_srvc.descriptor.dl_len = p_cb->dscp_info.descriptor.dl_len;
    p_cb->hid_srvc.descriptor.dl_len = p_cb->dscp_info.descriptor.dl_len;
+2 −1
Original line number Original line Diff line number Diff line
@@ -138,7 +138,8 @@ typedef enum : uint8_t {
  BTA_HH_ERR_NO_RES,      /* out of system resources */
  BTA_HH_ERR_NO_RES,      /* out of system resources */
  BTA_HH_ERR_AUTH_FAILED, /* authentication fail */
  BTA_HH_ERR_AUTH_FAILED, /* authentication fail */
  BTA_HH_ERR_HDL,
  BTA_HH_ERR_HDL,
  BTA_HH_ERR_SEC
  BTA_HH_ERR_SEC,
  BTA_HH_HS_SERVICE_CHANGED /* GATT service changed on the peer */
} tBTA_HH_STATUS;
} tBTA_HH_STATUS;


inline tBTA_HH_STATUS to_bta_hh_status(uint32_t status) {
inline tBTA_HH_STATUS to_bta_hh_status(uint32_t status) {
+7 −0
Original line number Original line Diff line number Diff line
@@ -878,6 +878,13 @@ static void btif_hh_upstreams_evt(uint16_t event, char* p_param) {
        if (p_dev->local_vup) {
        if (p_dev->local_vup) {
          p_dev->local_vup = false;
          p_dev->local_vup = false;
          BTA_DmRemoveDevice(p_dev->bd_addr);
          BTA_DmRemoveDevice(p_dev->bd_addr);
        } else if (p_data->dev_status.status == BTA_HH_HS_SERVICE_CHANGED) {
          /* Local disconnection due to service change in the HOGP device.
             HID descriptor would be read again, so remove it from cache. */
          LOG_WARN(
              "Removing cached descriptor due to service change, handle = %d",
              p_data->dev_status.handle);
          btif_storage_remove_hid_info(p_dev->bd_addr);
        }
        }


        btif_hh_cb.status = (BTIF_HH_STATUS)BTIF_HH_DEV_DISCONNECTED;
        btif_hh_cb.status = (BTIF_HH_STATUS)BTIF_HH_DEV_DISCONNECTED;