1 /* 2 * Copyright (C) 2023 BlueKitchen GmbH 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the copyright holders nor the names of 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 4. Any redistribution, use, or modification is done solely for 17 * personal benefit and not for any commercial purpose or for 18 * monetary gain. 19 * 20 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN 24 * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * Please inquire about commercial licensing options at 34 * [email protected] 35 * 36 */ 37 38 #define BTSTACK_FILE__ "avrcp_cover_art_client.c" 39 40 #include <string.h> 41 42 #include "btstack_debug.h" 43 #include "btstack_event.h" 44 #include "classic/avrcp.h" 45 #include "classic/avrcp_cover_art_client.h" 46 #include "classic/obex.h" 47 #include "hci_event.h" 48 49 #ifdef ENABLE_AVRCP_COVER_ART 50 51 static const hci_event_t avrcp_cover_art_client_connected = { 52 .event_code = HCI_EVENT_AVRCP_META, 53 .subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_ESTABLISHED, 54 .format = "1B22" 55 }; 56 57 static const hci_event_t avrcp_cover_art_client_disconnected = { 58 .event_code = HCI_EVENT_AVRCP_META, 59 .subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_RELEASED, 60 .format = "2" 61 }; 62 63 static const hci_event_t avrcp_cover_art_client_operation_complete = { 64 .event_code = HCI_EVENT_AVRCP_META, 65 .subevent_code = AVRCP_SUBEVENT_COVER_ART_OPERATION_COMPLETE, 66 .format = "21" 67 }; 68 69 // 7163DD54-4A7E-11E2-B47C-0050C2490048 70 static const uint8_t avrcp_cover_art_uuid[] = { 0x71, 0x63, 0xDD, 0x54, 0x4A, 0x7E, 0x11, 0xE2, 0xB4, 0x7C, 0x00, 0x50, 0xC2, 0x49, 0x00, 0x48 }; 71 72 // OBEX types 73 const char * avrcp_cover_art_image_properties_type = "x-bt/img-properties"; 74 const char * avrcp_cover_art_image_type = "x-bt/img-img"; 75 const char * avrcp_cover_art_linked_thumbnail_type = "x-bt/img-thm"; 76 77 static btstack_linked_list_t avrcp_cover_art_client_connections; 78 79 static uint16_t avrcp_cover_art_client_next_cid(void) { 80 static uint16_t cid = 0; 81 cid++; 82 if (cid == 0){ 83 cid = 1; 84 } 85 return cid; 86 } 87 88 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_goep_cid(uint16_t goep_cid){ 89 btstack_linked_list_iterator_t it; 90 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections); 91 while (btstack_linked_list_iterator_has_next(&it)){ 92 avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it); 93 if (connection->goep_cid == goep_cid) { 94 return connection; 95 }; 96 } 97 return NULL; 98 } 99 100 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_avrcp_cid(uint16_t avrcp_cid){ 101 btstack_linked_list_iterator_t it; 102 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections); 103 while (btstack_linked_list_iterator_has_next(&it)){ 104 avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it); 105 if (connection->avrcp_cid == avrcp_cid) { 106 return connection; 107 }; 108 } 109 return NULL; 110 } 111 112 static avrcp_cover_art_client_t * avrcp_cover_art_client_for_cover_art_cid(uint16_t cover_art_cid){ 113 btstack_linked_list_iterator_t it; 114 btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections); 115 while (btstack_linked_list_iterator_has_next(&it)){ 116 avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it); 117 if (connection->cover_art_cid == cover_art_cid) { 118 return connection; 119 }; 120 } 121 return NULL; 122 } 123 124 static void avrcp_cover_art_client_emit_connection_established(btstack_packet_handler_t packet_handler, uint8_t status, 125 bd_addr_t addr, uint16_t avrcp_cid, 126 uint16_t cover_art_cid) { 127 uint8_t buffer[20]; 128 uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_connected, status, addr, avrcp_cid, cover_art_cid); 129 (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size); 130 } 131 132 static void cover_art_client_emit_operation_complete_event(avrcp_cover_art_client_t * cover_art_client, uint8_t status) { 133 uint8_t buffer[20]; 134 uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_operation_complete, cover_art_client->cover_art_cid, status); 135 (*cover_art_client->packet_handler)(HCI_EVENT_PACKET, 0, buffer, size); 136 } 137 138 static void avrcp_cover_art_client_emit_connection_released(btstack_packet_handler_t packet_handler, uint16_t cover_art_cid) { 139 uint8_t buffer[20]; 140 uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_disconnected, cover_art_cid); 141 (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size); 142 } 143 144 static void avrcp_cover_art_finalize_connection(avrcp_cover_art_client_t *cover_art_client) { 145 btstack_assert(cover_art_client != NULL); 146 memset(cover_art_client, 0, sizeof(avrcp_cover_art_client_t)); 147 btstack_linked_list_remove(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client); 148 } 149 150 static void avrcp_cover_art_client_parser_callback_connect(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){ 151 avrcp_cover_art_client_t * client = (avrcp_cover_art_client_t *) user_data; 152 switch (header_id){ 153 case OBEX_HEADER_CONNECTION_ID: 154 if (obex_parser_header_store(client->obex_header_buffer, sizeof(client->obex_header_buffer), total_len, data_offset, data_buffer, data_len) == OBEX_PARSER_HEADER_COMPLETE){ 155 goep_client_set_connection_id(client->goep_cid, big_endian_read_32(client->obex_header_buffer, 0)); 156 } 157 break; 158 default: 159 break; 160 } 161 } 162 163 static void avrcp_cover_art_client_prepare_srm_header(avrcp_cover_art_client_t * cover_art_client){ 164 if (cover_art_client->flow_control_enabled == false){ 165 obex_srm_client_prepare_header(&cover_art_client->obex_srm, cover_art_client->goep_cid); 166 } 167 } 168 169 static void avrcp_cover_art_client_parser_callback_get_operation(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){ 170 avrcp_cover_art_client_t *client = (avrcp_cover_art_client_t *) user_data; 171 switch (header_id) { 172 case OBEX_HEADER_SINGLE_RESPONSE_MODE: 173 obex_parser_header_store(&client->obex_srm.srm_value, 1, total_len, data_offset, data_buffer, data_len); 174 break; 175 case OBEX_HEADER_SINGLE_RESPONSE_MODE_PARAMETER: 176 obex_parser_header_store(&client->obex_srm.srmp_value, 1, total_len, data_offset, data_buffer, data_len); 177 break; 178 case OBEX_HEADER_BODY: 179 case OBEX_HEADER_END_OF_BODY: 180 switch(client->state){ 181 case AVRCP_COVER_ART_W4_OBJECT: 182 client->packet_handler(BIP_DATA_PACKET, client->cover_art_cid, (uint8_t *) data_buffer, data_len); 183 if (data_offset + data_len == total_len){ 184 client->flow_wait_for_user = true; 185 } 186 break; 187 default: 188 btstack_unreachable(); 189 break; 190 } 191 break; 192 default: 193 // ignore other headers 194 break; 195 } 196 } 197 198 static void avrcp_cover_art_client_prepare_get_operation(avrcp_cover_art_client_t * cover_art_client){ 199 obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_GET, avrcp_cover_art_client_parser_callback_get_operation, cover_art_client); 200 obex_srm_client_reset_fields(&cover_art_client->obex_srm); 201 cover_art_client->obex_parser_waiting_for_response = true; 202 } 203 204 static void avrcp_cover_art_client_handle_can_send_now(avrcp_cover_art_client_t * cover_art_client){ 205 switch (cover_art_client->state) { 206 case AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST: 207 // prepare request 208 goep_client_request_create_connect(cover_art_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT); 209 goep_client_header_add_target(cover_art_client->goep_cid, avrcp_cover_art_uuid, 16); 210 // state 211 cover_art_client->state = AVRCP_COVER_ART_W4_CONNECT_RESPONSE; 212 // prepare response 213 obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_CONNECT, 214 avrcp_cover_art_client_parser_callback_connect, cover_art_client); 215 obex_srm_client_init(&cover_art_client->obex_srm); 216 cover_art_client->obex_parser_waiting_for_response = true; 217 // send packet 218 goep_client_execute(cover_art_client->goep_cid); 219 break; 220 case AVRCP_COVER_ART_W2_SEND_GET_OBJECT: 221 goep_client_request_create_get(cover_art_client->goep_cid); 222 if (cover_art_client->first_request){ 223 cover_art_client->first_request = false; 224 obex_srm_client_init(&cover_art_client->obex_srm); 225 avrcp_cover_art_client_prepare_srm_header(cover_art_client); 226 goep_client_header_add_type(cover_art_client->goep_cid, cover_art_client->object_type); 227 if (cover_art_client->image_descriptor != NULL){ 228 goep_client_header_add_variable(cover_art_client->goep_cid, OBEX_HEADER_IMG_DESCRIPTOR, (const uint8_t *) cover_art_client->image_descriptor, (uint16_t) strlen(cover_art_client->image_descriptor)); 229 } 230 uint8_t image_handle_len = btstack_max(7, (uint16_t) strlen(cover_art_client->image_handle)); 231 goep_client_header_add_unicode_prefix(cover_art_client->goep_cid, OBEX_HEADER_IMG_HANDLE, cover_art_client->image_handle, image_handle_len); 232 } 233 // state 234 cover_art_client->state = AVRCP_COVER_ART_W4_OBJECT; 235 cover_art_client->flow_next_triggered = 0; 236 cover_art_client->flow_wait_for_user = 0; 237 // prepare response 238 avrcp_cover_art_client_prepare_get_operation(cover_art_client); 239 // send packet 240 goep_client_execute(cover_art_client->goep_cid); 241 break; 242 case AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST: 243 // prepare request 244 goep_client_request_create_disconnect(cover_art_client->goep_cid); 245 // state 246 cover_art_client->state = AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE; 247 // prepare response 248 obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_DISCONNECT, NULL, cover_art_client); 249 cover_art_client->obex_parser_waiting_for_response = true; 250 // send packet 251 goep_client_execute(cover_art_client->goep_cid); 252 return; 253 default: 254 break; 255 } 256 } 257 258 static void avrcp_cover_art_goep_event_handler(const uint8_t *packet, uint16_t size) { 259 UNUSED(size); 260 uint8_t status; 261 avrcp_cover_art_client_t * cover_art_client; 262 btstack_packet_handler_t packet_handler; 263 uint16_t cover_art_cid; 264 265 switch (hci_event_packet_get_type(packet)) { 266 case HCI_EVENT_GOEP_META: 267 switch (hci_event_goep_meta_get_subevent_code(packet)){ 268 case GOEP_SUBEVENT_CONNECTION_OPENED: 269 cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet)); 270 btstack_assert(cover_art_client != NULL); 271 status = goep_subevent_connection_opened_get_status(packet); 272 if (status){ 273 log_info("connection failed %u", status); 274 avrcp_cover_art_finalize_connection(cover_art_client); 275 avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, status, 276 cover_art_client->addr, 277 cover_art_client->avrcp_cid, 278 cover_art_client->cover_art_cid); 279 } else { 280 log_info("connection established"); 281 cover_art_client->state = AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST; 282 goep_client_request_can_send_now(cover_art_client->goep_cid); 283 } 284 break; 285 case GOEP_SUBEVENT_CONNECTION_CLOSED: 286 cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet)); 287 btstack_assert(cover_art_client != NULL); 288 if (cover_art_client->state > AVRCP_COVER_ART_CONNECTED){ 289 cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_DISCONNECTED); 290 } 291 cover_art_cid = cover_art_client->cover_art_cid; 292 packet_handler = cover_art_client->packet_handler; 293 avrcp_cover_art_finalize_connection(cover_art_client); 294 avrcp_cover_art_client_emit_connection_released(packet_handler, cover_art_cid); 295 break; 296 case GOEP_SUBEVENT_CAN_SEND_NOW: 297 cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_can_send_now_get_goep_cid(packet)); 298 btstack_assert(cover_art_client != NULL); 299 avrcp_cover_art_client_handle_can_send_now(cover_art_client); 300 break; 301 default: 302 break; 303 } 304 break; 305 default: 306 break; 307 } 308 } 309 310 static void avrcp_cover_art_client_goep_data_handler(avrcp_cover_art_client_t * cover_art_client, uint8_t *packet, uint16_t size){ 311 btstack_assert(cover_art_client->obex_parser_waiting_for_response); 312 313 obex_parser_object_state_t parser_state; 314 parser_state = obex_parser_process_data(&cover_art_client->obex_parser, packet, size); 315 if (parser_state == OBEX_PARSER_OBJECT_STATE_COMPLETE){ 316 cover_art_client->obex_parser_waiting_for_response = false; 317 obex_parser_operation_info_t op_info; 318 obex_parser_get_operation_info(&cover_art_client->obex_parser, &op_info); 319 switch (cover_art_client->state){ 320 case AVRCP_COVER_ART_W4_CONNECT_RESPONSE: 321 switch (op_info.response_code) { 322 case OBEX_RESP_SUCCESS: 323 cover_art_client->state = AVRCP_COVER_ART_CONNECTED; 324 avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, 325 ERROR_CODE_SUCCESS, 326 cover_art_client->addr, 327 cover_art_client->avrcp_cid, 328 cover_art_client->cover_art_cid); 329 break; 330 default: 331 log_info("pbap: obex connect failed, result 0x%02x", packet[0]); 332 cover_art_client->state = AVRCP_COVER_ART_INIT; 333 avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, 334 OBEX_CONNECT_FAILED, 335 cover_art_client->addr, 336 cover_art_client->avrcp_cid, 337 cover_art_client->cover_art_cid); 338 break; 339 } 340 break; 341 case AVRCP_COVER_ART_W4_OBJECT: 342 switch (op_info.response_code) { 343 case OBEX_RESP_CONTINUE: 344 obex_srm_client_handle_headers(&cover_art_client->obex_srm); 345 if (obex_srm_client_is_srm_active(&cover_art_client->obex_srm)) { 346 // prepare response 347 avrcp_cover_art_client_prepare_get_operation(cover_art_client); 348 break; 349 } 350 cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT; 351 if (!cover_art_client->flow_control_enabled || !cover_art_client->flow_wait_for_user || 352 cover_art_client->flow_next_triggered) { 353 goep_client_request_can_send_now(cover_art_client->goep_cid); 354 } 355 break; 356 case OBEX_RESP_SUCCESS: 357 cover_art_client->state = AVRCP_COVER_ART_CONNECTED; 358 cover_art_client_emit_operation_complete_event(cover_art_client, ERROR_CODE_SUCCESS); 359 break; 360 default: 361 log_info("unexpected response 0x%02x", packet[0]); 362 cover_art_client->state = AVRCP_COVER_ART_CONNECTED; 363 cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_UNKNOWN_ERROR); 364 break; 365 } 366 break; 367 case AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE: 368 goep_client_disconnect(cover_art_client->goep_cid); 369 break; 370 default: 371 btstack_unreachable(); 372 break; 373 } 374 } 375 } 376 377 static void avrcp_cover_art_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 378 UNUSED(channel); // ok: there is no channel 379 UNUSED(size); // ok: handling own goep events 380 avrcp_cover_art_client_t * cover_art_client; 381 switch (packet_type){ 382 case HCI_EVENT_PACKET: 383 avrcp_cover_art_goep_event_handler(packet, size); 384 break; 385 case GOEP_DATA_PACKET: 386 cover_art_client = avrcp_cover_art_client_for_goep_cid(channel); 387 btstack_assert(cover_art_client != NULL); 388 avrcp_cover_art_client_goep_data_handler(cover_art_client, packet, size); 389 break; 390 default: 391 break; 392 } 393 } 394 395 static uint8_t avrcp_cover_art_client_setup_connection(avrcp_cover_art_client_t * cover_art_client, uint16_t l2cap_psm){ 396 cover_art_client->state = AVRCP_COVER_ART_W4_GOEP_CONNECTION; 397 return goep_client_connect_l2cap(&cover_art_client->goep_client, 398 (l2cap_ertm_config_t *) cover_art_client->ertm_config, 399 cover_art_client->ertm_buffer, 400 cover_art_client->ertm_buffer_size, 401 &avrcp_cover_art_packet_handler, 402 cover_art_client->addr, 403 l2cap_psm, 404 &cover_art_client->goep_cid); 405 } 406 407 static void avrcp_cover_art_handle_sdp_query_complete(avrcp_connection_t * connection, uint8_t status){ 408 avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_avrcp_cid(connection->avrcp_cid); 409 410 if (cover_art_client == NULL) { 411 return; 412 } 413 if (cover_art_client->state != AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE){ 414 return; 415 } 416 417 // l2cap available? 418 if (status == ERROR_CODE_SUCCESS){ 419 if (connection->cover_art_psm == 0){ 420 status = SDP_SERVICE_NOT_FOUND; 421 } 422 } 423 424 if (status == ERROR_CODE_SUCCESS) { 425 // ready to connect 426 cover_art_client->state = AVRCP_COVER_ART_W2_GOEP_CONNECT; 427 status = avrcp_cover_art_client_setup_connection(cover_art_client, connection->cover_art_psm); 428 } 429 430 if (status != ERROR_CODE_SUCCESS){ 431 btstack_packet_handler_t packet_handler = cover_art_client->packet_handler; 432 uint16_t cover_art_cid = cover_art_client->cover_art_cid; 433 uint16_t avrcp_cid = cover_art_client->avrcp_cid; 434 avrcp_cover_art_finalize_connection(cover_art_client); 435 avrcp_cover_art_client_emit_connection_established(packet_handler, status, connection->remote_addr, 436 avrcp_cid, cover_art_cid); 437 } 438 } 439 440 void avrcp_cover_art_client_init(void){ 441 avrcp_register_cover_art_sdp_query_complete_handler(&avrcp_cover_art_handle_sdp_query_complete); 442 } 443 444 uint8_t 445 avrcp_cover_art_client_connect(avrcp_cover_art_client_t *cover_art_client, btstack_packet_handler_t packet_handler, 446 bd_addr_t remote_addr, uint8_t *ertm_buffer, uint32_t ertm_buffer_size, 447 const l2cap_ertm_config_t *ertm_config, uint16_t *avrcp_cover_art_cid) { 448 449 avrcp_connection_t * connection_controller = avrcp_get_connection_for_bd_addr_for_role(AVRCP_CONTROLLER, remote_addr); 450 avrcp_connection_t * connection_target = avrcp_get_connection_for_bd_addr_for_role(AVRCP_TARGET, remote_addr); 451 if ((connection_target == NULL) || (connection_controller == NULL)){ 452 return ERROR_CODE_COMMAND_DISALLOWED; 453 } 454 455 cover_art_client->cover_art_cid = avrcp_cover_art_client_next_cid(); 456 memcpy(cover_art_client->addr, remote_addr, 6); 457 cover_art_client->avrcp_cid = connection_controller->avrcp_cid; 458 cover_art_client->packet_handler = packet_handler; 459 cover_art_client->flow_control_enabled = false; 460 461 // store ERTM config 462 cover_art_client->ertm_config = ertm_config; 463 cover_art_client->ertm_buffer = ertm_buffer; 464 cover_art_client->ertm_buffer_size = ertm_buffer_size; 465 466 if (avrcp_cover_art_cid != NULL){ 467 *avrcp_cover_art_cid = cover_art_client->cover_art_cid; 468 } 469 470 btstack_linked_list_add(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client); 471 472 if (connection_controller->cover_art_psm == 0){ 473 cover_art_client->state = AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE; 474 avrcp_trigger_sdp_query(connection_controller, connection_controller); 475 return ERROR_CODE_SUCCESS; 476 } else { 477 return avrcp_cover_art_client_setup_connection(cover_art_client, connection_controller->cover_art_psm); 478 } 479 } 480 481 static uint8_t avrcp_cover_art_client_get_object(uint16_t avrcp_cover_art_cid, const char * object_type, const char * image_handle, const char * image_descriptor){ 482 avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid); 483 if (cover_art_client == NULL){ 484 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 485 } 486 if (cover_art_client->state != AVRCP_COVER_ART_CONNECTED){ 487 return ERROR_CODE_COMMAND_DISALLOWED; 488 } 489 cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT; 490 cover_art_client->first_request = true; 491 cover_art_client->image_handle = image_handle; 492 cover_art_client->image_descriptor = image_descriptor; 493 cover_art_client->object_type = object_type; 494 goep_client_request_can_send_now(cover_art_client->goep_cid); 495 return ERROR_CODE_SUCCESS; 496 } 497 498 uint8_t avrcp_cover_art_client_get_linked_thumbnail(uint16_t avrcp_cover_art_cid, const char * image_handle){ 499 return avrcp_cover_art_client_get_object(avrcp_cover_art_cid, 500 avrcp_cover_art_linked_thumbnail_type, 501 image_handle, 502 NULL); 503 } 504 505 uint8_t avrcp_cover_art_client_get_image(uint16_t avrcp_cover_art_cid, const char * image_handle, const char * image_descriptor){ 506 return avrcp_cover_art_client_get_object(avrcp_cover_art_cid, 507 avrcp_cover_art_image_type, 508 image_handle, 509 image_descriptor); 510 } 511 512 uint8_t avrcp_cover_art_client_get_image_properties(uint16_t avrcp_cover_art_cid, const char * image_handle){ 513 return avrcp_cover_art_client_get_object(avrcp_cover_art_cid, 514 avrcp_cover_art_image_properties_type, 515 image_handle, 516 NULL); 517 } 518 519 uint8_t avrcp_cover_art_client_disconnect(uint16_t avrcp_cover_art_cid){ 520 avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid); 521 if (cover_art_client == NULL){ 522 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 523 } 524 if (cover_art_client->state < AVRCP_COVER_ART_CONNECTED){ 525 return ERROR_CODE_COMMAND_DISALLOWED; 526 } 527 cover_art_client->state = AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST; 528 goep_client_request_can_send_now(cover_art_client->goep_cid); 529 return ERROR_CODE_SUCCESS; 530 } 531 532 void avrcp_cover_art_client_deinit(void){ 533 avrcp_cover_art_client_connections = NULL; 534 } 535 536 #endif /* ENABLE_AVRCP_COVER_ART */ 537