LE: Register for service change indication

Some bonded remote devices require the client to register for service
change indication and actually set the client configuration descriptor
before service change indications can be removed.

This change add an additional step after device bonding has occured to
register for service change indication if possible.

Bug: 18173911
Change-Id: I25386faec0d58834ee2b0a9d1db2d2e052311264
diff --git a/bta/dm/bta_dm_act.c b/bta/dm/bta_dm_act.c
index 2ef728b..ed38b7e 100644
--- a/bta/dm/bta_dm_act.c
+++ b/bta/dm/bta_dm_act.c
@@ -4937,8 +4937,8 @@
             else
             {
                 sec_event.auth_cmpl.success = TRUE;
+                GATT_ConfigServiceChangeCCC(bda, TRUE, BT_TRANSPORT_LE);
             }
-            sec_event.auth_cmpl.privacy_enabled = p_data->complt.privacy_supported;
             if (bta_dm_cb.p_sec_cback)
             {
                 //bta_dm_cb.p_sec_cback(BTA_DM_AUTH_CMPL_EVT, &sec_event);
diff --git a/bta/include/bta_api.h b/bta/include/bta_api.h
index b9403e9..22eab79 100644
--- a/bta/include/bta_api.h
+++ b/bta/include/bta_api.h
@@ -761,9 +761,6 @@
     LINK_KEY        key;                /* Link key associated with peer device. */
     UINT8           key_type;           /* The type of Link Key */
     BOOLEAN         success;            /* TRUE of authentication succeeded, FALSE if failed. */
-#if BLE_INCLUDED == TRUE
-    BOOLEAN         privacy_enabled;    /* used for BLE device only */
-#endif
     UINT8           fail_reason;        /* The HCI reason/error code for when success=FALSE */
 
 } tBTA_DM_AUTH_CMPL;
diff --git a/stack/gatt/gatt_attr.c b/stack/gatt/gatt_attr.c
index df852e3..8773036 100644
--- a/stack/gatt/gatt_attr.c
+++ b/stack/gatt/gatt_attr.c
@@ -40,17 +40,24 @@
 #define GATTP_ATTR_DB_SIZE      GATT_DB_MEM_SIZE(GATTP_MAX_NUM_INC_SVR, GATTP_MAX_CHAR_NUM, GATTP_MAX_CHAR_VALUE_SIZE)
 #endif
 
-static void gatt_profile_request_cback (UINT16 conn_id, UINT32 trans_id, UINT8 op_code, tGATTS_DATA *p_data);
-static void gatt_profile_connect_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id,
-                                         BOOLEAN connected, tGATT_DISCONN_REASON reason, tBT_TRANSPORT transport);
+static void gatt_request_cback(UINT16 conn_id, UINT32 trans_id, UINT8 op_code, tGATTS_DATA *p_data);
+static void gatt_connect_cback(tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, BOOLEAN connected,
+              tGATT_DISCONN_REASON reason, tBT_TRANSPORT transport);
+static void gatt_disc_res_cback(UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT_DISC_RES *p_data);
+static void gatt_disc_cmpl_cback(UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT_STATUS status);
+static void gatt_cl_op_cmpl_cback(UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS status,
+              tGATT_CL_COMPLETE *p_data);
+
+static void gatt_cl_start_config_ccc(tGATT_PROFILE_CLCB *p_clcb);
+
 
 static tGATT_CBACK gatt_profile_cback =
 {
-    gatt_profile_connect_cback,
-    NULL,
-    NULL,
-    NULL,
-    gatt_profile_request_cback,
+    gatt_connect_cback,
+    gatt_cl_op_cmpl_cback,
+    gatt_disc_res_cback,
+    gatt_disc_cmpl_cback,
+    gatt_request_cback,
     NULL,
     NULL
 } ;
@@ -59,25 +66,39 @@
 **
 ** Function         gatt_profile_find_conn_id_by_bd_addr
 **
-** Description      The function searches all LCB with macthing bd address
+** Description      Find the connection ID by remote address
 **
-** Returns          total number of clcb found.
+** Returns          Connection ID
 **
 *******************************************************************************/
-UINT16 gatt_profile_find_conn_id_by_bd_addr(BD_ADDR bda)
+UINT16 gatt_profile_find_conn_id_by_bd_addr(BD_ADDR remote_bda)
+{
+    UINT16 conn_id = GATT_INVALID_CONN_ID;
+    GATT_GetConnIdIfConnected (gatt_cb.gatt_if, remote_bda, &conn_id, BT_TRANSPORT_LE);
+    return conn_id;
+}
+
+/*******************************************************************************
+**
+** Function         gatt_profile_find_clcb_by_conn_id
+**
+** Description      find clcb by Connection ID
+**
+** Returns          Pointer to the found link conenction control block.
+**
+*******************************************************************************/
+static tGATT_PROFILE_CLCB *gatt_profile_find_clcb_by_conn_id(UINT16 conn_id)
 {
     UINT8 i_clcb;
     tGATT_PROFILE_CLCB    *p_clcb = NULL;
 
     for (i_clcb = 0, p_clcb= gatt_cb.profile_clcb; i_clcb < GATT_MAX_APPS; i_clcb++, p_clcb++)
     {
-        if (p_clcb->in_use && p_clcb->connected && !memcmp(p_clcb->bda, bda, BD_ADDR_LEN))
-        {
-            return p_clcb->conn_id;
-        }
+        if (p_clcb->in_use && p_clcb->conn_id == conn_id)
+            return p_clcb;
     }
 
-    return GATT_INVALID_CONN_ID;
+    return p_clcb;
 }
 
 /*******************************************************************************
@@ -98,9 +119,7 @@
     {
         if (p_clcb->in_use && p_clcb->transport == transport &&
             p_clcb->connected && !memcmp(p_clcb->bda, bda, BD_ADDR_LEN))
-        {
             return p_clcb;
-        }
     }
 
     return p_clcb;
@@ -134,42 +153,31 @@
     }
     return p_clcb;
 }
+
 /*******************************************************************************
 **
 ** Function         gatt_profile_clcb_dealloc
 **
 ** Description      The function deallocates a GATT profile  connection link control block
 **
-** Returns           NTrue the deallocation is successful
+** Returns          void
 **
 *******************************************************************************/
-BOOLEAN gatt_profile_clcb_dealloc (UINT16 conn_id)
+void gatt_profile_clcb_dealloc (tGATT_PROFILE_CLCB *p_clcb)
 {
-    UINT8                   i_clcb = 0;
-    tGATT_PROFILE_CLCB      *p_clcb = NULL;
-
-    for (i_clcb = 0, p_clcb= gatt_cb.profile_clcb; i_clcb < GATT_MAX_APPS; i_clcb++, p_clcb++)
-    {
-        if (p_clcb->in_use && p_clcb->connected && (p_clcb->conn_id == conn_id))
-        {
-            memset(p_clcb, 0, sizeof(tGATT_PROFILE_CLCB));
-            return TRUE;
-        }
-    }
-    return FALSE;
+    memset(p_clcb, 0, sizeof(tGATT_PROFILE_CLCB));
 }
 
-
 /*******************************************************************************
 **
-** Function         gatt_profile_request_cback
+** Function         gatt_request_cback
 **
 ** Description      GATT profile attribute access request callback.
 **
 ** Returns          void.
 **
 *******************************************************************************/
-static void gatt_profile_request_cback (UINT16 conn_id, UINT32 trans_id, tGATTS_REQ_TYPE type,
+static void gatt_request_cback (UINT16 conn_id, UINT32 trans_id, tGATTS_REQ_TYPE type,
                                         tGATTS_DATA *p_data)
 {
     UINT8       status = GATT_INVALID_PDU;
@@ -211,36 +219,40 @@
 
 /*******************************************************************************
 **
-** Function         gatt_profile_connect_cback
+** Function         gatt_connect_cback
 **
 ** Description      Gatt profile connection callback.
 **
 ** Returns          void
 **
 *******************************************************************************/
-static void gatt_profile_connect_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id,
+static void gatt_connect_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id,
                                         BOOLEAN connected, tGATT_DISCONN_REASON reason,
                                         tBT_TRANSPORT transport)
 {
     UNUSED(gatt_if);
 
-    GATT_TRACE_EVENT ("gatt_profile_connect_cback: from %08x%04x connected:%d conn_id=%d reason = 0x%04x",
+    GATT_TRACE_EVENT ("%s: from %08x%04x connected:%d conn_id=%d reason = 0x%04x", __FUNCTION__,
                        (bda[0]<<24)+(bda[1]<<16)+(bda[2]<<8)+bda[3],
                        (bda[4]<<8)+bda[5], connected, conn_id, reason);
 
+    tGATT_PROFILE_CLCB *p_clcb = gatt_profile_find_clcb_by_bd_addr(bda, transport);
+    if (p_clcb == NULL)
+        return;
+
     if (connected)
     {
-        if (gatt_profile_clcb_alloc(conn_id, bda, transport) == NULL)
-        {
-            GATT_TRACE_ERROR ("gatt_profile_connect_cback: no_resource");
-            return;
-        }
-    }
-    else
-    {
-        gatt_profile_clcb_dealloc(conn_id);
-    }
+        p_clcb->conn_id = conn_id;
+        p_clcb->connected = TRUE;
 
+        if (p_clcb->ccc_stage == GATT_SVC_CHANGED_CONNECTING)
+        {
+            p_clcb->ccc_stage ++;
+            gatt_cl_start_config_ccc(p_clcb);
+        }
+    } else {
+        gatt_profile_clcb_dealloc(p_clcb);
+    }
 }
 
 /*******************************************************************************
@@ -284,4 +296,215 @@
                        gatt_cb.gatt_if,  status);
 }
 
+/*******************************************************************************
+**
+** Function         gatt_config_ccc_complete
+**
+** Description      The function finish the service change ccc configuration
+**
+** Returns          void
+**
+*******************************************************************************/
+static void gatt_config_ccc_complete(tGATT_PROFILE_CLCB *p_clcb)
+{
+    GATT_Disconnect(p_clcb->conn_id);
+    gatt_profile_clcb_dealloc(p_clcb);
+}
+
+/*******************************************************************************
+**
+** Function         gatt_disc_res_cback
+**
+** Description      Gatt profile discovery result callback
+**
+** Returns          void
+**
+*******************************************************************************/
+static void gatt_disc_res_cback (UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT_DISC_RES *p_data)
+{
+    tGATT_PROFILE_CLCB *p_clcb = gatt_profile_find_clcb_by_conn_id(conn_id);
+
+    if (p_clcb == NULL)
+        return;
+
+    switch (disc_type)
+    {
+    case GATT_DISC_SRVC_BY_UUID:/* stage 1 */
+        p_clcb->e_handle = p_data->value.group_value.e_handle;
+        p_clcb->ccc_result ++;
+        break;
+
+    case GATT_DISC_CHAR:/* stage 2 */
+        p_clcb->s_handle = p_data->value.dclr_value.val_handle;
+        p_clcb->ccc_result ++;
+        break;
+
+    case GATT_DISC_CHAR_DSCPT: /* stage 3 */
+        if (p_data->type.uu.uuid16 == GATT_UUID_CHAR_CLIENT_CONFIG)
+        {
+            p_clcb->s_handle = p_data->handle;
+            p_clcb->ccc_result ++;
+        }
+        break;
+    }
+}
+
+/*******************************************************************************
+**
+** Function         gatt_disc_cmpl_cback
+**
+** Description      Gatt profile discovery complete callback
+**
+** Returns          void
+**
+*******************************************************************************/
+static void gatt_disc_cmpl_cback (UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT_STATUS status)
+{
+    tGATT_PROFILE_CLCB *p_clcb = gatt_profile_find_clcb_by_conn_id(conn_id);
+
+    if (p_clcb == NULL)
+        return;
+
+    if (status == GATT_SUCCESS && p_clcb->ccc_result > 0)
+    {
+        p_clcb->ccc_result = 0;
+        p_clcb->ccc_stage ++;
+        gatt_cl_start_config_ccc(p_clcb);
+    } else {
+        GATT_TRACE_ERROR("%s() - Register for service changed indication failure", __FUNCTION__);
+    }
+}
+
+/*******************************************************************************
+**
+** Function         gatt_cl_op_cmpl_cback
+**
+** Description      Gatt profile client operation complete callback
+**
+** Returns          void
+**
+*******************************************************************************/
+static void gatt_cl_op_cmpl_cback (UINT16 conn_id, tGATTC_OPTYPE op,
+                                           tGATT_STATUS status, tGATT_CL_COMPLETE *p_data)
+{
+    tGATT_PROFILE_CLCB *p_clcb = gatt_profile_find_clcb_by_conn_id(conn_id);
+
+    if (p_clcb == NULL)
+        return;
+
+    if (op == GATTC_OPTYPE_WRITE)
+    {
+        GATT_TRACE_DEBUG("%s() - ccc write status : %d", __FUNCTION__, status);
+    }
+
+    /* free the connection */
+    gatt_config_ccc_complete (p_clcb);
+}
+
+/*******************************************************************************
+**
+** Function         gatt_cl_start_config_ccc
+**
+** Description      Gatt profile start configure service change CCC
+**
+** Returns          void
+**
+*******************************************************************************/
+static void gatt_cl_start_config_ccc(tGATT_PROFILE_CLCB *p_clcb)
+{
+    tGATT_DISC_PARAM    srvc_disc_param;
+    tGATT_VALUE         ccc_value;
+
+    GATT_TRACE_DEBUG("%s() - stage: %d", __FUNCTION__, p_clcb->ccc_stage);
+
+    memset (&srvc_disc_param, 0 , sizeof(tGATT_DISC_PARAM));
+    memset (&ccc_value, 0 , sizeof(tGATT_VALUE));
+
+    switch(p_clcb->ccc_stage)
+    {
+    case GATT_SVC_CHANGED_SERVICE: /* discover GATT service */
+        srvc_disc_param.s_handle = 1;
+        srvc_disc_param.e_handle = 0xffff;
+        srvc_disc_param.service.len = 2;
+        srvc_disc_param.service.uu.uuid16 = UUID_SERVCLASS_GATT_SERVER;
+        if (GATTC_Discover (p_clcb->conn_id, GATT_DISC_SRVC_BY_UUID, &srvc_disc_param) != GATT_SUCCESS)
+        {
+            GATT_TRACE_ERROR("%s() - ccc service error", __FUNCTION__);
+            gatt_config_ccc_complete(p_clcb);
+        }
+        break;
+
+    case GATT_SVC_CHANGED_CHARACTERISTIC: /* discover service change char */
+        srvc_disc_param.s_handle = 1;
+        srvc_disc_param.e_handle = p_clcb->e_handle;
+        srvc_disc_param.service.len = 2;
+        srvc_disc_param.service.uu.uuid16 = GATT_UUID_GATT_SRV_CHGD;
+        if (GATTC_Discover (p_clcb->conn_id, GATT_DISC_CHAR, &srvc_disc_param) != GATT_SUCCESS)
+        {
+            GATT_TRACE_ERROR("%s() - ccc char error", __FUNCTION__);
+            gatt_config_ccc_complete(p_clcb);
+        }
+        break;
+
+    case GATT_SVC_CHANGED_DESCRIPTOR: /* discover service change ccc */
+        srvc_disc_param.s_handle = p_clcb->s_handle;
+        srvc_disc_param.e_handle = p_clcb->e_handle;
+        if (GATTC_Discover (p_clcb->conn_id, GATT_DISC_CHAR_DSCPT, &srvc_disc_param) != GATT_SUCCESS)
+        {
+            GATT_TRACE_ERROR("%s() - ccc char descriptor error", __FUNCTION__);
+            gatt_config_ccc_complete(p_clcb);
+        }
+        break;
+
+    case GATT_SVC_CHANGED_CONFIGURE_CCCD: /* write ccc */
+        ccc_value.handle = p_clcb->s_handle;
+        ccc_value.len = 2;
+        ccc_value.value[0] = GATT_CLT_CONFIG_INDICATION;
+        if (GATTC_Write (p_clcb->conn_id, GATT_WRITE, &ccc_value) != GATT_SUCCESS)
+        {
+            GATT_TRACE_ERROR("%s() - write ccc error", __FUNCTION__);
+            gatt_config_ccc_complete(p_clcb);
+        }
+        break;
+    }
+}
+
+/*******************************************************************************
+**
+** Function         GATT_ConfigServiceChangeCCC
+**
+** Description      Configure service change indication on remote device
+**
+** Returns          none
+**
+*******************************************************************************/
+void GATT_ConfigServiceChangeCCC (BD_ADDR remote_bda, BOOLEAN enable, tBT_TRANSPORT transport)
+{
+    UINT16              conn_id = GATT_INVALID_CONN_ID;
+    tGATT_PROFILE_CLCB   *p_clcb = gatt_profile_find_clcb_by_bd_addr (remote_bda, transport);
+
+    if (p_clcb == NULL)
+        p_clcb = gatt_profile_clcb_alloc (0, remote_bda, transport);
+
+    if (p_clcb == NULL)
+        return;
+
+    if (GATT_GetConnIdIfConnected (gatt_cb.gatt_if, remote_bda, &p_clcb->conn_id, transport))
+    {
+        p_clcb->connected = TRUE;
+    }
+    /* hold the link here */
+    GATT_Connect(gatt_cb.gatt_if, remote_bda, TRUE, transport);
+    p_clcb->ccc_stage = GATT_SVC_CHANGED_CONNECTING;
+
+    if (!p_clcb->connected)
+    {
+        /* wait for connection */
+        return;
+    }
+
+    p_clcb->ccc_stage ++;
+    gatt_cl_start_config_ccc(p_clcb);
+}
+
 #endif  /* BLE_INCLUDED */
diff --git a/stack/gatt/gatt_int.h b/stack/gatt/gatt_int.h
index 663aa7d..d49c6e1 100644
--- a/stack/gatt/gatt_int.h
+++ b/stack/gatt/gatt_int.h
@@ -462,6 +462,11 @@
     BOOLEAN         in_use;
 }tGATT_BG_CONN_DEV;
 
+#define GATT_SVC_CHANGED_CONNECTING        1   /* wait for connection */
+#define GATT_SVC_CHANGED_SERVICE           2   /* GATT service discovery */
+#define GATT_SVC_CHANGED_CHARACTERISTIC    3   /* service change char discovery */
+#define GATT_SVC_CHANGED_DESCRIPTOR        4   /* service change CCC discoery */
+#define GATT_SVC_CHANGED_CONFIGURE_CCCD    5   /* config CCC */
 
 typedef struct
 {
@@ -470,6 +475,12 @@
     BOOLEAN connected;
     BD_ADDR bda;
     tBT_TRANSPORT   transport;
+
+    /* GATT service change CCC related variables */
+    UINT8       ccc_stage;
+    UINT8       ccc_result;
+    UINT16      s_handle;
+    UINT16      e_handle;
 }tGATT_PROFILE_CLCB;
 
 typedef struct
@@ -557,8 +568,6 @@
 
 /* from gatt_attr.c */
 extern UINT16 gatt_profile_find_conn_id_by_bd_addr(BD_ADDR bda);
-extern BOOLEAN gatt_profile_clcb_dealloc (UINT16 conn_id);
-extern tGATT_PROFILE_CLCB *gatt_profile_clcb_alloc (UINT16 conn_id, BD_ADDR bda, tBT_TRANSPORT transport);
 
 
 /* Functions provided by att_protocol.c */
diff --git a/stack/include/gatt_api.h b/stack/include/gatt_api.h
index 392b4b2..2c99c02 100644
--- a/stack/include/gatt_api.h
+++ b/stack/include/gatt_api.h
@@ -1169,7 +1169,18 @@
 *******************************************************************************/
     GATT_API extern BOOLEAN GATT_Listen (tGATT_IF gatt_if, BOOLEAN start, BD_ADDR_PTR bd_addr);
 
-
+/*******************************************************************************
+**
+** Function         GATT_ConfigServiceChangeCCC
+**
+** Description      Configure service change indication on remote device
+**
+** Returns          None.
+**
+*******************************************************************************/
+    GATT_API extern void GATT_ConfigServiceChangeCCC (BD_ADDR remote_bda, BOOLEAN enable,
+                                                    tBT_TRANSPORT transport);
+ 
 #ifdef __cplusplus
 
 }