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