1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 "dbus/test_service.h" 6 7 #include "base/bind.h" 8 #include "base/test/test_timeouts.h" 9 #include "base/threading/platform_thread.h" 10 #include "dbus/bus.h" 11 #include "dbus/exported_object.h" 12 #include "dbus/message.h" 13 #include "dbus/object_manager.h" 14 #include "dbus/object_path.h" 15 #include "dbus/property.h" 16 17 namespace { 18 19 void EmptyCallback(bool /* success */) { 20 } 21 22 } // namespace 23 24 namespace dbus { 25 26 // Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set, PerformAction, 27 // GetManagedObjects. 28 const int TestService::kNumMethodsToExport = 9; 29 30 TestService::Options::Options() 31 : request_ownership_options(Bus::REQUIRE_PRIMARY) { 32 } 33 34 TestService::Options::~Options() { 35 } 36 37 TestService::TestService(const Options& options) 38 : base::Thread("TestService"), 39 request_ownership_options_(options.request_ownership_options), 40 dbus_task_runner_(options.dbus_task_runner), 41 on_name_obtained_(false, false), 42 num_exported_methods_(0) { 43 } 44 45 TestService::~TestService() { 46 Stop(); 47 } 48 49 bool TestService::StartService() { 50 base::Thread::Options thread_options; 51 thread_options.message_loop_type = base::MessageLoop::TYPE_IO; 52 return StartWithOptions(thread_options); 53 } 54 55 bool TestService::WaitUntilServiceIsStarted() { 56 const base::TimeDelta timeout(TestTimeouts::action_max_timeout()); 57 // Wait until the ownership of the service name is obtained. 58 return on_name_obtained_.TimedWait(timeout); 59 } 60 61 void TestService::ShutdownAndBlock() { 62 message_loop()->PostTask( 63 FROM_HERE, 64 base::Bind(&TestService::ShutdownAndBlockInternal, 65 base::Unretained(this))); 66 } 67 68 bool TestService::HasDBusThread() { 69 return bus_->HasDBusThread(); 70 } 71 72 void TestService::ShutdownAndBlockInternal() { 73 if (HasDBusThread()) 74 bus_->ShutdownOnDBusThreadAndBlock(); 75 else 76 bus_->ShutdownAndBlock(); 77 } 78 79 void TestService::SendTestSignal(const std::string& message) { 80 message_loop()->PostTask( 81 FROM_HERE, 82 base::Bind(&TestService::SendTestSignalInternal, 83 base::Unretained(this), 84 message)); 85 } 86 87 void TestService::SendTestSignalFromRoot(const std::string& message) { 88 message_loop()->PostTask( 89 FROM_HERE, 90 base::Bind(&TestService::SendTestSignalFromRootInternal, 91 base::Unretained(this), 92 message)); 93 } 94 95 void TestService::SendTestSignalInternal(const std::string& message) { 96 Signal signal("org.chromium.TestInterface", "Test"); 97 MessageWriter writer(&signal); 98 writer.AppendString(message); 99 exported_object_->SendSignal(&signal); 100 } 101 102 void TestService::SendTestSignalFromRootInternal(const std::string& message) { 103 Signal signal("org.chromium.TestInterface", "Test"); 104 MessageWriter writer(&signal); 105 writer.AppendString(message); 106 107 bus_->RequestOwnership("org.chromium.TestService", 108 request_ownership_options_, 109 base::Bind(&TestService::OnOwnership, 110 base::Unretained(this), 111 base::Bind(&EmptyCallback))); 112 113 // Use "/" just like dbus-send does. 114 ExportedObject* root_object = bus_->GetExportedObject(ObjectPath("/")); 115 root_object->SendSignal(&signal); 116 } 117 118 void TestService::RequestOwnership(base::Callback<void(bool)> callback) { 119 message_loop()->PostTask( 120 FROM_HERE, 121 base::Bind(&TestService::RequestOwnershipInternal, 122 base::Unretained(this), 123 callback)); 124 } 125 126 void TestService::RequestOwnershipInternal( 127 base::Callback<void(bool)> callback) { 128 bus_->RequestOwnership("org.chromium.TestService", 129 request_ownership_options_, 130 base::Bind(&TestService::OnOwnership, 131 base::Unretained(this), 132 callback)); 133 } 134 135 void TestService::OnOwnership(base::Callback<void(bool)> callback, 136 const std::string& service_name, 137 bool success) { 138 has_ownership_ = success; 139 LOG_IF(ERROR, !success) << "Failed to own: " << service_name; 140 callback.Run(success); 141 142 on_name_obtained_.Signal(); 143 } 144 145 void TestService::ReleaseOwnership(base::Closure callback) { 146 bus_->GetDBusTaskRunner()->PostTask( 147 FROM_HERE, 148 base::Bind(&TestService::ReleaseOwnershipInternal, 149 base::Unretained(this), 150 callback)); 151 } 152 153 void TestService::ReleaseOwnershipInternal( 154 base::Closure callback) { 155 bus_->ReleaseOwnership("org.chromium.TestService"); 156 has_ownership_ = false; 157 158 bus_->GetOriginTaskRunner()->PostTask( 159 FROM_HERE, 160 callback); 161 } 162 163 void TestService::OnExported(const std::string& interface_name, 164 const std::string& method_name, 165 bool success) { 166 if (!success) { 167 LOG(ERROR) << "Failed to export: " << interface_name << "." 168 << method_name; 169 // Returning here will make WaitUntilServiceIsStarted() to time out 170 // and return false. 171 return; 172 } 173 174 ++num_exported_methods_; 175 if (num_exported_methods_ == kNumMethodsToExport) { 176 // As documented in exported_object.h, the service name should be 177 // requested after all methods are exposed. 178 bus_->RequestOwnership("org.chromium.TestService", 179 request_ownership_options_, 180 base::Bind(&TestService::OnOwnership, 181 base::Unretained(this), 182 base::Bind(&EmptyCallback))); 183 } 184 } 185 186 void TestService::Run(base::MessageLoop* message_loop) { 187 Bus::Options bus_options; 188 bus_options.bus_type = Bus::SESSION; 189 bus_options.connection_type = Bus::PRIVATE; 190 bus_options.dbus_task_runner = dbus_task_runner_; 191 bus_ = new Bus(bus_options); 192 193 exported_object_ = bus_->GetExportedObject( 194 ObjectPath("/org/chromium/TestObject")); 195 196 int num_methods = 0; 197 exported_object_->ExportMethod( 198 "org.chromium.TestInterface", 199 "Echo", 200 base::Bind(&TestService::Echo, 201 base::Unretained(this)), 202 base::Bind(&TestService::OnExported, 203 base::Unretained(this))); 204 ++num_methods; 205 206 exported_object_->ExportMethod( 207 "org.chromium.TestInterface", 208 "SlowEcho", 209 base::Bind(&TestService::SlowEcho, 210 base::Unretained(this)), 211 base::Bind(&TestService::OnExported, 212 base::Unretained(this))); 213 ++num_methods; 214 215 exported_object_->ExportMethod( 216 "org.chromium.TestInterface", 217 "AsyncEcho", 218 base::Bind(&TestService::AsyncEcho, 219 base::Unretained(this)), 220 base::Bind(&TestService::OnExported, 221 base::Unretained(this))); 222 ++num_methods; 223 224 exported_object_->ExportMethod( 225 "org.chromium.TestInterface", 226 "BrokenMethod", 227 base::Bind(&TestService::BrokenMethod, 228 base::Unretained(this)), 229 base::Bind(&TestService::OnExported, 230 base::Unretained(this))); 231 ++num_methods; 232 233 exported_object_->ExportMethod( 234 "org.chromium.TestInterface", 235 "PerformAction", 236 base::Bind(&TestService::PerformAction, 237 base::Unretained(this)), 238 base::Bind(&TestService::OnExported, 239 base::Unretained(this))); 240 ++num_methods; 241 242 exported_object_->ExportMethod( 243 kPropertiesInterface, 244 kPropertiesGetAll, 245 base::Bind(&TestService::GetAllProperties, 246 base::Unretained(this)), 247 base::Bind(&TestService::OnExported, 248 base::Unretained(this))); 249 ++num_methods; 250 251 exported_object_->ExportMethod( 252 kPropertiesInterface, 253 kPropertiesGet, 254 base::Bind(&TestService::GetProperty, 255 base::Unretained(this)), 256 base::Bind(&TestService::OnExported, 257 base::Unretained(this))); 258 ++num_methods; 259 260 exported_object_->ExportMethod( 261 kPropertiesInterface, 262 kPropertiesSet, 263 base::Bind(&TestService::SetProperty, 264 base::Unretained(this)), 265 base::Bind(&TestService::OnExported, 266 base::Unretained(this))); 267 ++num_methods; 268 269 exported_object_manager_ = bus_->GetExportedObject( 270 ObjectPath("/org/chromium/TestService")); 271 272 exported_object_manager_->ExportMethod( 273 kObjectManagerInterface, 274 kObjectManagerGetManagedObjects, 275 base::Bind(&TestService::GetManagedObjects, 276 base::Unretained(this)), 277 base::Bind(&TestService::OnExported, 278 base::Unretained(this))); 279 ++num_methods; 280 281 // Just print an error message as we don't want to crash tests. 282 // Tests will fail at a call to WaitUntilServiceIsStarted(). 283 if (num_methods != kNumMethodsToExport) { 284 LOG(ERROR) << "The number of methods does not match"; 285 } 286 message_loop->Run(); 287 } 288 289 void TestService::Echo(MethodCall* method_call, 290 ExportedObject::ResponseSender response_sender) { 291 MessageReader reader(method_call); 292 std::string text_message; 293 if (!reader.PopString(&text_message)) { 294 response_sender.Run(scoped_ptr<Response>()); 295 return; 296 } 297 298 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 299 MessageWriter writer(response.get()); 300 writer.AppendString(text_message); 301 response_sender.Run(response.Pass()); 302 } 303 304 void TestService::SlowEcho(MethodCall* method_call, 305 ExportedObject::ResponseSender response_sender) { 306 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); 307 Echo(method_call, response_sender); 308 } 309 310 void TestService::AsyncEcho(MethodCall* method_call, 311 ExportedObject::ResponseSender response_sender) { 312 // Schedule a call to Echo() to send an asynchronous response after we return. 313 message_loop()->PostDelayedTask(FROM_HERE, 314 base::Bind(&TestService::Echo, 315 base::Unretained(this), 316 method_call, 317 response_sender), 318 TestTimeouts::tiny_timeout()); 319 } 320 321 void TestService::BrokenMethod(MethodCall* method_call, 322 ExportedObject::ResponseSender response_sender) { 323 response_sender.Run(scoped_ptr<Response>()); 324 } 325 326 327 void TestService::GetAllProperties( 328 MethodCall* method_call, 329 ExportedObject::ResponseSender response_sender) { 330 MessageReader reader(method_call); 331 std::string interface; 332 if (!reader.PopString(&interface)) { 333 response_sender.Run(scoped_ptr<Response>()); 334 return; 335 } 336 337 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 338 MessageWriter writer(response.get()); 339 340 AddPropertiesToWriter(&writer); 341 342 response_sender.Run(response.Pass()); 343 } 344 345 void TestService::GetProperty(MethodCall* method_call, 346 ExportedObject::ResponseSender response_sender) { 347 MessageReader reader(method_call); 348 std::string interface; 349 if (!reader.PopString(&interface)) { 350 response_sender.Run(scoped_ptr<Response>()); 351 return; 352 } 353 354 std::string name; 355 if (!reader.PopString(&name)) { 356 response_sender.Run(scoped_ptr<Response>()); 357 return; 358 } 359 360 if (name == "Name") { 361 // Return the previous value for the "Name" property: 362 // Variant<"TestService"> 363 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 364 MessageWriter writer(response.get()); 365 366 writer.AppendVariantOfString("TestService"); 367 368 response_sender.Run(response.Pass()); 369 } else if (name == "Version") { 370 // Return a new value for the "Version" property: 371 // Variant<20> 372 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 373 MessageWriter writer(response.get()); 374 375 writer.AppendVariantOfInt16(20); 376 377 response_sender.Run(response.Pass()); 378 } else if (name == "Methods") { 379 // Return the previous value for the "Methods" property: 380 // Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]> 381 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 382 MessageWriter writer(response.get()); 383 MessageWriter variant_writer(NULL); 384 MessageWriter variant_array_writer(NULL); 385 386 writer.OpenVariant("as", &variant_writer); 387 variant_writer.OpenArray("s", &variant_array_writer); 388 variant_array_writer.AppendString("Echo"); 389 variant_array_writer.AppendString("SlowEcho"); 390 variant_array_writer.AppendString("AsyncEcho"); 391 variant_array_writer.AppendString("BrokenMethod"); 392 variant_writer.CloseContainer(&variant_array_writer); 393 writer.CloseContainer(&variant_writer); 394 395 response_sender.Run(response.Pass()); 396 } else if (name == "Objects") { 397 // Return the previous value for the "Objects" property: 398 // Variant<[objectpath:"/TestObjectPath"]> 399 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 400 MessageWriter writer(response.get()); 401 MessageWriter variant_writer(NULL); 402 MessageWriter variant_array_writer(NULL); 403 404 writer.OpenVariant("ao", &variant_writer); 405 variant_writer.OpenArray("o", &variant_array_writer); 406 variant_array_writer.AppendObjectPath(ObjectPath("/TestObjectPath")); 407 variant_writer.CloseContainer(&variant_array_writer); 408 writer.CloseContainer(&variant_writer); 409 410 response_sender.Run(response.Pass()); 411 } else if (name == "Bytes") { 412 // Return the previous value for the "Bytes" property: 413 // Variant<[0x54, 0x65, 0x73, 0x74]> 414 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 415 MessageWriter writer(response.get()); 416 MessageWriter variant_writer(NULL); 417 MessageWriter variant_array_writer(NULL); 418 419 writer.OpenVariant("ay", &variant_writer); 420 const uint8 bytes[] = { 0x54, 0x65, 0x73, 0x74 }; 421 variant_writer.AppendArrayOfBytes(bytes, sizeof(bytes)); 422 writer.CloseContainer(&variant_writer); 423 424 response_sender.Run(response.Pass()); 425 } else { 426 // Return error. 427 response_sender.Run(scoped_ptr<Response>()); 428 return; 429 } 430 } 431 432 void TestService::SetProperty(MethodCall* method_call, 433 ExportedObject::ResponseSender response_sender) { 434 MessageReader reader(method_call); 435 std::string interface; 436 if (!reader.PopString(&interface)) { 437 response_sender.Run(scoped_ptr<Response>()); 438 return; 439 } 440 441 std::string name; 442 if (!reader.PopString(&name)) { 443 response_sender.Run(scoped_ptr<Response>()); 444 return; 445 } 446 447 if (name != "Name") { 448 response_sender.Run(scoped_ptr<Response>()); 449 return; 450 } 451 452 std::string value; 453 if (!reader.PopVariantOfString(&value)) { 454 response_sender.Run(scoped_ptr<Response>()); 455 return; 456 } 457 458 SendPropertyChangedSignal(value); 459 460 response_sender.Run(Response::FromMethodCall(method_call)); 461 } 462 463 void TestService::PerformAction( 464 MethodCall* method_call, 465 ExportedObject::ResponseSender response_sender) { 466 MessageReader reader(method_call); 467 std::string action; 468 ObjectPath object_path; 469 if (!reader.PopString(&action) || !reader.PopObjectPath(&object_path)) { 470 response_sender.Run(scoped_ptr<Response>()); 471 return; 472 } 473 474 if (action == "AddObject") 475 AddObject(object_path); 476 else if (action == "RemoveObject") 477 RemoveObject(object_path); 478 else if (action == "ReleaseOwnership") { 479 ReleaseOwnership(base::Bind(&TestService::PerformActionResponse, 480 base::Unretained(this), 481 method_call, response_sender)); 482 return; 483 } else if (action == "Ownership") { 484 ReleaseOwnership(base::Bind(&TestService::OwnershipReleased, 485 base::Unretained(this), 486 method_call, response_sender)); 487 return; 488 } 489 490 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 491 response_sender.Run(response.Pass()); 492 } 493 494 void TestService::PerformActionResponse( 495 MethodCall* method_call, 496 ExportedObject::ResponseSender response_sender) { 497 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 498 response_sender.Run(response.Pass()); 499 } 500 501 void TestService::OwnershipReleased( 502 MethodCall* method_call, 503 ExportedObject::ResponseSender response_sender) { 504 RequestOwnership(base::Bind(&TestService::OwnershipRegained, 505 base::Unretained(this), 506 method_call, response_sender)); 507 } 508 509 510 void TestService::OwnershipRegained( 511 MethodCall* method_call, 512 ExportedObject::ResponseSender response_sender, 513 bool success) { 514 PerformActionResponse(method_call, response_sender); 515 } 516 517 518 void TestService::GetManagedObjects( 519 MethodCall* method_call, 520 ExportedObject::ResponseSender response_sender) { 521 scoped_ptr<Response> response = Response::FromMethodCall(method_call); 522 MessageWriter writer(response.get()); 523 524 // The managed objects response is a dictionary of object paths identifying 525 // the object(s) with a dictionary of strings identifying the interface(s) 526 // they implement and then a dictionary of property values. 527 // 528 // Thus this looks something like: 529 // 530 // { 531 // "/org/chromium/TestObject": { 532 // "org.chromium.TestInterface": { /* Properties */ } 533 // } 534 // } 535 536 537 MessageWriter array_writer(NULL); 538 MessageWriter dict_entry_writer(NULL); 539 MessageWriter object_array_writer(NULL); 540 MessageWriter object_dict_entry_writer(NULL); 541 542 writer.OpenArray("{oa{sa{sv}}}", &array_writer); 543 544 array_writer.OpenDictEntry(&dict_entry_writer); 545 dict_entry_writer.AppendObjectPath(ObjectPath("/org/chromium/TestObject")); 546 dict_entry_writer.OpenArray("{sa{sv}}", &object_array_writer); 547 548 object_array_writer.OpenDictEntry(&object_dict_entry_writer); 549 object_dict_entry_writer.AppendString("org.chromium.TestInterface"); 550 AddPropertiesToWriter(&object_dict_entry_writer); 551 object_array_writer.CloseContainer(&object_dict_entry_writer); 552 553 dict_entry_writer.CloseContainer(&object_array_writer); 554 555 array_writer.CloseContainer(&dict_entry_writer); 556 writer.CloseContainer(&array_writer); 557 558 response_sender.Run(response.Pass()); 559 } 560 561 void TestService::AddPropertiesToWriter(MessageWriter* writer) { 562 // The properties response is a dictionary of strings identifying the 563 // property and a variant containing the property value. We return all 564 // of the properties, thus the response is: 565 // 566 // { 567 // "Name": Variant<"TestService">, 568 // "Version": Variant<10>, 569 // "Methods": Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]>, 570 // "Objects": Variant<[objectpath:"/TestObjectPath"]> 571 // "Bytes": Variant<[0x54, 0x65, 0x73, 0x74]> 572 // } 573 574 MessageWriter array_writer(NULL); 575 MessageWriter dict_entry_writer(NULL); 576 MessageWriter variant_writer(NULL); 577 MessageWriter variant_array_writer(NULL); 578 579 writer->OpenArray("{sv}", &array_writer); 580 581 array_writer.OpenDictEntry(&dict_entry_writer); 582 dict_entry_writer.AppendString("Name"); 583 dict_entry_writer.AppendVariantOfString("TestService"); 584 array_writer.CloseContainer(&dict_entry_writer); 585 586 array_writer.OpenDictEntry(&dict_entry_writer); 587 dict_entry_writer.AppendString("Version"); 588 dict_entry_writer.AppendVariantOfInt16(10); 589 array_writer.CloseContainer(&dict_entry_writer); 590 591 array_writer.OpenDictEntry(&dict_entry_writer); 592 dict_entry_writer.AppendString("Methods"); 593 dict_entry_writer.OpenVariant("as", &variant_writer); 594 variant_writer.OpenArray("s", &variant_array_writer); 595 variant_array_writer.AppendString("Echo"); 596 variant_array_writer.AppendString("SlowEcho"); 597 variant_array_writer.AppendString("AsyncEcho"); 598 variant_array_writer.AppendString("BrokenMethod"); 599 variant_writer.CloseContainer(&variant_array_writer); 600 dict_entry_writer.CloseContainer(&variant_writer); 601 array_writer.CloseContainer(&dict_entry_writer); 602 603 array_writer.OpenDictEntry(&dict_entry_writer); 604 dict_entry_writer.AppendString("Objects"); 605 dict_entry_writer.OpenVariant("ao", &variant_writer); 606 variant_writer.OpenArray("o", &variant_array_writer); 607 variant_array_writer.AppendObjectPath(ObjectPath("/TestObjectPath")); 608 variant_writer.CloseContainer(&variant_array_writer); 609 dict_entry_writer.CloseContainer(&variant_writer); 610 array_writer.CloseContainer(&dict_entry_writer); 611 612 array_writer.OpenDictEntry(&dict_entry_writer); 613 dict_entry_writer.AppendString("Bytes"); 614 dict_entry_writer.OpenVariant("ay", &variant_writer); 615 const uint8 bytes[] = { 0x54, 0x65, 0x73, 0x74 }; 616 variant_writer.AppendArrayOfBytes(bytes, sizeof(bytes)); 617 dict_entry_writer.CloseContainer(&variant_writer); 618 array_writer.CloseContainer(&dict_entry_writer); 619 620 writer->CloseContainer(&array_writer); 621 } 622 623 void TestService::AddObject(const ObjectPath& object_path) { 624 message_loop()->PostTask( 625 FROM_HERE, 626 base::Bind(&TestService::AddObjectInternal, 627 base::Unretained(this), 628 object_path)); 629 } 630 631 void TestService::AddObjectInternal(const ObjectPath& object_path) { 632 Signal signal(kObjectManagerInterface, kObjectManagerInterfacesAdded); 633 MessageWriter writer(&signal); 634 writer.AppendObjectPath(object_path); 635 636 MessageWriter array_writer(NULL); 637 MessageWriter dict_entry_writer(NULL); 638 639 writer.OpenArray("{sa{sv}}", &array_writer); 640 array_writer.OpenDictEntry(&dict_entry_writer); 641 dict_entry_writer.AppendString("org.chromium.TestInterface"); 642 AddPropertiesToWriter(&dict_entry_writer); 643 array_writer.CloseContainer(&dict_entry_writer); 644 writer.CloseContainer(&array_writer); 645 646 exported_object_manager_->SendSignal(&signal); 647 } 648 649 void TestService::RemoveObject(const ObjectPath& object_path) { 650 message_loop()->PostTask(FROM_HERE, 651 base::Bind(&TestService::RemoveObjectInternal, 652 base::Unretained(this), 653 object_path)); 654 } 655 656 void TestService::RemoveObjectInternal(const ObjectPath& object_path) { 657 Signal signal(kObjectManagerInterface, kObjectManagerInterfacesRemoved); 658 MessageWriter writer(&signal); 659 660 writer.AppendObjectPath(object_path); 661 662 std::vector<std::string> interfaces; 663 interfaces.push_back("org.chromium.TestInterface"); 664 writer.AppendArrayOfStrings(interfaces); 665 666 exported_object_manager_->SendSignal(&signal); 667 } 668 669 void TestService::SendPropertyChangedSignal(const std::string& name) { 670 message_loop()->PostTask( 671 FROM_HERE, 672 base::Bind(&TestService::SendPropertyChangedSignalInternal, 673 base::Unretained(this), 674 name)); 675 } 676 677 void TestService::SendPropertyChangedSignalInternal(const std::string& name) { 678 Signal signal(kPropertiesInterface, kPropertiesChanged); 679 MessageWriter writer(&signal); 680 writer.AppendString("org.chromium.TestInterface"); 681 682 MessageWriter array_writer(NULL); 683 MessageWriter dict_entry_writer(NULL); 684 685 writer.OpenArray("{sv}", &array_writer); 686 array_writer.OpenDictEntry(&dict_entry_writer); 687 dict_entry_writer.AppendString("Name"); 688 dict_entry_writer.AppendVariantOfString(name); 689 array_writer.CloseContainer(&dict_entry_writer); 690 writer.CloseContainer(&array_writer); 691 692 exported_object_->SendSignal(&signal); 693 } 694 695 } // namespace dbus 696