Coverage Report

Created: 2026-04-09 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/adhd/cras/src/server/cras_bt_player.c
Line
Count
Source
1
/* Copyright 2016 The ChromiumOS Authors
2
 * Use of this source code is governed by a BSD-style license that can be
3
 * found in the LICENSE file.
4
 */
5
#include "cras/src/server/cras_bt_player.h"
6
7
#include <dbus/dbus.h>
8
#include <errno.h>
9
#include <stdbool.h>
10
#include <stdio.h>
11
#include <stdlib.h>
12
#include <string.h>
13
#include <strings.h>
14
#include <syslog.h>
15
16
#include "cras/src/server/cras_bt_adapter.h"
17
#include "cras/src/server/cras_bt_constants.h"
18
#include "cras/src/server/cras_dbus_util.h"
19
#include "cras/src/server/cras_utf8.h"
20
#include "third_party/strlcpy/strlcpy.h"
21
22
/* Object to hold current metadata. This is not a full list of what BlueZ/MPRIS
23
 * supports but a subset because Chromium only provides the following.
24
 */
25
struct cras_bt_player_metadata {
26
  char title[CRAS_PLAYER_METADATA_SIZE_MAX];
27
  char artist[CRAS_PLAYER_METADATA_SIZE_MAX];
28
  char album[CRAS_PLAYER_METADATA_SIZE_MAX];
29
  int64_t length;
30
};
31
32
/* Object to register as media player so that bluetoothd will report hardware
33
 * volume from device through bt_transport. Properties of the player are defined
34
 * in BlueZ's media API.
35
 */
36
struct cras_bt_player {
37
  const char* object_path;
38
  char* playback_status;
39
  char* identity;
40
  const char* loop_status;
41
  struct cras_bt_player_metadata* metadata;
42
  int64_t position;
43
  bool can_go_next;
44
  bool can_go_prev;
45
  bool can_play;
46
  bool can_pause;
47
  bool can_control;
48
  bool shuffle;
49
  void (*message_cb)(const char* message);
50
};
51
52
static void cras_bt_on_player_registered(DBusPendingCall* pending_call,
53
0
                                         void* data) {
54
0
  DBusMessage* reply;
55
56
0
  reply = dbus_pending_call_steal_reply(pending_call);
57
0
  dbus_pending_call_unref(pending_call);
58
59
0
  if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
60
0
    syslog(LOG_WARNING, "RegisterPlayer returned error: %s",
61
0
           dbus_message_get_error_name(reply));
62
0
    dbus_message_unref(reply);
63
0
    return;
64
0
  }
65
66
0
  dbus_message_unref(reply);
67
0
}
68
69
/* Note that player properties will be used mostly for AVRCP qualification and
70
 * not for normal use cases. The corresponding media events won't be routed by
71
 * CRAS until we have a plan to provide general system API to handle media
72
 * control.
73
 */
74
static struct cras_bt_player player = {
75
    .object_path = CRAS_DEFAULT_PLAYER,
76
    .playback_status = NULL,
77
    .identity = NULL,
78
    .loop_status = "None",
79
    .shuffle = 0,
80
    .metadata = NULL,
81
    .position = 0,
82
    .can_go_next = 0,
83
    .can_go_prev = 0,
84
    .can_play = 0,
85
    .can_pause = 0,
86
    .can_control = 0,
87
    .message_cb = NULL,
88
};
89
90
int cras_bt_register_player(DBusConnection* conn,
91
0
                            const struct cras_bt_adapter* adapter) {
92
0
  const char* adapter_path;
93
0
  DBusMessage* method_call;
94
0
  DBusMessageIter message_iter, dict;
95
0
  DBusPendingCall* pending_call;
96
97
0
  adapter_path = cras_bt_adapter_object_path(adapter);
98
0
  method_call = dbus_message_new_method_call(
99
0
      BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "RegisterPlayer");
100
0
  if (!method_call) {
101
0
    return -ENOMEM;
102
0
  }
103
104
0
  dbus_message_iter_init_append(method_call, &message_iter);
105
0
  dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
106
0
                                 &player.object_path);
107
108
0
  dbus_message_iter_open_container(
109
0
      &message_iter, DBUS_TYPE_ARRAY,
110
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
111
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
112
0
      &dict);
113
114
0
  append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
115
0
                   DBUS_TYPE_STRING_AS_STRING, &player.playback_status);
116
0
  append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
117
0
                   DBUS_TYPE_STRING_AS_STRING, &player.identity);
118
0
  append_key_value(&dict, "LoopStatus", DBUS_TYPE_STRING,
119
0
                   DBUS_TYPE_STRING_AS_STRING, &player.loop_status);
120
0
  append_key_value(&dict, "Position", DBUS_TYPE_INT64,
121
0
                   DBUS_TYPE_INT64_AS_STRING, &player.position);
122
0
  append_key_value(&dict, "Shuffle", DBUS_TYPE_BOOLEAN,
123
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.shuffle);
124
0
  append_key_value(&dict, "CanGoNext", DBUS_TYPE_BOOLEAN,
125
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_go_next);
126
0
  append_key_value(&dict, "CanGoPrevious", DBUS_TYPE_BOOLEAN,
127
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_go_prev);
128
0
  append_key_value(&dict, "CanPlay", DBUS_TYPE_BOOLEAN,
129
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_play);
130
0
  append_key_value(&dict, "CanPause", DBUS_TYPE_BOOLEAN,
131
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_pause);
132
0
  append_key_value(&dict, "CanControl", DBUS_TYPE_BOOLEAN,
133
0
                   DBUS_TYPE_BOOLEAN_AS_STRING, &player.can_control);
134
135
0
  dbus_message_iter_close_container(&message_iter, &dict);
136
137
0
  if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
138
0
                                       DBUS_TIMEOUT_USE_DEFAULT)) {
139
0
    dbus_message_unref(method_call);
140
0
    return -ENOMEM;
141
0
  }
142
143
0
  dbus_message_unref(method_call);
144
0
  if (!pending_call) {
145
0
    return -EIO;
146
0
  }
147
148
0
  if (!dbus_pending_call_set_notify(pending_call, cras_bt_on_player_registered,
149
0
                                    &player, NULL)) {
150
0
    dbus_pending_call_cancel(pending_call);
151
0
    dbus_pending_call_unref(pending_call);
152
0
    return -ENOMEM;
153
0
  }
154
0
  return 0;
155
0
}
156
157
static void cras_bt_on_player_unregistered(DBusPendingCall* pending_call,
158
0
                                           void* data) {
159
0
  DBusMessage* reply;
160
161
0
  reply = dbus_pending_call_steal_reply(pending_call);
162
0
  dbus_pending_call_unref(pending_call);
163
164
0
  if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
165
0
    syslog(LOG_WARNING, "UnregisterPlayer returned error: %s",
166
0
           dbus_message_get_error_name(reply));
167
0
  }
168
169
0
  dbus_message_unref(reply);
170
0
}
171
int cras_bt_unregister_player(DBusConnection* conn,
172
0
                              const struct cras_bt_adapter* adapter) {
173
0
  const char* adapter_path;
174
0
  DBusMessage* method_call;
175
0
  DBusPendingCall* pending_call;
176
177
0
  adapter_path = cras_bt_adapter_object_path(adapter);
178
0
  method_call = dbus_message_new_method_call(
179
0
      BLUEZ_SERVICE, adapter_path, BLUEZ_INTERFACE_MEDIA, "UnregisterPlayer");
180
0
  if (!method_call) {
181
0
    return -ENOMEM;
182
0
  }
183
184
0
  if (!dbus_message_append_args(method_call, DBUS_TYPE_OBJECT_PATH,
185
0
                                &player.object_path, DBUS_TYPE_INVALID)) {
186
0
    dbus_message_unref(method_call);
187
0
    return -ENOMEM;
188
0
  }
189
190
0
  if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
191
0
                                       DBUS_TIMEOUT_USE_DEFAULT)) {
192
0
    dbus_message_unref(method_call);
193
0
    return -ENOMEM;
194
0
  }
195
196
0
  dbus_message_unref(method_call);
197
0
  if (!pending_call) {
198
0
    return -EIO;
199
0
  }
200
201
0
  if (!dbus_pending_call_set_notify(
202
0
          pending_call, cras_bt_on_player_unregistered, &player, NULL)) {
203
0
    dbus_pending_call_cancel(pending_call);
204
0
    dbus_pending_call_unref(pending_call);
205
0
    return -ENOMEM;
206
0
  }
207
0
  return 0;
208
0
}
209
210
static DBusHandlerResult cras_bt_player_handle_message(DBusConnection* conn,
211
                                                       DBusMessage* message,
212
0
                                                       void* arg) {
213
0
  const char* msg = dbus_message_get_member(message);
214
215
0
  if (player.message_cb) {
216
0
    player.message_cb(msg);
217
0
  }
218
219
0
  return DBUS_HANDLER_RESULT_HANDLED;
220
0
}
221
222
0
static int cras_bt_player_init() {
223
0
  player.playback_status = calloc(1, CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
224
0
  if (!player.playback_status) {
225
0
    return -ENOMEM;
226
0
  }
227
228
0
  player.identity = calloc(1, CRAS_PLAYER_IDENTITY_SIZE_MAX);
229
0
  if (!player.identity) {
230
0
    goto nomem;
231
0
  }
232
233
0
  strlcpy(player.playback_status, CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT,
234
0
          CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
235
0
  strlcpy(player.identity, CRAS_PLAYER_IDENTITY_DEFAULT,
236
0
          CRAS_PLAYER_IDENTITY_SIZE_MAX);
237
0
  player.position = 0;
238
239
0
  player.metadata = calloc(1, sizeof(*(player.metadata)));
240
0
  if (!player.metadata) {
241
0
    goto nomem;
242
0
  }
243
0
  return 0;
244
0
nomem:
245
0
  free(player.playback_status);
246
0
  free(player.identity);
247
0
  return -ENOMEM;
248
0
}
249
250
static void cras_bt_player_append_metadata_artist(DBusMessageIter* iter,
251
0
                                                  const char* artist) {
252
0
  DBusMessageIter dict, variant, array;
253
0
  const char* artist_key = "xesam:artist";
254
255
0
  dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
256
0
  dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &artist_key);
257
0
  dbus_message_iter_open_container(
258
0
      &dict, DBUS_TYPE_VARIANT,
259
0
      DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, &variant);
260
0
  dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
261
0
                                   DBUS_TYPE_STRING_AS_STRING, &array);
262
0
  dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &artist);
263
0
  dbus_message_iter_close_container(&variant, &array);
264
0
  dbus_message_iter_close_container(&dict, &variant);
265
0
  dbus_message_iter_close_container(iter, &dict);
266
0
}
267
268
static void cras_bt_player_append_metadata(DBusMessageIter* iter,
269
                                           const char* title,
270
                                           const char* artist,
271
                                           const char* album,
272
0
                                           dbus_int64_t length) {
273
0
  DBusMessageIter variant, array;
274
0
  dbus_message_iter_open_container(
275
0
      iter, DBUS_TYPE_VARIANT,
276
0
      DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
277
0
          DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
278
0
              DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
279
0
      &variant);
280
0
  dbus_message_iter_open_container(
281
0
      &variant, DBUS_TYPE_ARRAY,
282
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
283
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
284
0
      &array);
285
0
  if (!is_utf8_string(title)) {
286
0
    syslog(LOG_DEBUG, "Non-utf8 title: %s", title);
287
0
    title = "";
288
0
  }
289
0
  if (!is_utf8_string(album)) {
290
0
    syslog(LOG_DEBUG, "Non-utf8 album: %s", album);
291
0
    album = "";
292
0
  }
293
0
  if (!is_utf8_string(artist)) {
294
0
    syslog(LOG_DEBUG, "Non-utf8 artist: %s", artist);
295
0
    artist = "";
296
0
  }
297
298
0
  append_key_value(&array, "xesam:title", DBUS_TYPE_STRING,
299
0
                   DBUS_TYPE_STRING_AS_STRING, &title);
300
0
  append_key_value(&array, "xesam:album", DBUS_TYPE_STRING,
301
0
                   DBUS_TYPE_STRING_AS_STRING, &album);
302
0
  append_key_value(&array, "mpris:length", DBUS_TYPE_INT64,
303
0
                   DBUS_TYPE_INT64_AS_STRING, &length);
304
0
  cras_bt_player_append_metadata_artist(&array, artist);
305
306
0
  dbus_message_iter_close_container(&variant, &array);
307
0
  dbus_message_iter_close_container(iter, &variant);
308
0
}
309
310
static bool cras_bt_player_parse_metadata(const char* title,
311
                                          const char* album,
312
                                          const char* artist,
313
0
                                          const dbus_int64_t length) {
314
0
  bool require_update = false;
315
316
0
  if (title && strcmp(player.metadata->title, title)) {
317
0
    snprintf(player.metadata->title, CRAS_PLAYER_METADATA_SIZE_MAX, "%s",
318
0
             title);
319
0
    require_update = true;
320
0
  }
321
0
  if (artist && strcmp(player.metadata->artist, artist)) {
322
0
    snprintf(player.metadata->artist, CRAS_PLAYER_METADATA_SIZE_MAX, "%s",
323
0
             artist);
324
0
    require_update = true;
325
0
  }
326
0
  if (album && strcmp(player.metadata->album, album)) {
327
0
    snprintf(player.metadata->album, CRAS_PLAYER_METADATA_SIZE_MAX, "%s",
328
0
             album);
329
0
    require_update = true;
330
0
  }
331
0
  if (length && player.metadata->length != length) {
332
0
    player.metadata->length = length;
333
0
    require_update = true;
334
0
  }
335
336
0
  return require_update;
337
0
}
338
339
0
int cras_bt_player_create(DBusConnection* conn) {
340
0
  static const DBusObjectPathVTable player_vtable = {
341
0
      .message_function = cras_bt_player_handle_message};
342
343
0
  DBusError dbus_error;
344
0
  struct cras_bt_adapter** adapters;
345
0
  size_t num_adapters, i;
346
0
  int ret;
347
348
0
  ret = cras_bt_player_init();
349
0
  if (ret < 0) {
350
0
    return ret;
351
0
  }
352
353
0
  dbus_error_init(&dbus_error);
354
355
0
  if (!dbus_connection_register_object_path(conn, player.object_path,
356
0
                                            &player_vtable, &dbus_error)) {
357
0
    syslog(LOG_WARNING, "Cannot register player %s", player.object_path);
358
0
    dbus_error_free(&dbus_error);
359
0
    return -ENOMEM;
360
0
  }
361
362
0
  num_adapters = cras_bt_adapter_get_list(&adapters);
363
0
  for (i = 0; i < num_adapters; ++i) {
364
0
    cras_bt_register_player(conn, adapters[i]);
365
0
  }
366
0
  free(adapters);
367
0
  return 0;
368
0
}
369
370
0
int cras_bt_player_destroy(DBusConnection* conn) {
371
0
  struct cras_bt_adapter** adapters;
372
0
  size_t num_adapters, i;
373
374
0
  num_adapters = cras_bt_adapter_get_list(&adapters);
375
376
0
  for (i = 0; i < num_adapters; ++i) {
377
0
    cras_bt_unregister_player(conn, adapters[i]);
378
0
  }
379
0
  free(adapters);
380
381
0
  return dbus_connection_unregister_object_path(conn, player.object_path);
382
0
}
383
384
int cras_bt_player_update_playback_status(DBusConnection* conn,
385
0
                                          const char* status) {
386
0
  DBusMessage* msg;
387
0
  DBusMessageIter iter, dict;
388
0
  const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
389
390
0
  if (!player.playback_status) {
391
0
    return -ENXIO;
392
0
  }
393
394
  /* Verify the string value matches one of the possible status defined in
395
   * bluez/profiles/audio/avrcp.c
396
   */
397
0
  if (strcasecmp(status, "stopped") != 0 &&
398
0
      strcasecmp(status, "playing") != 0 && strcasecmp(status, "paused") != 0 &&
399
0
      strcasecmp(status, "forward-seek") != 0 &&
400
0
      strcasecmp(status, "reverse-seek") != 0 &&
401
0
      strcasecmp(status, "error") != 0) {
402
0
    return -EINVAL;
403
0
  }
404
405
0
  if (!strcasecmp(player.playback_status, status)) {
406
0
    return 0;
407
0
  }
408
409
0
  strlcpy(player.playback_status, status, CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
410
411
0
  msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES,
412
0
                                "PropertiesChanged");
413
0
  if (!msg) {
414
0
    return -ENOMEM;
415
0
  }
416
417
0
  dbus_message_iter_init_append(msg, &iter);
418
0
  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface);
419
0
  dbus_message_iter_open_container(
420
0
      &iter, DBUS_TYPE_ARRAY,
421
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
422
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
423
0
      &dict);
424
0
  append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
425
0
                   DBUS_TYPE_STRING_AS_STRING, &status);
426
0
  dbus_message_iter_close_container(&iter, &dict);
427
428
0
  if (!dbus_connection_send(conn, msg, NULL)) {
429
0
    dbus_message_unref(msg);
430
0
    return -ENOMEM;
431
0
  }
432
433
0
  dbus_message_unref(msg);
434
0
  return 0;
435
0
}
436
437
0
int cras_bt_player_update_identity(DBusConnection* conn, const char* identity) {
438
0
  DBusMessage* msg;
439
0
  DBusMessageIter iter, dict;
440
0
  const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
441
442
0
  if (!player.identity) {
443
0
    return -ENXIO;
444
0
  }
445
446
0
  if (!identity) {
447
0
    return -EINVAL;
448
0
  }
449
450
0
  if (strnlen(identity, CRAS_PLAYER_IDENTITY_SIZE_MAX - 1) ==
451
0
      CRAS_PLAYER_IDENTITY_SIZE_MAX - 1) {
452
0
    syslog(LOG_WARNING, "Identity is too long, using default");
453
0
    identity = CRAS_PLAYER_IDENTITY_DEFAULT;
454
0
  }
455
456
0
  if (!is_utf8_string(identity)) {
457
0
    syslog(LOG_DEBUG, "Non-utf8 identity: %s", identity);
458
0
    identity = "";
459
0
  }
460
461
0
  if (!strcasecmp(player.identity, identity)) {
462
0
    return 0;
463
0
  }
464
465
0
  strlcpy(player.identity, identity, CRAS_PLAYER_IDENTITY_SIZE_MAX);
466
467
0
  msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES,
468
0
                                "PropertiesChanged");
469
0
  if (!msg) {
470
0
    return -ENOMEM;
471
0
  }
472
473
0
  dbus_message_iter_init_append(msg, &iter);
474
0
  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface);
475
0
  dbus_message_iter_open_container(
476
0
      &iter, DBUS_TYPE_ARRAY,
477
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
478
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
479
0
      &dict);
480
0
  append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
481
0
                   DBUS_TYPE_STRING_AS_STRING, &identity);
482
0
  dbus_message_iter_close_container(&iter, &dict);
483
484
0
  if (!dbus_connection_send(conn, msg, NULL)) {
485
0
    dbus_message_unref(msg);
486
0
    return -ENOMEM;
487
0
  }
488
489
0
  dbus_message_unref(msg);
490
0
  return 0;
491
0
}
492
493
int cras_bt_player_update_position(DBusConnection* conn,
494
0
                                   const dbus_int64_t position) {
495
0
  DBusMessage* msg;
496
0
  DBusMessageIter iter, dict;
497
0
  const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
498
499
0
  if (position < 0) {
500
0
    return -EINVAL;
501
0
  }
502
503
0
  player.position = position;
504
505
0
  msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES,
506
0
                                "PropertiesChanged");
507
0
  if (!msg) {
508
0
    return -ENOMEM;
509
0
  }
510
511
0
  dbus_message_iter_init_append(msg, &iter);
512
0
  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface);
513
0
  dbus_message_iter_open_container(
514
0
      &iter, DBUS_TYPE_ARRAY,
515
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
516
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
517
0
      &dict);
518
0
  append_key_value(&dict, "Position", DBUS_TYPE_INT64,
519
0
                   DBUS_TYPE_INT64_AS_STRING, &player.position);
520
0
  dbus_message_iter_close_container(&iter, &dict);
521
522
0
  if (!dbus_connection_send(conn, msg, NULL)) {
523
0
    dbus_message_unref(msg);
524
0
    return -ENOMEM;
525
0
  }
526
527
0
  dbus_message_unref(msg);
528
0
  return 0;
529
0
}
530
531
int cras_bt_player_update_metadata(DBusConnection* conn,
532
                                   const char* title,
533
                                   const char* artist,
534
                                   const char* album,
535
0
                                   const dbus_int64_t length) {
536
0
  DBusMessage* msg;
537
0
  DBusMessageIter iter, array, dict;
538
0
  const char* property = "Metadata";
539
0
  const char* playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
540
541
0
  if (!player.metadata) {
542
0
    return -ENXIO;
543
0
  }
544
545
0
  msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER, DBUS_INTERFACE_PROPERTIES,
546
0
                                "PropertiesChanged");
547
0
  if (!msg) {
548
0
    return -ENOMEM;
549
0
  }
550
551
0
  dbus_message_iter_init_append(msg, &iter);
552
0
  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &playerInterface);
553
0
  dbus_message_iter_open_container(
554
0
      &iter, DBUS_TYPE_ARRAY,
555
0
      DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
556
0
          DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
557
0
      &array);
558
0
  dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
559
0
  dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
560
561
0
  if (!cras_bt_player_parse_metadata(title, album, artist, length)) {
562
    // Nothing to update.
563
0
    dbus_message_unref(msg);
564
0
    return 0;
565
0
  }
566
567
0
  cras_bt_player_append_metadata(
568
0
      &dict, player.metadata->title, player.metadata->artist,
569
0
      player.metadata->album, player.metadata->length);
570
571
0
  dbus_message_iter_close_container(&array, &dict);
572
0
  dbus_message_iter_close_container(&iter, &array);
573
574
0
  if (!dbus_connection_send(conn, msg, NULL)) {
575
0
    dbus_message_unref(msg);
576
0
    return -ENOMEM;
577
0
  }
578
579
0
  dbus_message_unref(msg);
580
0
  return 0;
581
0
}