chromeos-dbus-bindings: Add support for ObjectManager/Properties

When generating D-Bus object proxy classes, add support for D-Bus
Object Manager proxy and properties on D-Bus objects.

BUG=chromium:431737
TEST=FEATURES=test emerge-link chromeos-dbus-bindings

Change-Id: I4d399fc5ed9613e7c51a7b489383fada184e498f
Reviewed-on: https://chromium-review.googlesource.com/232532
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
index 946156b..a4d108e 100644
--- a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
+++ b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc
@@ -84,6 +84,18 @@
     return false;
 
   dict->GetStringWithoutPathExpansion("service_name", &config->service_name);
+
+  base::DictionaryValue* om_dict = nullptr;  // Owned by |dict|.
+  if (dict->GetDictionaryWithoutPathExpansion("object_manager", &om_dict)) {
+    if (!om_dict->GetStringWithoutPathExpansion("name",
+                                                &config->object_manager.name) &&
+        !config->service_name.empty()) {
+      config->object_manager.name = config->service_name + ".ObjectManager";
+    }
+    om_dict->GetStringWithoutPathExpansion("object_path",
+                                           &config->object_manager.object_path);
+  }
+
   return true;
 }
 
diff --git a/chromeos-dbus-bindings/header_generator.h b/chromeos-dbus-bindings/header_generator.h
index 55b1265..1be7d34 100644
--- a/chromeos-dbus-bindings/header_generator.h
+++ b/chromeos-dbus-bindings/header_generator.h
@@ -21,8 +21,25 @@
 struct Interface;
 class  IndentedText;
 
+// General D-Bus service configuration settings used by Adaptor/Proxy code
+// generators.
 struct ServiceConfig {
+  // D-Bus service name to be used when constructing proxy objects.
+  // If omitted (empty), the service name parameter will be added to the
+  // constructor of generated proxy class(es).
   std::string service_name;
+  // Object Manager settings.
+  struct {
+    // The name of the Object Manager class to use. If empty, no object manager
+    // is generated in the proxy code (this also disables property support on
+    // proxy objects).
+    // This is a "fake" name used to generate namespaces and the actual class
+    // name for the object manager proxy. This name has no relationship to the
+    // actual D-Bus properties of the actual object manager.
+    std::string name;
+    // The D-Bus path to Object Manager instance.
+    std::string object_path;
+  } object_manager;
 };
 
 class HeaderGenerator {
diff --git a/chromeos-dbus-bindings/indented_text.cc b/chromeos-dbus-bindings/indented_text.cc
index 5a8ed6f..a261fb4 100644
--- a/chromeos-dbus-bindings/indented_text.cc
+++ b/chromeos-dbus-bindings/indented_text.cc
@@ -41,6 +41,20 @@
   contents_.emplace_back(line, shift + offset_);
 }
 
+void IndentedText::AddLineAndPushOffsetTo(const std::string& line,
+                                          size_t occurrence,
+                                          char c) {
+  AddLine(line);
+  size_t pos = 0;
+  while (occurrence > 0) {
+    pos = line.find(c, pos);
+    CHECK(pos != string::npos);
+    pos++;
+    occurrence--;
+  }
+  PushOffset(pos);
+}
+
 void IndentedText::AddComments(const std::string& doc_string) {
   // Try to retain indentation in the comments. Find the first non-empty line
   // of the comment and find its whitespace indentation prefix.
diff --git a/chromeos-dbus-bindings/indented_text.h b/chromeos-dbus-bindings/indented_text.h
index 701c2e6..42fd744 100644
--- a/chromeos-dbus-bindings/indented_text.h
+++ b/chromeos-dbus-bindings/indented_text.h
@@ -28,6 +28,12 @@
   // Add a line at the current indentation.
   void AddLine(const std::string& line);
   void AddLineWithOffset(const std::string& line, size_t shift);
+  // Adds a line and pushes an offset past the |nth_occurrence| of character |c|
+  // in that line, effectively allowing to align following line to the position
+  // following that character.
+  void AddLineAndPushOffsetTo(const std::string& line,
+                              size_t nth_occurrence,
+                              char c);
 
   // Adds a block of comments.
   void AddComments(const std::string& doc_string);
diff --git a/chromeos-dbus-bindings/indented_text_unittest.cc b/chromeos-dbus-bindings/indented_text_unittest.cc
index d615770..2bb0e1e 100644
--- a/chromeos-dbus-bindings/indented_text_unittest.cc
+++ b/chromeos-dbus-bindings/indented_text_unittest.cc
@@ -52,6 +52,11 @@
   EXPECT_EQ(string(kShift, ' ') + kTestString + "\n", text_.GetContents());
 }
 
+TEST_F(IndentedTextTest, AddLineAndPushOffsetTo) {
+  text_.AddLineAndPushOffsetTo("foo(bar(baz", 2, '(');
+  EXPECT_THAT(GetHistory(), ElementsAre(8));
+}
+
 TEST_F(IndentedTextTest, AddBlock) {
   IndentedText block0;
   const char kTestString[] = "test";
diff --git a/chromeos-dbus-bindings/proxy_generator.cc b/chromeos-dbus-bindings/proxy_generator.cc
index 1c09f33..96f2bc8 100644
--- a/chromeos-dbus-bindings/proxy_generator.cc
+++ b/chromeos-dbus-bindings/proxy_generator.cc
@@ -22,9 +22,20 @@
 namespace chromeos_dbus_bindings {
 
 namespace {
-string GetParamString(const pair<string, string>& param_def) {
-  return StringPrintf("const %s& %s",
-                      param_def.first.c_str(), param_def.second.c_str());
+// Helper struct to encapsulate information about method call parameter during
+// code generation.
+struct ParamDef {
+  ParamDef(const string& param_type, const string& param_name, bool param_ref)
+      : type(param_type), name(param_name), is_const_ref(param_ref) {}
+
+  string type;
+  string name;
+  bool is_const_ref;
+};
+
+string GetParamString(const ParamDef& param_def) {
+  return StringPrintf(param_def.is_const_ref ? "const %s& %s" : "%s* %s",
+                      param_def.type.c_str(), param_def.name.c_str());
 }
 }  // anonymous namespace
 
@@ -42,6 +53,7 @@
   string header_guard = GenerateHeaderGuard(output_file);
   text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str()));
   text.AddLine(StringPrintf("#define %s", header_guard.c_str()));
+  text.AddLine("#include <memory>");
   text.AddLine("#include <string>");
   text.AddLine("#include <vector>");
   text.AddBlankLine();
@@ -52,19 +64,33 @@
   text.AddLine("#include <base/memory/ref_counted.h>");
   text.AddLine("#include <chromeos/any.h>");
   text.AddLine("#include <chromeos/dbus/dbus_method_invoker.h>");
+  text.AddLine("#include <chromeos/dbus/dbus_property.h>");
   text.AddLine("#include <chromeos/dbus/dbus_signal_handler.h>");
   text.AddLine("#include <chromeos/errors/error.h>");
   text.AddLine("#include <chromeos/variant_dictionary.h>");
   text.AddLine("#include <dbus/bus.h>");
   text.AddLine("#include <dbus/message.h>");
+  text.AddLine("#include <dbus/object_manager.h>");
   text.AddLine("#include <dbus/object_path.h>");
   text.AddLine("#include <dbus/object_proxy.h>");
   text.AddBlankLine();
 
+  if (!config.object_manager.name.empty()) {
+    // Add forward-declaration for Object Manager proxy class.
+    NameParser parser{config.object_manager.name};
+    parser.AddOpenNamespaces(&text, false);
+    text.AddLine(StringPrintf("class %s;",
+                              parser.MakeProxyName(false).c_str()));
+    parser.AddCloseNamespaces(&text, false);
+    text.AddBlankLine();
+  }
+
   for (const auto& interface : interfaces) {
     GenerateInterfaceProxy(config, interface, &text);
   }
 
+  ObjectManager::GenerateProxy(config, interfaces, &text);
+
   text.AddLine(StringPrintf("#endif  // %s", header_guard.c_str()));
   return WriteTextToFile(output_file, text);
 }
@@ -86,19 +112,27 @@
   text->AddLineWithOffset("public:", kScopeOffset);
   text->PushOffset(kBlockOffset);
   AddSignalReceiver(interface, text);
+  AddPropertySet(config, interface, text);
   AddConstructor(config, interface, proxy_name, text);
   AddDestructor(proxy_name, text);
   AddReleaseObjectProxy(text);
+  AddGetObjectPath(text);
+  AddGetObjectProxy(text);
+  if (!config.object_manager.name.empty() && !interface.properties.empty())
+    AddPropertyPublicMethods(proxy_name, text);
   if (!interface.signals.empty())
     AddSignalConnectedCallback(text);
   for (const auto& method : interface.methods) {
     AddMethodProxy(method, interface.name, text);
   }
+  AddProperties(config, interface, text);
 
   text->PopOffset();
   text->AddLineWithOffset("private:", kScopeOffset);
 
   text->PushOffset(kBlockOffset);
+  if (!config.object_manager.name.empty() && !interface.properties.empty())
+    AddOnPropertyChanged(text);
   text->AddLine("scoped_refptr<dbus::Bus> bus_;");
   if (config.service_name.empty()) {
     text->AddLine("std::string service_name_;");
@@ -112,11 +146,22 @@
     text->AddLine(StringPrintf("const dbus::ObjectPath object_path_{\"%s\"};",
                                interface.path.c_str()));
   }
+  if (!config.object_manager.name.empty() && !interface.properties.empty()) {
+    text->AddLine("PropertySet* property_set_;");
+    text->AddLine(StringPrintf("base::Callback<void(%s*, const std::string&)> "
+                               "on_property_changed_;",
+                               proxy_name.c_str()));
+  }
   text->AddLine("dbus::ObjectProxy* dbus_object_proxy_;");
   text->AddBlankLine();
 
-  text->AddLine(StringPrintf(
-      "DISALLOW_COPY_AND_ASSIGN(%s);", proxy_name.c_str()));
+  if (!config.object_manager.name.empty() && !interface.properties.empty()) {
+    text->AddLine(StringPrintf(
+        "friend class %s;",
+        NameParser{config.object_manager.name}.MakeProxyName(true).c_str()));
+  }
+  text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);",
+                             proxy_name.c_str()));
   text->PopOffset();
   text->AddLine("};");
 
@@ -133,11 +178,13 @@
                                     const string& class_name,
                                     IndentedText* text) {
   IndentedText block;
-  vector<std::pair<string, string>> args{{"scoped_refptr<dbus::Bus>", "bus"}};
+  vector<ParamDef> args{{"scoped_refptr<dbus::Bus>", "bus", true}};
   if (config.service_name.empty())
-    args.emplace_back("std::string", "service_name");
+    args.emplace_back("std::string", "service_name", true);
   if (interface.path.empty())
-    args.emplace_back("std::string", "object_path");
+    args.emplace_back("dbus::ObjectPath", "object_path", true);
+  if (!config.object_manager.name.empty() && !interface.properties.empty())
+    args.emplace_back("PropertySet", "property_set", false);
 
   if (args.size() == 1) {
     block.AddLine(StringPrintf("%s(%s) :", class_name.c_str(),
@@ -152,12 +199,12 @@
   }
   block.PushOffset(kLineContinuationOffset);
   for (const auto& arg : args) {
-    block.AddLine(StringPrintf("%s_(%s),", arg.second.c_str(),
-                               arg.second.c_str()));
+    block.AddLine(StringPrintf("%s_{%s},", arg.name.c_str(),
+                               arg.name.c_str()));
   }
-  block.AddLine("dbus_object_proxy_(");
+  block.AddLine("dbus_object_proxy_{");
   block.AddLineWithOffset(
-      "bus_->GetObjectProxy(service_name_, object_path_)) {",
+      "bus_->GetObjectProxy(service_name_, object_path_)} {",
       kLineContinuationOffset);
   block.PopOffset();
   if (args.size() > 1)
@@ -170,7 +217,7 @@
     vector<string> param_names;
     for (const auto& arg : args) {
       block.AddLine(StringPrintf("%s,", GetParamString(arg).c_str()));
-      param_names.push_back(arg.second);
+      param_names.push_back(arg.name);
     }
     block.AddLine("SignalReceiver* signal_receiver) :");
     string param_list = chromeos::string_utils::Join(", ", param_names);
@@ -220,15 +267,58 @@
 
 // static
 void ProxyGenerator::AddReleaseObjectProxy(IndentedText* text) {
-  IndentedText block;
-  block.AddLine("void ReleaseObjectProxy(const base::Closure& callback) {");
-  block.PushOffset(kBlockOffset);
-  block.AddLine(
-      "bus_->RemoveObjectProxy(service_name_, object_path_, callback);");
-  block.PopOffset();
-  block.AddLine("}");
-  block.AddBlankLine();
-  text->AddBlock(block);
+  text->AddLine("void ReleaseObjectProxy(const base::Closure& callback) {");
+  text->AddLineWithOffset(
+      "bus_->RemoveObjectProxy(service_name_, object_path_, callback);",
+      kBlockOffset);
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+// static
+void ProxyGenerator::AddGetObjectPath(IndentedText* text) {
+  text->AddLine("const dbus::ObjectPath& GetObjectPath() const {");
+  text->AddLineWithOffset("return object_path_;", kBlockOffset);
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+// static
+void ProxyGenerator::AddGetObjectProxy(IndentedText* text) {
+  text->AddLine("dbus::ObjectProxy* GetObjectProxy() const { "
+                "return dbus_object_proxy_; }");
+  text->AddBlankLine();
+}
+
+// static
+void ProxyGenerator::AddPropertyPublicMethods(const string& class_name,
+                                              IndentedText* text) {
+  text->AddLine("void SetPropertyChangedCallback(");
+  text->AddLineWithOffset(
+      StringPrintf("const base::Callback<void(%s*, "
+                   "const std::string&)>& callback) {", class_name.c_str()),
+      kLineContinuationOffset);
+  text->AddLineWithOffset("on_property_changed_ = callback;", kBlockOffset);
+  text->AddLine("}");
+  text->AddBlankLine();
+
+  text->AddLine("const PropertySet* GetProperties() const "
+                "{ return property_set_; }");
+  text->AddLine("PropertySet* GetProperties() { return property_set_; }");
+  text->AddBlankLine();
+}
+
+// static
+void ProxyGenerator::AddOnPropertyChanged(IndentedText* text) {
+  text->AddLine("void OnPropertyChanged(const std::string& property_name) {");
+  text->PushOffset(kBlockOffset);
+  text->AddLine("if (!on_property_changed_.is_null())");
+  text->PushOffset(kBlockOffset);
+  text->AddLine("on_property_changed_.Run(this, property_name);");
+  text->PopOffset();
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
 }
 
 // static
@@ -304,6 +394,86 @@
 }
 
 // static
+void ProxyGenerator::AddPropertySet(const ServiceConfig& config,
+                                    const Interface& interface,
+                                    IndentedText* text) {
+  // Must have ObjectManager in order for property system to work correctly.
+  if (config.object_manager.name.empty())
+    return;
+
+  IndentedText block;
+  block.AddLine("class PropertySet : public dbus::PropertySet {");
+  block.AddLineWithOffset("public:", kScopeOffset);
+  block.PushOffset(kBlockOffset);
+  block.AddLineAndPushOffsetTo("PropertySet(dbus::ObjectProxy* object_proxy,",
+                               1, '(');
+  block.AddLine("const PropertyChangedCallback& callback)");
+  block.PopOffset();
+  block.PushOffset(kLineContinuationOffset);
+  block.AddLineAndPushOffsetTo(": dbus::PropertySet{object_proxy,", 1, '{');
+  block.AddLine(StringPrintf("\"%s\",", interface.name.c_str()));
+  block.AddLine("callback} {");
+  block.PopOffset();
+  block.PopOffset();
+  block.PushOffset(kBlockOffset);
+  for (const auto& prop : interface.properties) {
+    block.AddLine(
+        StringPrintf("RegisterProperty(\"%s\", &%s);",
+                     prop.name.c_str(),
+                     NameParser{prop.name}.MakeVariableName().c_str()));
+  }
+  block.PopOffset();
+  block.AddLine("}");
+  block.AddBlankLine();
+
+  DbusSignature signature;
+  for (const auto& prop : interface.properties) {
+    string type;
+    CHECK(signature.Parse(prop.type, &type));
+    block.AddLine(
+        StringPrintf("chromeos::dbus_utils::Property<%s> %s;",
+                     type.c_str(),
+                     NameParser{prop.name}.MakeVariableName().c_str()));
+  }
+  block.AddBlankLine();
+
+  block.PopOffset();
+  block.AddLineWithOffset("private:", kScopeOffset);
+  block.AddLineWithOffset("DISALLOW_COPY_AND_ASSIGN(PropertySet);",
+                          kBlockOffset);
+  block.AddLine("};");
+  block.AddBlankLine();
+
+  text->AddBlock(block);
+}
+
+// static
+void ProxyGenerator::AddProperties(const ServiceConfig& config,
+                                   const Interface& interface,
+                                   IndentedText* text) {
+  // Must have ObjectManager in order for property system to work correctly.
+  if (config.object_manager.name.empty())
+    return;
+
+  DbusSignature signature;
+  for (const auto& prop : interface.properties) {
+    string type;
+    CHECK(signature.Parse(prop.type, &type));
+    MakeConstReferenceIfNeeded(&type);
+    string name = NameParser{prop.name}.MakeVariableName();
+    text->AddLine(
+        StringPrintf("%s %s() const {",
+                     type.c_str(),
+                     name.c_str()));
+    text->AddLineWithOffset(
+        StringPrintf("return property_set_->%s.value();", name.c_str()),
+        kBlockOffset);
+    text->AddLine("}");
+    text->AddBlankLine();
+  }
+}
+
+// static
 void ProxyGenerator::AddMethodProxy(const Interface::Method& method,
                                     const string& interface_name,
                                     IndentedText* text) {
@@ -362,6 +532,369 @@
 }
 
 // static
+void ProxyGenerator::ObjectManager::GenerateProxy(
+    const ServiceConfig& config,
+    const std::vector<Interface>& interfaces,
+    IndentedText* text) {
+  if (config.object_manager.name.empty())
+    return;
+
+  NameParser object_manager{config.object_manager.name};
+  object_manager.AddOpenNamespaces(text, false);
+  text->AddBlankLine();
+
+  string class_name = object_manager.type_name + "Proxy";
+  text->AddLine(StringPrintf("class %s : "
+                             "public dbus::ObjectManager::Interface {",
+                             class_name.c_str()));
+  text->AddLineWithOffset("public:", kScopeOffset);
+  text->PushOffset(kBlockOffset);
+
+  AddConstructor(config, class_name, interfaces, text);
+  AddGetObjectManagerProxy(text);
+  for (const auto& itf : interfaces) {
+    AddInterfaceAccessors(itf, text);
+  }
+  text->PopOffset();
+
+  text->AddLineWithOffset("private:", kScopeOffset);
+  text->PushOffset(kBlockOffset);
+  AddOnPropertyChanged(interfaces, text);
+  AddObjectAdded(interfaces, text);
+  AddObjectRemoved(interfaces, text);
+  AddCreateProperties(interfaces, class_name, text);
+  AddDataMembers(interfaces, class_name, text);
+
+  text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);",
+                              class_name.c_str()));
+  text->PopOffset();
+  text->AddLine("};");
+  text->AddBlankLine();
+  object_manager.AddCloseNamespaces(text, false);
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddConstructor(
+    const ServiceConfig& config,
+    const std::string& class_name,
+    const std::vector<Interface>& interfaces,
+    IndentedText* text) {
+  if (config.service_name.empty()) {
+    text->AddLineAndPushOffsetTo(
+        StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus,",
+                     class_name.c_str()),
+        1, '(');
+    text->AddLine("const std::string& service_name)");
+    text->PopOffset();
+  } else {
+    text->AddLine(StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus)",
+                               class_name.c_str()));
+  }
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine(": bus_{bus},");
+  text->PushOffset(kBlockOffset);
+  text->AddLine("dbus_object_manager_{bus->GetObjectManager(");
+  text->PushOffset(kLineContinuationOffset);
+  if (config.service_name.empty()) {
+    text->AddLine("service_name,");
+  } else {
+    text->AddLine(StringPrintf("\"%s\",", config.service_name.c_str()));
+  }
+  text->AddLine(StringPrintf("dbus::ObjectPath{\"%s\"})} {",
+                             config.object_manager.object_path.c_str()));
+  text->PopOffset();
+  text->PopOffset();
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  for (const auto& itf : interfaces) {
+    text->AddLine(
+        StringPrintf("dbus_object_manager_->RegisterInterface(\"%s\", this);",
+                     itf.name.c_str()));
+  }
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddGetObjectManagerProxy(
+    IndentedText* text) {
+  text->AddLine("dbus::ObjectManager* GetObjectManagerProxy() const {");
+  text->AddLineWithOffset("return dbus_object_manager_;", kBlockOffset);
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddInterfaceAccessors(
+    const Interface& interface,
+    IndentedText* text) {
+  NameParser itf_name{interface.name};
+  string map_name = itf_name.MakeVariableName() + "_instances_";
+
+  // GetProxy().
+  text->AddLine(StringPrintf("%s* Get%s(",
+                              itf_name.MakeProxyName(true).c_str(),
+                              itf_name.MakeProxyName(false).c_str()));
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine("const dbus::ObjectPath& object_path) {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  text->AddLine(StringPrintf("auto p = %s.find(object_path);",
+                              map_name.c_str()));
+  text->AddLine(StringPrintf("if (p != %s.end())", map_name.c_str()));
+  text->PushOffset(kBlockOffset);
+  text->AddLine("return p->second.get();");
+  text->PopOffset();
+  text->AddLine("return nullptr;");
+  text->PopOffset();
+  text->AddLine("}");
+
+  // GetInstances().
+  text->AddLine(StringPrintf("std::vector<%s*> Get%sInstances() const {",
+                              itf_name.MakeProxyName(true).c_str(),
+                              itf_name.type_name.c_str()));
+  text->PushOffset(kBlockOffset);
+  text->AddLine(StringPrintf("std::vector<%s*> values;",
+                             itf_name.MakeProxyName(true).c_str()));
+  text->AddLine(StringPrintf("values.reserve(%s.size());", map_name.c_str()));
+  text->AddLine(StringPrintf("for (const auto& pair : %s)", map_name.c_str()));
+  text->AddLineWithOffset("values.push_back(pair.second.get());", kBlockOffset);
+  text->AddLine("return values;");
+  text->PopOffset();
+  text->AddLine("}");
+
+  // SetAddedCallback().
+  text->AddLine(StringPrintf("void Set%sAddedCallback(",
+                              itf_name.type_name.c_str()));
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine(
+      StringPrintf("const base::Callback<void(%s*)>& callback) {",
+                    itf_name.MakeProxyName(true).c_str()));
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  text->AddLine(StringPrintf("on_%s_added_ = callback;",
+                              itf_name.MakeVariableName().c_str()));
+  text->PopOffset();
+  text->AddLine("}");
+
+  // SetRemovedCallback().
+  text->AddLine(StringPrintf("void Set%sRemovedCallback(",
+                              itf_name.type_name.c_str()));
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine("const base::Callback<void(const dbus::ObjectPath&)>& "
+                "callback) {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  text->AddLine(StringPrintf("on_%s_removed_ = callback;",
+                              itf_name.MakeVariableName().c_str()));
+  text->PopOffset();
+  text->AddLine("}");
+
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddOnPropertyChanged(
+    const std::vector<Interface>& interfaces,
+    IndentedText* text) {
+  text->AddLineAndPushOffsetTo("void OnPropertyChanged("
+                               "const dbus::ObjectPath& object_path,",
+                               1, '(');
+  text->AddLine("const std::string& interface_name,");
+  text->AddLine("const std::string& property_name) {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  for (const auto& itf : interfaces) {
+    if (itf.properties.empty())
+      continue;
+
+    NameParser itf_name{itf.name};
+    text->AddLine(StringPrintf("if (interface_name == \"%s\") {",
+                               itf.name.c_str()));
+    text->PushOffset(kBlockOffset);
+    string map_name = itf_name.MakeVariableName() + "_instances_";
+    text->AddLine(StringPrintf("auto p = %s.find(object_path);",
+                               map_name.c_str()));
+    text->AddLine(StringPrintf("if (p == %s.end())", map_name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine("return;");
+    text->PopOffset();
+    text->AddLine("p->second->OnPropertyChanged(property_name);");
+    text->AddLine("return;");
+    text->PopOffset();
+    text->AddLine("}");
+  }
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddObjectAdded(
+    const std::vector<Interface>& interfaces,
+    IndentedText* text) {
+  text->AddLine("void ObjectAdded(");
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine("const dbus::ObjectPath& object_path,");
+  text->AddLine("const std::string& interface_name) override {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  for (const auto& itf : interfaces) {
+    NameParser itf_name{itf.name};
+    string var_name = itf_name.MakeVariableName();
+    text->AddLine(StringPrintf("if (interface_name == \"%s\") {",
+                               itf.name.c_str()));
+    text->PushOffset(kBlockOffset);
+    if (!itf.properties.empty()) {
+      text->AddLine("auto property_set =");
+      text->PushOffset(kLineContinuationOffset);
+      text->AddLine(StringPrintf("static_cast<%s::PropertySet*>(",
+                                 itf_name.MakeProxyName(true).c_str()));
+      text->PushOffset(kLineContinuationOffset);
+      text->AddLine("dbus_object_manager_->GetProperties(object_path, "
+                    "interface_name));");
+      text->PopOffset();
+      text->PopOffset();
+    }
+    text->AddLine(StringPrintf("std::unique_ptr<%s> %s_proxy{",
+                               itf_name.MakeProxyName(true).c_str(),
+                               var_name.c_str()));
+    text->PushOffset(kBlockOffset);
+    string new_instance = StringPrintf("new %s{bus_",
+                                       itf_name.MakeProxyName(true).c_str());
+    if (itf.path.empty())
+      new_instance += ", object_path";
+    if (!itf.properties.empty())
+      new_instance += ", property_set";
+    new_instance += "}";
+    text->AddLine(new_instance);
+    text->PopOffset();
+    text->AddLine("};");
+    text->AddLine(StringPrintf("auto p = %s_instances_.emplace(object_path, "
+                               "std::move(%s_proxy));",
+                               var_name.c_str(), var_name.c_str()));
+    text->AddLine(StringPrintf("if (!on_%s_added_.is_null())",
+                               var_name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine(StringPrintf("on_%s_added_.Run(p.first->second.get());",
+                               var_name.c_str()));
+    text->PopOffset();
+    text->AddLine("return;");
+    text->PopOffset();
+    text->AddLine("}");
+  }
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddObjectRemoved(
+    const std::vector<Interface>& interfaces,
+    IndentedText* text) {
+  text->AddLine("void ObjectRemoved(");
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine("const dbus::ObjectPath& object_path,");
+  text->AddLine("const std::string& interface_name) override {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  for (const auto& itf : interfaces) {
+    NameParser itf_name{itf.name};
+    string var_name = itf_name.MakeVariableName();
+    text->AddLine(StringPrintf("if (interface_name == \"%s\") {",
+                               itf.name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine(StringPrintf("auto p = %s_instances_.find(object_path);",
+                               var_name.c_str()));
+    text->AddLine(StringPrintf("if (p != %s_instances_.end()) {",
+                               var_name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine(StringPrintf("if (!on_%s_removed_.is_null())",
+                               var_name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine(StringPrintf("on_%s_removed_.Run(object_path);",
+                               var_name.c_str()));
+    text->PopOffset();
+    text->AddLine(StringPrintf("%s_instances_.erase(p);",
+                               var_name.c_str()));
+    text->PopOffset();
+    text->AddLine("}");
+    text->AddLine("return;");
+    text->PopOffset();
+    text->AddLine("}");
+  }
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddCreateProperties(
+    const std::vector<Interface>& interfaces,
+    const std::string& class_name,
+    IndentedText* text) {
+  text->AddLine("dbus::PropertySet* CreateProperties(");
+  text->PushOffset(kLineContinuationOffset);
+  text->AddLine("dbus::ObjectProxy* object_proxy,");
+  text->AddLine("const dbus::ObjectPath& object_path,");
+  text->AddLine("const std::string& interface_name) override {");
+  text->PopOffset();
+  text->PushOffset(kBlockOffset);
+  for (const auto& itf : interfaces) {
+    NameParser itf_name{itf.name};
+    text->AddLine(StringPrintf("if (interface_name == \"%s\") {",
+                               itf.name.c_str()));
+    text->PushOffset(kBlockOffset);
+    text->AddLine(StringPrintf("return new %s::PropertySet{",
+                               itf_name.MakeProxyName(true).c_str()));
+    text->PushOffset(kLineContinuationOffset);
+    text->AddLine("object_proxy,");
+    text->AddLineAndPushOffsetTo(
+        StringPrintf("base::Bind(&%s::OnPropertyChanged,",
+                     class_name.c_str()),
+        1, '(');
+    text->AddLine("weak_ptr_factory_.GetWeakPtr(),");
+    text->AddLine("object_path,");
+    text->AddLine("interface_name)");
+    text->PopOffset();
+    text->PopOffset();
+    text->AddLine("};");
+    text->PopOffset();
+    text->AddLine("}");
+  }
+  text->AddLineAndPushOffsetTo("LOG(FATAL) << \"Creating properties for "
+                               "unsupported interface \"", 1, ' ');
+  text->AddLine("<< interface_name;");
+  text->PopOffset();
+  text->AddLine("return nullptr;");
+  text->PopOffset();
+  text->AddLine("}");
+  text->AddBlankLine();
+}
+
+void ProxyGenerator::ObjectManager::AddDataMembers(
+    const std::vector<Interface>& interfaces,
+    const std::string& class_name,
+    IndentedText* text) {
+  text->AddLine("scoped_refptr<dbus::Bus> bus_;");
+  text->AddLine("dbus::ObjectManager* dbus_object_manager_;");
+  for (const auto& itf : interfaces) {
+    NameParser itf_name{itf.name};
+    string var_name = itf_name.MakeVariableName();
+    text->AddLineAndPushOffsetTo("std::map<dbus::ObjectPath,", 1, '<');
+    text->AddLine(StringPrintf("std::unique_ptr<%s>> %s_instances_;",
+                               itf_name.MakeProxyName(true).c_str(),
+                               var_name.c_str()));
+    text->PopOffset();
+    text->AddLine(StringPrintf("base::Callback<void(%s*)> on_%s_added_;",
+                               itf_name.MakeProxyName(true).c_str(),
+                               var_name.c_str()));
+    text->AddLine(StringPrintf("base::Callback<void(const dbus::ObjectPath&)> "
+                               "on_%s_removed_;",
+                               var_name.c_str()));
+  }
+  text->AddLine(
+      StringPrintf("base::WeakPtrFactory<%s> weak_ptr_factory_{this};",
+                   class_name.c_str()));
+  text->AddBlankLine();
+}
+
+// static
 string ProxyGenerator::GetHandlerNameForSignal(const string& signal) {
   return StringPrintf("On%sSignal", signal.c_str());
 }
diff --git a/chromeos-dbus-bindings/proxy_generator.h b/chromeos-dbus-bindings/proxy_generator.h
index c0c909b..7d4549b 100644
--- a/chromeos-dbus-bindings/proxy_generator.h
+++ b/chromeos-dbus-bindings/proxy_generator.h
@@ -54,15 +54,80 @@
   // of the object proxy.
   static void AddReleaseObjectProxy(IndentedText* text);
 
+  // Generates AddGetObjectPath() method.
+  static void AddGetObjectPath(IndentedText* text);
+
+  // Generates GetObjectProxy() method.
+  static void AddGetObjectProxy(IndentedText* text);
+
+  // Generates SetPropertyChangedCallback/GetProperties() methods.
+  static void AddPropertyPublicMethods(const std::string& class_name,
+                                       IndentedText* text);
+
+  // Generates OnPropertyChanged() method.
+  static void AddOnPropertyChanged(IndentedText* text);
+
   // Generates the method signatures for signal receivers.
   static void AddSignalReceiver(const Interface& interface,
                                 IndentedText* text);
 
+  // Generates the property set class to contain interface properties.
+  static void AddPropertySet(const ServiceConfig& config,
+                             const Interface& interface,
+                             IndentedText* text);
+
+  // Generates the property accessors.
+  static void AddProperties(const ServiceConfig& config,
+                            const Interface& interface,
+                            IndentedText* text);
+
   // Generates a native C++ method which calls a D-Bus method on the proxy.
   static void AddMethodProxy(const Interface::Method& interface,
                              const std::string& interface_name,
                              IndentedText* text);
 
+  // Generates the Object Manager proxy class.
+  struct ObjectManager {
+    // Generates the top-level class for Object Manager proxy.
+    static void GenerateProxy(const ServiceConfig& config,
+                              const std::vector<Interface>& interfaces,
+                              IndentedText* text);
+
+    // Generates Object Manager constructor.
+    static void AddConstructor(const ServiceConfig& config,
+                               const std::string& class_name,
+                               const std::vector<Interface>& interfaces,
+                               IndentedText* text);
+
+    // Generates GetObjectManagerProxy() method.
+    static void AddGetObjectManagerProxy(IndentedText* text);
+
+    // Generates code for interface-specific accessor methods
+    static void AddInterfaceAccessors(const Interface& interface,
+                                      IndentedText* text);
+
+    // Generates OnPropertyChanged() method.
+    static void AddOnPropertyChanged(const std::vector<Interface>& interfaces,
+                                     IndentedText* text);
+
+    // Generates ObjectAdded() method.
+    static void AddObjectAdded(const std::vector<Interface>& interfaces,
+                               IndentedText* text);
+
+    // Generates ObjectRemoved() method.
+    static void AddObjectRemoved(const std::vector<Interface>& interfaces,
+                                 IndentedText* text);
+
+    // Generates CreateProperties() method.
+    static void AddCreateProperties(const std::vector<Interface>& interfaces,
+                                    const std::string& class_name,
+                                    IndentedText* text);
+
+    // Generates data members of the class.
+    static void AddDataMembers(const std::vector<Interface>& interfaces,
+                               const std::string& class_name,
+                               IndentedText* text);
+  };
   // Generates the signal handler name for a given signal name.
   static std::string GetHandlerNameForSignal(const std::string& signal);
 
diff --git a/chromeos-dbus-bindings/proxy_generator_unittest.cc b/chromeos-dbus-bindings/proxy_generator_unittest.cc
index e4f596e..d3ebaad 100644
--- a/chromeos-dbus-bindings/proxy_generator_unittest.cc
+++ b/chromeos-dbus-bindings/proxy_generator_unittest.cc
@@ -45,6 +45,7 @@
 const char kSignal2Argument1[] = "as";
 const char kSignal2Argument2[] = "y";
 const char kExpectedContent[] = R"literal_string(
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -55,11 +56,13 @@
 #include <base/memory/ref_counted.h>
 #include <chromeos/any.h>
 #include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/dbus/dbus_property.h>
 #include <chromeos/dbus/dbus_signal_handler.h>
 #include <chromeos/errors/error.h>
 #include <chromeos/variant_dictionary.h>
 #include <dbus/bus.h>
 #include <dbus/message.h>
+#include <dbus/object_manager.h>
 #include <dbus/object_path.h>
 #include <dbus/object_proxy.h>
 
@@ -80,10 +83,10 @@
   TestInterfaceProxy(
       const scoped_refptr<dbus::Bus>& bus,
       const std::string& service_name) :
-          bus_(bus),
-          service_name_(service_name),
-          dbus_object_proxy_(
-              bus_->GetObjectProxy(service_name_, object_path_)) {
+          bus_{bus},
+          service_name_{service_name},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
   }
 
   TestInterfaceProxy(
@@ -120,6 +123,12 @@
     bus_->RemoveObjectProxy(service_name_, object_path_, callback);
   }
 
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
   void OnDBusSignalConnected(
       const std::string& interface,
       const std::string& signal,
@@ -207,12 +216,12 @@
   TestInterface2Proxy(
       const scoped_refptr<dbus::Bus>& bus,
       const std::string& service_name,
-      const std::string& object_path) :
-          bus_(bus),
-          service_name_(service_name),
-          object_path_(object_path),
-          dbus_object_proxy_(
-              bus_->GetObjectProxy(service_name_, object_path_)) {
+      const dbus::ObjectPath& object_path) :
+          bus_{bus},
+          service_name_{service_name},
+          object_path_{object_path},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
   }
 
   ~TestInterface2Proxy() {
@@ -222,6 +231,12 @@
     bus_->RemoveObjectProxy(service_name_, object_path_, callback);
   }
 
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
   bool GetPersonInfo(
       std::string* out_name,
       int32_t* out_age,
@@ -246,10 +261,10 @@
 
 }  // namespace chromium
 }  // namespace org
-
 )literal_string";
 
 const char kExpectedContentWithService[] = R"literal_string(
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -260,11 +275,13 @@
 #include <base/memory/ref_counted.h>
 #include <chromeos/any.h>
 #include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/dbus/dbus_property.h>
 #include <chromeos/dbus/dbus_signal_handler.h>
 #include <chromeos/errors/error.h>
 #include <chromeos/variant_dictionary.h>
 #include <dbus/bus.h>
 #include <dbus/message.h>
+#include <dbus/object_manager.h>
 #include <dbus/object_path.h>
 #include <dbus/object_proxy.h>
 
@@ -280,9 +297,9 @@
   };
 
   TestInterfaceProxy(const scoped_refptr<dbus::Bus>& bus) :
-      bus_(bus),
-      dbus_object_proxy_(
-          bus_->GetObjectProxy(service_name_, object_path_)) {
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
   }
 
   TestInterfaceProxy(
@@ -308,6 +325,12 @@
     bus_->RemoveObjectProxy(service_name_, object_path_, callback);
   }
 
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
   void OnDBusSignalConnected(
       const std::string& interface,
       const std::string& signal,
@@ -340,11 +363,11 @@
  public:
   TestInterface2Proxy(
       const scoped_refptr<dbus::Bus>& bus,
-      const std::string& object_path) :
-          bus_(bus),
-          object_path_(object_path),
-          dbus_object_proxy_(
-              bus_->GetObjectProxy(service_name_, object_path_)) {
+      const dbus::ObjectPath& object_path) :
+          bus_{bus},
+          object_path_{object_path},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
   }
 
   ~TestInterface2Proxy() {
@@ -354,6 +377,12 @@
     bus_->RemoveObjectProxy(service_name_, object_path_, callback);
   }
 
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
  private:
   scoped_refptr<dbus::Bus> bus_;
   const std::string service_name_{"org.chromium.Test"};
@@ -365,9 +394,705 @@
 
 }  // namespace chromium
 }  // namespace org
-
 )literal_string";
 
+const char kExpectedContentWithObjectManager[] = R"literal_string(
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <chromeos/any.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/dbus/dbus_property.h>
+#include <chromeos/dbus/dbus_signal_handler.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+class ObjectManagerProxy;
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::Itf1.
+class Itf1Proxy final {
+ public:
+  class SignalReceiver {
+   public:
+    virtual void OnCloserSignal() {}
+  };
+
+  class PropertySet : public dbus::PropertySet {
+   public:
+    PropertySet(dbus::ObjectProxy* object_proxy,
+                const PropertyChangedCallback& callback)
+        : dbus::PropertySet{object_proxy,
+                            "org.chromium.Itf1",
+                            callback} {
+      RegisterProperty("data", &data);
+    }
+
+    chromeos::dbus_utils::Property<std::string> data;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PropertySet);
+  };
+
+  Itf1Proxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      const std::string& service_name,
+      PropertySet* property_set) :
+          bus_{bus},
+          service_name_{service_name},
+          property_set_{property_set},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  Itf1Proxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      const std::string& service_name,
+      PropertySet* property_set,
+      SignalReceiver* signal_receiver) :
+          Itf1Proxy(bus, service_name, property_set) {
+    chromeos::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.Itf1",
+        "Closer",
+        base::Bind(
+            &SignalReceiver::OnCloserSignal,
+            base::Unretained(signal_receiver)),
+        base::Bind(
+            &Itf1Proxy::OnDBusSignalConnected,
+            base::Unretained(this)));
+  }
+
+  ~Itf1Proxy() {
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  void SetPropertyChangedCallback(
+      const base::Callback<void(Itf1Proxy*, const std::string&)>& callback) {
+    on_property_changed_ = callback;
+  }
+
+  const PropertySet* GetProperties() const { return property_set_; }
+  PropertySet* GetProperties() { return property_set_; }
+
+  void OnDBusSignalConnected(
+      const std::string& interface,
+      const std::string& signal,
+      bool success) {
+    if (!success) {
+      LOG(ERROR)
+          << "Failed to connect to " << interface << "." << signal
+          << " for " << service_name_ << " at "
+          << object_path_.value();
+    }
+  }
+
+  const std::string& data() const {
+    return property_set_->data.value();
+  }
+
+ private:
+  void OnPropertyChanged(const std::string& property_name) {
+    if (!on_property_changed_.is_null())
+      on_property_changed_.Run(this, property_name);
+  }
+
+  scoped_refptr<dbus::Bus> bus_;
+  std::string service_name_;
+  const dbus::ObjectPath object_path_{"/org/chromium/Test/Object"};
+  PropertySet* property_set_;
+  base::Callback<void(Itf1Proxy*, const std::string&)> on_property_changed_;
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  friend class org::chromium::ObjectManagerProxy;
+  DISALLOW_COPY_AND_ASSIGN(Itf1Proxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::Itf2.
+class Itf2Proxy final {
+ public:
+  class PropertySet : public dbus::PropertySet {
+   public:
+    PropertySet(dbus::ObjectProxy* object_proxy,
+                const PropertyChangedCallback& callback)
+        : dbus::PropertySet{object_proxy,
+                            "org.chromium.Itf2",
+                            callback} {
+    }
+
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PropertySet);
+  };
+
+  Itf2Proxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      const std::string& service_name,
+      const dbus::ObjectPath& object_path) :
+          bus_{bus},
+          service_name_{service_name},
+          object_path_{object_path},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~Itf2Proxy() {
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  std::string service_name_;
+  dbus::ObjectPath object_path_;
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(Itf2Proxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+class ObjectManagerProxy : public dbus::ObjectManager::Interface {
+ public:
+  ObjectManagerProxy(const scoped_refptr<dbus::Bus>& bus,
+                     const std::string& service_name)
+      : bus_{bus},
+        dbus_object_manager_{bus->GetObjectManager(
+            service_name,
+            dbus::ObjectPath{"/org/chromium/Test"})} {
+    dbus_object_manager_->RegisterInterface("org.chromium.Itf1", this);
+    dbus_object_manager_->RegisterInterface("org.chromium.Itf2", this);
+  }
+
+  dbus::ObjectManager* GetObjectManagerProxy() const {
+    return dbus_object_manager_;
+  }
+
+  org::chromium::Itf1Proxy* GetItf1Proxy(
+      const dbus::ObjectPath& object_path) {
+    auto p = itf1_instances_.find(object_path);
+    if (p != itf1_instances_.end())
+      return p->second.get();
+    return nullptr;
+  }
+  std::vector<org::chromium::Itf1Proxy*> GetItf1Instances() const {
+    std::vector<org::chromium::Itf1Proxy*> values;
+    values.reserve(itf1_instances_.size());
+    for (const auto& pair : itf1_instances_)
+      values.push_back(pair.second.get());
+    return values;
+  }
+  void SetItf1AddedCallback(
+      const base::Callback<void(org::chromium::Itf1Proxy*)>& callback) {
+    on_itf1_added_ = callback;
+  }
+  void SetItf1RemovedCallback(
+      const base::Callback<void(const dbus::ObjectPath&)>& callback) {
+    on_itf1_removed_ = callback;
+  }
+
+  org::chromium::Itf2Proxy* GetItf2Proxy(
+      const dbus::ObjectPath& object_path) {
+    auto p = itf2_instances_.find(object_path);
+    if (p != itf2_instances_.end())
+      return p->second.get();
+    return nullptr;
+  }
+  std::vector<org::chromium::Itf2Proxy*> GetItf2Instances() const {
+    std::vector<org::chromium::Itf2Proxy*> values;
+    values.reserve(itf2_instances_.size());
+    for (const auto& pair : itf2_instances_)
+      values.push_back(pair.second.get());
+    return values;
+  }
+  void SetItf2AddedCallback(
+      const base::Callback<void(org::chromium::Itf2Proxy*)>& callback) {
+    on_itf2_added_ = callback;
+  }
+  void SetItf2RemovedCallback(
+      const base::Callback<void(const dbus::ObjectPath&)>& callback) {
+    on_itf2_removed_ = callback;
+  }
+
+ private:
+  void OnPropertyChanged(const dbus::ObjectPath& object_path,
+                         const std::string& interface_name,
+                         const std::string& property_name) {
+    if (interface_name == "org.chromium.Itf1") {
+      auto p = itf1_instances_.find(object_path);
+      if (p == itf1_instances_.end())
+        return;
+      p->second->OnPropertyChanged(property_name);
+      return;
+    }
+  }
+
+  void ObjectAdded(
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      auto property_set =
+          static_cast<org::chromium::Itf1Proxy::PropertySet*>(
+              dbus_object_manager_->GetProperties(object_path, interface_name));
+      std::unique_ptr<org::chromium::Itf1Proxy> itf1_proxy{
+        new org::chromium::Itf1Proxy{bus_, property_set}
+      };
+      auto p = itf1_instances_.emplace(object_path, std::move(itf1_proxy));
+      if (!on_itf1_added_.is_null())
+        on_itf1_added_.Run(p.first->second.get());
+      return;
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      std::unique_ptr<org::chromium::Itf2Proxy> itf2_proxy{
+        new org::chromium::Itf2Proxy{bus_, object_path}
+      };
+      auto p = itf2_instances_.emplace(object_path, std::move(itf2_proxy));
+      if (!on_itf2_added_.is_null())
+        on_itf2_added_.Run(p.first->second.get());
+      return;
+    }
+  }
+
+  void ObjectRemoved(
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      auto p = itf1_instances_.find(object_path);
+      if (p != itf1_instances_.end()) {
+        if (!on_itf1_removed_.is_null())
+          on_itf1_removed_.Run(object_path);
+        itf1_instances_.erase(p);
+      }
+      return;
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      auto p = itf2_instances_.find(object_path);
+      if (p != itf2_instances_.end()) {
+        if (!on_itf2_removed_.is_null())
+          on_itf2_removed_.Run(object_path);
+        itf2_instances_.erase(p);
+      }
+      return;
+    }
+  }
+
+  dbus::PropertySet* CreateProperties(
+      dbus::ObjectProxy* object_proxy,
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      return new org::chromium::Itf1Proxy::PropertySet{
+          object_proxy,
+          base::Bind(&ObjectManagerProxy::OnPropertyChanged,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     object_path,
+                     interface_name)
+      };
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      return new org::chromium::Itf2Proxy::PropertySet{
+          object_proxy,
+          base::Bind(&ObjectManagerProxy::OnPropertyChanged,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     object_path,
+                     interface_name)
+      };
+    }
+    LOG(FATAL) << "Creating properties for unsupported interface "
+               << interface_name;
+    return nullptr;
+  }
+
+  scoped_refptr<dbus::Bus> bus_;
+  dbus::ObjectManager* dbus_object_manager_;
+  std::map<dbus::ObjectPath,
+           std::unique_ptr<org::chromium::Itf1Proxy>> itf1_instances_;
+  base::Callback<void(org::chromium::Itf1Proxy*)> on_itf1_added_;
+  base::Callback<void(const dbus::ObjectPath&)> on_itf1_removed_;
+  std::map<dbus::ObjectPath,
+           std::unique_ptr<org::chromium::Itf2Proxy>> itf2_instances_;
+  base::Callback<void(org::chromium::Itf2Proxy*)> on_itf2_added_;
+  base::Callback<void(const dbus::ObjectPath&)> on_itf2_removed_;
+  base::WeakPtrFactory<ObjectManagerProxy> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(ObjectManagerProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+)literal_string";
+
+const char kExpectedContentWithObjectManagerAndServiceName[] = R"literal_string(
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <chromeos/any.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/dbus/dbus_property.h>
+#include <chromeos/dbus/dbus_signal_handler.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+class ObjectManagerProxy;
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::Itf1.
+class Itf1Proxy final {
+ public:
+  class SignalReceiver {
+   public:
+    virtual void OnCloserSignal() {}
+  };
+
+  class PropertySet : public dbus::PropertySet {
+   public:
+    PropertySet(dbus::ObjectProxy* object_proxy,
+                const PropertyChangedCallback& callback)
+        : dbus::PropertySet{object_proxy,
+                            "org.chromium.Itf1",
+                            callback} {
+    }
+
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PropertySet);
+  };
+
+  Itf1Proxy(const scoped_refptr<dbus::Bus>& bus) :
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  Itf1Proxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      SignalReceiver* signal_receiver) :
+          Itf1Proxy(bus) {
+    chromeos::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.Itf1",
+        "Closer",
+        base::Bind(
+            &SignalReceiver::OnCloserSignal,
+            base::Unretained(signal_receiver)),
+        base::Bind(
+            &Itf1Proxy::OnDBusSignalConnected,
+            base::Unretained(this)));
+  }
+
+  ~Itf1Proxy() {
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  void OnDBusSignalConnected(
+      const std::string& interface,
+      const std::string& signal,
+      bool success) {
+    if (!success) {
+      LOG(ERROR)
+          << "Failed to connect to " << interface << "." << signal
+          << " for " << service_name_ << " at "
+          << object_path_.value();
+    }
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.Test"};
+  const dbus::ObjectPath object_path_{"/org/chromium/Test/Object"};
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(Itf1Proxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::Itf2.
+class Itf2Proxy final {
+ public:
+  class PropertySet : public dbus::PropertySet {
+   public:
+    PropertySet(dbus::ObjectProxy* object_proxy,
+                const PropertyChangedCallback& callback)
+        : dbus::PropertySet{object_proxy,
+                            "org.chromium.Itf2",
+                            callback} {
+    }
+
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PropertySet);
+  };
+
+  Itf2Proxy(
+      const scoped_refptr<dbus::Bus>& bus,
+      const dbus::ObjectPath& object_path) :
+          bus_{bus},
+          object_path_{object_path},
+          dbus_object_proxy_{
+              bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~Itf2Proxy() {
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.Test"};
+  dbus::ObjectPath object_path_;
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(Itf2Proxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+class ObjectManagerProxy : public dbus::ObjectManager::Interface {
+ public:
+  ObjectManagerProxy(const scoped_refptr<dbus::Bus>& bus)
+      : bus_{bus},
+        dbus_object_manager_{bus->GetObjectManager(
+            "org.chromium.Test",
+            dbus::ObjectPath{"/org/chromium/Test"})} {
+    dbus_object_manager_->RegisterInterface("org.chromium.Itf1", this);
+    dbus_object_manager_->RegisterInterface("org.chromium.Itf2", this);
+  }
+
+  dbus::ObjectManager* GetObjectManagerProxy() const {
+    return dbus_object_manager_;
+  }
+
+  org::chromium::Itf1Proxy* GetItf1Proxy(
+      const dbus::ObjectPath& object_path) {
+    auto p = itf1_instances_.find(object_path);
+    if (p != itf1_instances_.end())
+      return p->second.get();
+    return nullptr;
+  }
+  std::vector<org::chromium::Itf1Proxy*> GetItf1Instances() const {
+    std::vector<org::chromium::Itf1Proxy*> values;
+    values.reserve(itf1_instances_.size());
+    for (const auto& pair : itf1_instances_)
+      values.push_back(pair.second.get());
+    return values;
+  }
+  void SetItf1AddedCallback(
+      const base::Callback<void(org::chromium::Itf1Proxy*)>& callback) {
+    on_itf1_added_ = callback;
+  }
+  void SetItf1RemovedCallback(
+      const base::Callback<void(const dbus::ObjectPath&)>& callback) {
+    on_itf1_removed_ = callback;
+  }
+
+  org::chromium::Itf2Proxy* GetItf2Proxy(
+      const dbus::ObjectPath& object_path) {
+    auto p = itf2_instances_.find(object_path);
+    if (p != itf2_instances_.end())
+      return p->second.get();
+    return nullptr;
+  }
+  std::vector<org::chromium::Itf2Proxy*> GetItf2Instances() const {
+    std::vector<org::chromium::Itf2Proxy*> values;
+    values.reserve(itf2_instances_.size());
+    for (const auto& pair : itf2_instances_)
+      values.push_back(pair.second.get());
+    return values;
+  }
+  void SetItf2AddedCallback(
+      const base::Callback<void(org::chromium::Itf2Proxy*)>& callback) {
+    on_itf2_added_ = callback;
+  }
+  void SetItf2RemovedCallback(
+      const base::Callback<void(const dbus::ObjectPath&)>& callback) {
+    on_itf2_removed_ = callback;
+  }
+
+ private:
+  void OnPropertyChanged(const dbus::ObjectPath& object_path,
+                         const std::string& interface_name,
+                         const std::string& property_name) {
+  }
+
+  void ObjectAdded(
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      std::unique_ptr<org::chromium::Itf1Proxy> itf1_proxy{
+        new org::chromium::Itf1Proxy{bus_}
+      };
+      auto p = itf1_instances_.emplace(object_path, std::move(itf1_proxy));
+      if (!on_itf1_added_.is_null())
+        on_itf1_added_.Run(p.first->second.get());
+      return;
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      std::unique_ptr<org::chromium::Itf2Proxy> itf2_proxy{
+        new org::chromium::Itf2Proxy{bus_, object_path}
+      };
+      auto p = itf2_instances_.emplace(object_path, std::move(itf2_proxy));
+      if (!on_itf2_added_.is_null())
+        on_itf2_added_.Run(p.first->second.get());
+      return;
+    }
+  }
+
+  void ObjectRemoved(
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      auto p = itf1_instances_.find(object_path);
+      if (p != itf1_instances_.end()) {
+        if (!on_itf1_removed_.is_null())
+          on_itf1_removed_.Run(object_path);
+        itf1_instances_.erase(p);
+      }
+      return;
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      auto p = itf2_instances_.find(object_path);
+      if (p != itf2_instances_.end()) {
+        if (!on_itf2_removed_.is_null())
+          on_itf2_removed_.Run(object_path);
+        itf2_instances_.erase(p);
+      }
+      return;
+    }
+  }
+
+  dbus::PropertySet* CreateProperties(
+      dbus::ObjectProxy* object_proxy,
+      const dbus::ObjectPath& object_path,
+      const std::string& interface_name) override {
+    if (interface_name == "org.chromium.Itf1") {
+      return new org::chromium::Itf1Proxy::PropertySet{
+          object_proxy,
+          base::Bind(&ObjectManagerProxy::OnPropertyChanged,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     object_path,
+                     interface_name)
+      };
+    }
+    if (interface_name == "org.chromium.Itf2") {
+      return new org::chromium::Itf2Proxy::PropertySet{
+          object_proxy,
+          base::Bind(&ObjectManagerProxy::OnPropertyChanged,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     object_path,
+                     interface_name)
+      };
+    }
+    LOG(FATAL) << "Creating properties for unsupported interface "
+               << interface_name;
+    return nullptr;
+  }
+
+  scoped_refptr<dbus::Bus> bus_;
+  dbus::ObjectManager* dbus_object_manager_;
+  std::map<dbus::ObjectPath,
+           std::unique_ptr<org::chromium::Itf1Proxy>> itf1_instances_;
+  base::Callback<void(org::chromium::Itf1Proxy*)> on_itf1_added_;
+  base::Callback<void(const dbus::ObjectPath&)> on_itf1_removed_;
+  std::map<dbus::ObjectPath,
+           std::unique_ptr<org::chromium::Itf2Proxy>> itf2_instances_;
+  base::Callback<void(org::chromium::Itf2Proxy*)> on_itf2_added_;
+  base::Callback<void(const dbus::ObjectPath&)> on_itf2_removed_;
+  base::WeakPtrFactory<ObjectManagerProxy> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(ObjectManagerProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+)literal_string";
 }  // namespace
 
 class ProxyGeneratorTest : public Test {
@@ -456,4 +1181,53 @@
       << kExpectedContentWithService << "...within content...\n" << contents;
 }
 
+TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithObjectManager) {
+  Interface interface;
+  interface.name = "org.chromium.Itf1";
+  interface.path = "/org/chromium/Test/Object";
+  interface.signals.emplace_back(kSignal1Name);
+  interface.properties.emplace_back("data", "s", "read");
+  Interface interface2;
+  interface2.name = "org.chromium.Itf2";
+  vector<Interface> interfaces{interface, interface2};
+  base::FilePath output_path = temp_dir_.path().Append("output3.h");
+  ServiceConfig config;
+  config.object_manager.name = "org.chromium.ObjectManager";
+  config.object_manager.object_path = "/org/chromium/Test";
+  EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path));
+  string contents;
+  EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
+  // The header guards contain the (temporary) filename, so we search for
+  // the content we need within the string.
+  EXPECT_NE(string::npos, contents.find(kExpectedContentWithObjectManager))
+      << "Expected to find the following content...\n"
+      << kExpectedContentWithObjectManager << "...within content...\n"
+      << contents;
+}
+
+TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithObjectManagerAndServiceName) {
+  Interface interface;
+  interface.name = "org.chromium.Itf1";
+  interface.path = "/org/chromium/Test/Object";
+  interface.signals.emplace_back(kSignal1Name);
+  Interface interface2;
+  interface2.name = "org.chromium.Itf2";
+  vector<Interface> interfaces{interface, interface2};
+  base::FilePath output_path = temp_dir_.path().Append("output4.h");
+  ServiceConfig config;
+  config.service_name = "org.chromium.Test";
+  config.object_manager.name = "org.chromium.ObjectManager";
+  config.object_manager.object_path = "/org/chromium/Test";
+  EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path));
+  string contents;
+  EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
+  // The header guards contain the (temporary) filename, so we search for
+  // the content we need within the string.
+  EXPECT_NE(string::npos,
+            contents.find(kExpectedContentWithObjectManagerAndServiceName))
+      << "Expected to find the following content...\n"
+      << kExpectedContentWithObjectManagerAndServiceName
+      << "...within content...\n" << contents;
+}
+
 }  // namespace chromeos_dbus_bindings