buffet: Move config and buffet state logic into BuffetConfig

Load/Save logic isolated in buffet/buffet_config.* files.
Added BuffetConfig::Change helper to make sure callbacks were called
and changes were saved.

BUG=brillo:1058
TEST='FEATURES=test emerge-gizmo buffet'

Change-Id: Id8f171c2109fe834daef43658abf6881b50b5c7d
Reviewed-on: https://chromium-review.googlesource.com/271343
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/base_api_handler_unittest.cc b/buffet/base_api_handler_unittest.cc
index 011f639..d1de49f 100644
--- a/buffet/base_api_handler_unittest.cc
+++ b/buffet/base_api_handler_unittest.cc
@@ -22,15 +22,15 @@
 class BaseApiHandlerTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    storage_ = std::make_shared<MemStorage>();
     transport_ = std::make_shared<chromeos::http::fake::Transport>();
     command_manager_ = std::make_shared<CommandManager>();
     state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_);
     state_manager_->Startup();
     dev_reg_.reset(new DeviceRegistrationInfo(
         command_manager_, state_manager_,
-        std::unique_ptr<BuffetConfig>{new BuffetConfig}, transport_, storage_,
-        true, nullptr));
+        std::unique_ptr<BuffetConfig>{new BuffetConfig{
+            std::unique_ptr<StorageInterface>{new MemStorage}}},
+        transport_, true));
     handler_.reset(new BaseApiHandler{dev_reg_->AsWeakPtr(), command_manager_});
   }
 
@@ -54,7 +54,6 @@
               command_manager_->FindCommand(id)->GetStatus());
   }
 
-  std::shared_ptr<MemStorage> storage_;
   std::shared_ptr<chromeos::http::fake::Transport> transport_;
   std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
   std::shared_ptr<CommandManager> command_manager_;
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index 7a466d7..a68d772 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -108,6 +108,7 @@
           'sources': [
             'base_api_handler_unittest.cc',
             'buffet_testrunner.cc',
+            'buffet_config_unittest.cc',
             'commands/command_definition_unittest.cc',
             'commands/command_dictionary_unittest.cc',
             'commands/command_instance_unittest.cc',
diff --git a/buffet/buffet_config.cc b/buffet/buffet_config.cc
index a97b654..4d97098 100644
--- a/buffet/buffet_config.cc
+++ b/buffet/buffet_config.cc
@@ -7,6 +7,9 @@
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
 
+#include "buffet/storage_impls.h"
+#include "buffet/storage_interface.h"
+
 namespace {
 
 // TODO(vitalybuka): Remove this when deviceKind is gone from server.
@@ -64,9 +67,21 @@
 const char kModelName[] = "model_name";
 const char kModelId[] = "model_id";
 const char kPollingPeriodMs[] = "polling_period_ms";
+const char kRefreshToken[] = "refresh_token";
+const char kDeviceId[] = "device_id";
+const char kRobotAccount[] = "robot_account";
 
 }  // namespace config_keys
 
+BuffetConfig::BuffetConfig(std::unique_ptr<StorageInterface> storage)
+    : storage_{std::move(storage)} {
+}
+
+BuffetConfig::BuffetConfig(const base::FilePath& state_path)
+    : BuffetConfig{
+          std::unique_ptr<StorageInterface>{new FileStorage{state_path}}} {
+}
+
 void BuffetConfig::Load(const base::FilePath& config_path) {
   chromeos::KeyValueStore store;
   if (store.Load(config_path)) {
@@ -75,6 +90,9 @@
 }
 
 void BuffetConfig::Load(const chromeos::KeyValueStore& store) {
+  Transaction change{this};
+  change.save_ = false;
+
   store.GetString(config_keys::kClientId, &client_id_);
   CHECK(!client_id_.empty());
 
@@ -117,24 +135,105 @@
   store.GetBoolean(config_keys::kLocalDiscoveryEnabled,
                    &local_discovery_enabled_);
   store.GetBoolean(config_keys::kLocalPairingEnabled, &local_pairing_enabled_);
+
+  change.LoadState();
 }
 
-bool BuffetConfig::set_name(const std::string& name) {
+void BuffetConfig::Transaction::LoadState() {
+  if (!config_->storage_)
+    return;
+  auto value = config_->storage_->Load();
+  const base::DictionaryValue* dict = nullptr;
+  if (!value || !value->GetAsDictionary(&dict))
+    return;
+
+  std::string name;
+  if (dict->GetString(config_keys::kName, &name))
+    set_name(name);
+
+  std::string description;
+  if (dict->GetString(config_keys::kDescription, &description))
+    set_description(description);
+
+  std::string location;
+  if (dict->GetString(config_keys::kLocation, &location))
+    set_location(location);
+
+  std::string access_role;
+  if (dict->GetString(config_keys::kLocalAnonymousAccessRole, &access_role))
+    set_local_anonymous_access_role(access_role);
+
+  bool discovery_enabled{false};
+  if (dict->GetBoolean(config_keys::kLocalDiscoveryEnabled, &discovery_enabled))
+    set_local_discovery_enabled(discovery_enabled);
+
+  bool pairing_enabled{false};
+  if (dict->GetBoolean(config_keys::kLocalPairingEnabled, &pairing_enabled))
+    set_local_pairing_enabled(pairing_enabled);
+
+  std::string token;
+  if (dict->GetString(config_keys::kRefreshToken, &token))
+    set_refresh_token(token);
+
+  std::string account;
+  if (dict->GetString(config_keys::kRobotAccount, &account))
+    set_robot_account(account);
+
+  std::string device_id;
+  if (dict->GetString(config_keys::kDeviceId, &device_id))
+    set_device_id(device_id);
+}
+
+bool BuffetConfig::Save() {
+  if (!storage_)
+    return false;
+  base::DictionaryValue dict;
+  dict.SetString(config_keys::kRefreshToken, refresh_token_);
+  dict.SetString(config_keys::kDeviceId, device_id_);
+  dict.SetString(config_keys::kRobotAccount, robot_account_);
+  dict.SetString(config_keys::kName, name_);
+  dict.SetString(config_keys::kDescription, description_);
+  dict.SetString(config_keys::kLocation, location_);
+  dict.SetString(config_keys::kLocalAnonymousAccessRole,
+                 local_anonymous_access_role_);
+  dict.SetBoolean(config_keys::kLocalDiscoveryEnabled,
+                  local_discovery_enabled_);
+  dict.SetBoolean(config_keys::kLocalPairingEnabled, local_pairing_enabled_);
+
+  return storage_->Save(dict);
+}
+
+BuffetConfig::Transaction::~Transaction() {
+  Commit();
+}
+
+bool BuffetConfig::Transaction::set_name(const std::string& name) {
   if (name.empty()) {
     LOG(ERROR) << "Invalid name: " << name;
     return false;
   }
-  name_ = name;
+  config_->name_ = name;
   return true;
 }
 
-bool BuffetConfig::set_local_anonymous_access_role(const std::string& role) {
+bool BuffetConfig::Transaction::set_local_anonymous_access_role(
+    const std::string& role) {
   if (!IsValidAccessRole(role)) {
     LOG(ERROR) << "Invalid role: " << role;
     return false;
   }
-  local_anonymous_access_role_ = role;
+  config_->local_anonymous_access_role_ = role;
   return true;
 }
 
+void BuffetConfig::Transaction::Commit() {
+  if (!config_)
+    return;
+  if (save_)
+    config_->Save();
+  for (const auto& cb : config_->on_changed_)
+    cb.Run(*config_);
+  config_ = nullptr;
+}
+
 }  // namespace buffet
diff --git a/buffet/buffet_config.h b/buffet/buffet_config.h
index 0ebdf8e..0470755 100644
--- a/buffet/buffet_config.h
+++ b/buffet/buffet_config.h
@@ -6,19 +6,78 @@
 #define BUFFET_BUFFET_CONFIG_H_
 
 #include <string>
+#include <vector>
 
+#include <base/callback.h>
 #include <base/files/file_path.h>
+#include <chromeos/errors/error.h>
 #include <chromeos/key_value_store.h>
 
 namespace buffet {
 
-class BuffetConfig {
+class StorageInterface;
+
+// Handles reading buffet config and state files.
+class BuffetConfig final {
  public:
-  BuffetConfig() = default;
+  using OnChangedCallback = base::Callback<void(const BuffetConfig&)>;
+
+  explicit BuffetConfig(std::unique_ptr<StorageInterface> storage);
+
+  explicit BuffetConfig(const base::FilePath& state_path);
+
+  void AddOnChangedCallback(const OnChangedCallback& callback) {
+    on_changed_.push_back(callback);
+    // Force to read current state.
+    callback.Run(*this);
+  }
 
   void Load(const base::FilePath& config_path);
   void Load(const chromeos::KeyValueStore& store);
 
+  // Allows editing of config. Makes sure that callbacks were called and changes
+  // were saved.
+  // User can commit changes by calling Commit method or by destroying the
+  // object.
+  class Transaction final {
+   public:
+    explicit Transaction(BuffetConfig* config) : config_(config) {
+      CHECK(config_);
+    }
+
+    ~Transaction();
+
+    bool set_name(const std::string& name);
+    void set_description(const std::string& description) {
+      config_->description_ = description;
+    }
+    void set_location(const std::string& location) {
+      config_->location_ = location;
+    }
+    bool set_local_anonymous_access_role(const std::string& role);
+    void set_local_discovery_enabled(bool enabled) {
+      config_->local_discovery_enabled_ = enabled;
+    }
+    void set_local_pairing_enabled(bool enabled) {
+      config_->local_pairing_enabled_ = enabled;
+    }
+    void set_device_id(const std::string& id) { config_->device_id_ = id; }
+    void set_refresh_token(const std::string& token) {
+      config_->refresh_token_ = token;
+    }
+    void set_robot_account(const std::string& account) {
+      config_->robot_account_ = account;
+    }
+
+    void Commit();
+
+   private:
+    friend class BuffetConfig;
+    void LoadState();
+    BuffetConfig* config_;
+    bool save_{true};
+  };
+
   const std::string& client_id() const { return client_id_; }
   const std::string& client_secret() const { return client_secret_; }
   const std::string& api_key() const { return api_key_; }
@@ -39,21 +98,13 @@
   bool local_pairing_enabled() const { return local_pairing_enabled_; }
   bool local_discovery_enabled() const { return local_discovery_enabled_; }
 
-  bool set_name(const std::string& name);
-  void set_description(const std::string& description) {
-    description_ = description;
-  }
-  void set_location(const std::string& location) { location_ = location; }
-
-  bool set_local_anonymous_access_role(const std::string& role);
-  void set_local_discovery_enabled(bool enabled) {
-    local_discovery_enabled_ = enabled;
-  }
-  void set_local_pairing_enabled(bool enabled) {
-    local_pairing_enabled_ = enabled;
-  }
+  std::string device_id() const { return device_id_; }
+  std::string refresh_token() const { return refresh_token_; }
+  std::string robot_account() const { return robot_account_; }
 
  private:
+  bool Save();
+
   std::string client_id_{"58855907228.apps.googleusercontent.com"};
   std::string client_secret_{"eHSAREAHrIqPsHBxCE9zPPBi"};
   std::string api_key_{"AIzaSyDSq46gG-AxUnC3zoqD9COIPrjolFsMfMA"};
@@ -71,6 +122,15 @@
   std::string device_kind_{"vendor"};
   uint64_t polling_period_ms_{7000};
 
+  std::string device_id_;
+  std::string refresh_token_;
+  std::string robot_account_;
+
+  // Serialization interface to save and load buffet state.
+  std::unique_ptr<StorageInterface> storage_;
+
+  std::vector<OnChangedCallback> on_changed_;
+
   DISALLOW_COPY_AND_ASSIGN(BuffetConfig);
 };
 
diff --git a/buffet/buffet_config_unittest.cc b/buffet/buffet_config_unittest.cc
new file mode 100644
index 0000000..cec8bbe
--- /dev/null
+++ b/buffet/buffet_config_unittest.cc
@@ -0,0 +1,247 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "buffet/buffet_config.h"
+
+#include <base/bind.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/commands/unittest_utils.h"
+#include "buffet/storage_impls.h"
+
+using testing::_;
+
+namespace buffet {
+
+class BuffetConfigTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    EXPECT_CALL(*this, OnConfigChanged(_))
+        .Times(1);  // Called from AddOnChangedCallback
+
+    std::unique_ptr<StorageInterface> storage{new MemStorage};
+    storage_ = storage.get();
+    config_.reset(new BuffetConfig{std::move(storage)});
+
+    config_->AddOnChangedCallback(
+        base::Bind(&BuffetConfigTest::OnConfigChanged, base::Unretained(this)));
+  }
+
+  MOCK_METHOD1(OnConfigChanged, void(const BuffetConfig&));
+
+  StorageInterface* storage_{nullptr};
+  std::unique_ptr<BuffetConfig> config_;
+  const BuffetConfig default_{nullptr};
+};
+
+TEST_F(BuffetConfigTest, NoStorage) {
+  BuffetConfig config{nullptr};
+  BuffetConfig::Transaction change{&config};
+  change.Commit();
+}
+
+TEST_F(BuffetConfigTest, Defaults) {
+  EXPECT_EQ("58855907228.apps.googleusercontent.com", config_->client_id());
+  EXPECT_EQ("eHSAREAHrIqPsHBxCE9zPPBi", config_->client_secret());
+  EXPECT_EQ("AIzaSyDSq46gG-AxUnC3zoqD9COIPrjolFsMfMA", config_->api_key());
+  EXPECT_EQ("https://accounts.google.com/o/oauth2/", config_->oauth_url());
+  EXPECT_EQ("https://www.googleapis.com/clouddevices/v1/",
+            config_->service_url());
+  EXPECT_EQ("Chromium", config_->oem_name());
+  EXPECT_EQ("Brillo", config_->model_name());
+  EXPECT_EQ("AAAAA", config_->model_id());
+  EXPECT_EQ("vendor", config_->device_kind());
+  EXPECT_EQ(7000, config_->polling_period_ms());
+  EXPECT_EQ("Developer device", config_->name());
+  EXPECT_EQ("", config_->description());
+  EXPECT_EQ("", config_->location());
+  EXPECT_EQ("viewer", config_->local_anonymous_access_role());
+  EXPECT_TRUE(config_->local_pairing_enabled());
+  EXPECT_TRUE(config_->local_discovery_enabled());
+  EXPECT_EQ("", config_->device_id());
+  EXPECT_EQ("", config_->refresh_token());
+  EXPECT_EQ("", config_->robot_account());
+}
+
+TEST_F(BuffetConfigTest, LoadConfig) {
+  chromeos::KeyValueStore config_store;
+  config_store.SetString("client_id", "conf_client_id");
+  config_store.SetString("client_secret", "conf_client_secret");
+  config_store.SetString("api_key", "conf_api_key");
+  config_store.SetString("oauth_url", "conf_oauth_url");
+  config_store.SetString("service_url", "conf_service_url");
+  config_store.SetString("oem_name", "conf_oem_name");
+  config_store.SetString("model_name", "conf_model_name");
+  config_store.SetString("model_id", "ABCDE");
+  config_store.SetString("polling_period_ms", "12345");
+  config_store.SetString("name", "conf_name");
+  config_store.SetString("description", "conf_description");
+  config_store.SetString("location", "conf_location");
+  config_store.SetString("local_anonymous_access_role", "user");
+  config_store.SetBoolean("local_pairing_enabled", false);
+  config_store.SetBoolean("local_discovery_enabled", false);
+
+  // Following will be ignored.
+  config_store.SetString("device_kind", "conf_device_kind");
+  config_store.SetString("device_id", "conf_device_id");
+  config_store.SetString("refresh_token", "conf_refresh_token");
+  config_store.SetString("robot_account", "conf_robot_account");
+
+  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
+  config_->Load(config_store);
+
+  EXPECT_EQ("conf_client_id", config_->client_id());
+  EXPECT_EQ("conf_client_secret", config_->client_secret());
+  EXPECT_EQ("conf_api_key", config_->api_key());
+  EXPECT_EQ("conf_oauth_url", config_->oauth_url());
+  EXPECT_EQ("conf_service_url", config_->service_url());
+  EXPECT_EQ("conf_oem_name", config_->oem_name());
+  EXPECT_EQ("conf_model_name", config_->model_name());
+  EXPECT_EQ("ABCDE", config_->model_id());
+  EXPECT_EQ("developmentBoard", config_->device_kind());
+  EXPECT_EQ(12345, config_->polling_period_ms());
+  EXPECT_EQ("conf_name", config_->name());
+  EXPECT_EQ("conf_description", config_->description());
+  EXPECT_EQ("conf_location", config_->location());
+  EXPECT_EQ("user", config_->local_anonymous_access_role());
+  EXPECT_FALSE(config_->local_pairing_enabled());
+  EXPECT_FALSE(config_->local_discovery_enabled());
+
+  // Not from config.
+  EXPECT_EQ(default_.device_id(), config_->device_id());
+  EXPECT_EQ(default_.refresh_token(), config_->refresh_token());
+  EXPECT_EQ(default_.robot_account(), config_->robot_account());
+
+  // Nothing should be saved yet.
+  EXPECT_JSON_EQ("{}", *storage_->Load());
+
+  BuffetConfig::Transaction change{config_.get()};
+  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
+  change.Commit();
+
+  auto expected = R"({
+     'description': 'conf_description',
+     'device_id': '',
+     'local_anonymous_access_role': 'user',
+     'local_discovery_enabled': false,
+     'local_pairing_enabled': false,
+     'location': 'conf_location',
+     'name': 'conf_name',
+     'refresh_token': '',
+     'robot_account': ''
+  })";
+  EXPECT_JSON_EQ(expected, *storage_->Load());
+}
+
+TEST_F(BuffetConfigTest, LoadState) {
+  auto state = R"({
+     'description': 'state_description',
+     'device_id': 'state_device_id',
+     'local_anonymous_access_role': 'user',
+     'local_discovery_enabled': false,
+     'local_pairing_enabled': false,
+     'location': 'state_location',
+     'name': 'state_name',
+     'refresh_token': 'state_refresh_token',
+     'robot_account': 'state_robot_account'
+  })";
+  storage_->Save(*buffet::unittests::CreateDictionaryValue(state));
+
+  chromeos::KeyValueStore config_store;
+  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
+  config_->Load(config_store);
+
+  // Clear storage.
+  storage_->Save(base::DictionaryValue());
+
+  EXPECT_EQ(default_.client_id(), config_->client_id());
+  EXPECT_EQ(default_.client_secret(), config_->client_secret());
+  EXPECT_EQ(default_.api_key(), config_->api_key());
+  EXPECT_EQ(default_.oauth_url(), config_->oauth_url());
+  EXPECT_EQ(default_.service_url(), config_->service_url());
+  EXPECT_EQ(default_.oem_name(), config_->oem_name());
+  EXPECT_EQ(default_.model_name(), config_->model_name());
+  EXPECT_EQ(default_.model_id(), config_->model_id());
+  EXPECT_EQ(default_.device_kind(), config_->device_kind());
+  EXPECT_EQ(default_.polling_period_ms(), config_->polling_period_ms());
+  EXPECT_EQ("state_name", config_->name());
+  EXPECT_EQ("state_description", config_->description());
+  EXPECT_EQ("state_location", config_->location());
+  EXPECT_EQ("user", config_->local_anonymous_access_role());
+  EXPECT_FALSE(config_->local_pairing_enabled());
+  EXPECT_FALSE(config_->local_discovery_enabled());
+  EXPECT_EQ("state_device_id", config_->device_id());
+  EXPECT_EQ("state_refresh_token", config_->refresh_token());
+  EXPECT_EQ("state_robot_account", config_->robot_account());
+
+  // Nothing should be saved yet.
+  EXPECT_JSON_EQ("{}", *storage_->Load());
+
+  BuffetConfig::Transaction change{config_.get()};
+  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
+  change.Commit();
+
+  EXPECT_JSON_EQ(state, *storage_->Load());
+}
+
+TEST_F(BuffetConfigTest, Setters) {
+  BuffetConfig::Transaction change{config_.get()};
+
+  change.set_name("set_name");
+  EXPECT_EQ("set_name", config_->name());
+
+  change.set_description("set_description");
+  EXPECT_EQ("set_description", config_->description());
+
+  change.set_location("set_location");
+  EXPECT_EQ("set_location", config_->location());
+
+  change.set_local_anonymous_access_role("viewer");
+  EXPECT_EQ("viewer", config_->local_anonymous_access_role());
+
+  change.set_local_anonymous_access_role("none");
+  EXPECT_EQ("none", config_->local_anonymous_access_role());
+
+  change.set_local_anonymous_access_role("user");
+  EXPECT_EQ("user", config_->local_anonymous_access_role());
+
+  change.set_local_discovery_enabled(false);
+  EXPECT_FALSE(config_->local_discovery_enabled());
+
+  change.set_local_pairing_enabled(false);
+  EXPECT_FALSE(config_->local_pairing_enabled());
+
+  change.set_local_discovery_enabled(true);
+  EXPECT_TRUE(config_->local_discovery_enabled());
+
+  change.set_local_pairing_enabled(true);
+  EXPECT_TRUE(config_->local_pairing_enabled());
+
+  change.set_device_id("set_id");
+  EXPECT_EQ("set_id", config_->device_id());
+
+  change.set_refresh_token("set_token");
+  EXPECT_EQ("set_token", config_->refresh_token());
+
+  change.set_robot_account("set_account");
+  EXPECT_EQ("set_account", config_->robot_account());
+
+  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
+  change.Commit();
+
+  auto expected = R"({
+    'description': 'set_description',
+    'device_id': 'set_id',
+    'local_anonymous_access_role': 'user',
+    'local_discovery_enabled': true,
+    'local_pairing_enabled': true,
+    'location': 'set_location',
+    'name': 'set_name',
+    'refresh_token': 'set_token',
+    'robot_account': 'set_account'
+  })";
+  EXPECT_JSON_EQ(expected, *storage_->Load());
+}
+}  // namespace buffet
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index e9f806b..3b176c3 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -28,7 +28,6 @@
 #include "buffet/commands/schema_constants.h"
 #include "buffet/device_registration_storage_keys.h"
 #include "buffet/notification/xmpp_channel.h"
-#include "buffet/org.chromium.Buffet.Manager.h"
 #include "buffet/states/state_manager.h"
 #include "buffet/utils.h"
 
@@ -133,17 +132,12 @@
     const std::shared_ptr<StateManager>& state_manager,
     std::unique_ptr<BuffetConfig> config,
     const std::shared_ptr<chromeos::http::Transport>& transport,
-    const std::shared_ptr<StorageInterface>& state_store,
-    bool notifications_enabled,
-    org::chromium::Buffet::ManagerAdaptor* manager)
+    bool notifications_enabled)
     : transport_{transport},
-      storage_{state_store},
       command_manager_{command_manager},
       state_manager_{state_manager},
       config_{std::move(config)},
-      notifications_enabled_{notifications_enabled},
-      manager_{manager} {
-  OnConfigChanged();
+      notifications_enabled_{notifications_enabled} {
   command_manager_->AddOnCommandDefChanged(
       base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
                  weak_factory_.GetWeakPtr()));
@@ -165,9 +159,9 @@
 std::string DeviceRegistrationInfo::GetDeviceURL(
     const std::string& subpath,
     const chromeos::data_encoding::WebParamList& params) const {
-  CHECK(!device_id_.empty()) << "Must have a valid device ID";
+  CHECK(!config_->device_id().empty()) << "Must have a valid device ID";
   return BuildURL(config_->service_url(),
-                  {"devices", device_id_, subpath}, params);
+                  {"devices", config_->device_id(), subpath}, params);
 }
 
 std::string DeviceRegistrationInfo::GetOAuthURL(
@@ -176,56 +170,7 @@
   return BuildURL(config_->oauth_url(), {subpath}, params);
 }
 
-const std::string& DeviceRegistrationInfo::GetDeviceId() const {
-  return device_id_;
-}
-
-bool DeviceRegistrationInfo::Load() {
-  // Set kInvalidCredentials to trigger on_status_changed_ callback.
-  registration_status_ = RegistrationStatus::kInvalidCredentials;
-  SetRegistrationStatus(RegistrationStatus::kUnconfigured);
-
-  auto value = storage_->Load();
-  const base::DictionaryValue* dict = nullptr;
-  if (!value || !value->GetAsDictionary(&dict))
-    return false;
-
-  // Read all available data before failing.
-  std::string name;
-  if (dict->GetString(storage_keys::kName, &name))
-    config_->set_name(name);
-
-  std::string description;
-  if (dict->GetString(storage_keys::kDescription, &description))
-    config_->set_description(description);
-
-  std::string location;
-  if (dict->GetString(storage_keys::kLocation, &location))
-    config_->set_location(location);
-
-  std::string access_role;
-  if (dict->GetString(storage_keys::kLocalAnonymousAccessRole, &access_role))
-    config_->set_local_anonymous_access_role(access_role);
-
-  bool local_discovery_enabled{false};
-  if (dict->GetBoolean(storage_keys::kLocalDiscoveryEnabled,
-                       &local_discovery_enabled))
-    config_->set_local_discovery_enabled(local_discovery_enabled);
-
-  bool local_pairing_enabled{false};
-  if (dict->GetBoolean(storage_keys::kLocalPairingEnabled,
-                       &local_pairing_enabled))
-    config_->set_local_pairing_enabled(local_pairing_enabled);
-
-  dict->GetString(storage_keys::kRefreshToken, &refresh_token_);
-  dict->GetString(storage_keys::kRobotAccount, &device_robot_account_);
-
-  std::string device_id;
-  if (dict->GetString(storage_keys::kDeviceId, &device_id))
-    SetDeviceId(device_id);
-
-  OnConfigChanged();
-
+void DeviceRegistrationInfo::Start() {
   if (HaveRegistrationCredentials(nullptr)) {
     // Wait a significant amount of time for local daemons to publish their
     // state to Buffet before publishing it to the cloud.
@@ -235,25 +180,6 @@
     //             we need not wait for them.
     ScheduleStartDevice(base::TimeDelta::FromSeconds(5));
   }
-  return true;
-}
-
-bool DeviceRegistrationInfo::Save() const {
-  base::DictionaryValue dict;
-  dict.SetString(storage_keys::kRefreshToken, refresh_token_);
-  dict.SetString(storage_keys::kDeviceId,     device_id_);
-  dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
-  dict.SetString(storage_keys::kName, config_->name());
-  dict.SetString(storage_keys::kDescription, config_->description());
-  dict.SetString(storage_keys::kLocation, config_->location());
-  dict.SetString(storage_keys::kLocalAnonymousAccessRole,
-                 config_->local_anonymous_access_role());
-  dict.SetBoolean(storage_keys::kLocalDiscoveryEnabled,
-                  config_->local_discovery_enabled());
-  dict.SetBoolean(storage_keys::kLocalPairingEnabled,
-                  config_->local_pairing_enabled());
-
-  return storage_->Save(dict);
 }
 
 void DeviceRegistrationInfo::ScheduleStartDevice(const base::TimeDelta& later) {
@@ -283,9 +209,9 @@
 
 bool DeviceRegistrationInfo::HaveRegistrationCredentials(
     chromeos::ErrorPtr* error) {
-  const bool have_credentials = !refresh_token_.empty() &&
-                                !device_id_.empty() &&
-                                !device_robot_account_.empty();
+  const bool have_credentials = !config_->refresh_token().empty() &&
+                                !config_->device_id().empty() &&
+                                !config_->robot_account().empty();
 
   VLOG(1) << "Device registration record "
           << ((have_credentials) ? "found" : "not found.");
@@ -336,12 +262,15 @@
 bool DeviceRegistrationInfo::RefreshAccessToken(
     chromeos::ErrorPtr* error) {
   LOG(INFO) << "Refreshing access token.";
-  auto response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
-    {"refresh_token", refresh_token_},
-    {"client_id", config_->client_id()},
-    {"client_secret", config_->client_secret()},
-    {"grant_type", "refresh_token"},
-  }, {}, transport_, error);
+  auto response = chromeos::http::PostFormDataAndBlock(
+      GetOAuthURL("token"),
+      {
+       {"refresh_token", config_->refresh_token()},
+       {"client_id", config_->client_id()},
+       {"client_secret", config_->client_secret()},
+       {"grant_type", "refresh_token"},
+      },
+      {}, transport_, error);
   if (!response)
     return false;
 
@@ -385,11 +314,18 @@
   // this class completely. Also to be added the secondary (poll) notification
   // channel.
   primary_notification_channel_.reset(
-      new XmppChannel{device_robot_account_, access_token_,
+      new XmppChannel{config_->robot_account(),
+                      access_token_,
                       base::MessageLoop::current()->task_runner()});
   primary_notification_channel_->Start(this);
 }
 
+void DeviceRegistrationInfo::AddOnRegistrationChangedCallback(
+    const OnRegistrationChangedCallback& callback) {
+  on_registration_changed_.push_back(callback);
+  callback.Run(registration_status_);
+}
+
 std::unique_ptr<base::DictionaryValue>
 DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
   // Limit only to commands that are visible to the cloud.
@@ -405,8 +341,8 @@
     return nullptr;
 
   std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
-  if (!device_id_.empty())
-    resource->SetString("id", device_id_);
+  if (!config_->device_id().empty())
+    resource->SetString("id", config_->device_id());
   resource->SetString("name", config_->name());
   if (!config_->description().empty())
     resource->SetString("description", config_->description());
@@ -534,7 +470,8 @@
 
   std::string auth_code;
   std::string device_id;
-  if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
+  std::string robot_account;
+  if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
       !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
       !json_resp->GetString("deviceDraft.id", &device_id)) {
     chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
@@ -542,7 +479,6 @@
                            "Device account missing in response");
     return std::string();
   }
-  SetDeviceId(device_id);
 
   // Now get access_token and refresh_token
   response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
@@ -558,13 +494,11 @@
 
   json_resp = ParseOAuthResponse(response.get(), error);
   int expires_in = 0;
-  if (!json_resp ||
-      !json_resp->GetString("access_token", &access_token_) ||
-      !json_resp->GetString("refresh_token", &refresh_token_) ||
+  std::string refresh_token;
+  if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
+      !json_resp->GetString("refresh_token", &refresh_token) ||
       !json_resp->GetInteger("expires_in", &expires_in) ||
-      access_token_.empty() ||
-      refresh_token_.empty() ||
-      expires_in <= 0) {
+      access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
     chromeos::Error::AddTo(error, FROM_HERE,
                            kErrorDomainGCD, "unexpected_response",
                            "Device access_token missing in response");
@@ -574,13 +508,18 @@
   access_token_expiration_ = base::Time::Now() +
                              base::TimeDelta::FromSeconds(expires_in);
 
-  Save();
+  BuffetConfig::Transaction change{config_.get()};
+  change.set_device_id(device_id);
+  change.set_robot_account(robot_account);
+  change.set_refresh_token(refresh_token);
+  change.Commit();
+
   StartNotificationChannel();
 
   // We're going to respond with our success immediately and we'll StartDevice
   // shortly after.
   ScheduleStartDevice(base::TimeDelta::FromSeconds(0));
-  return device_id_;
+  return device_id;
 }
 
 namespace {
@@ -769,17 +708,14 @@
                                               const std::string& description,
                                               const std::string& location,
                                               chromeos::ErrorPtr* error) {
-  if (!config_->set_name(name)) {
-    chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
-                                 "invalid_parameter", "Invalid name: %s",
-                                 name.c_str());
+  BuffetConfig::Transaction change{config_.get()};
+  if (!change.set_name(name)) {
+    chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
+                           "invalid_parameter", "Empty device name");
     return false;
   }
-  config_->set_description(description);
-  config_->set_location(location);
-
-  Save();
-  OnConfigChanged();
+  change.set_description(description);
+  change.set_location(location);
 
   if (HaveRegistrationCredentials(nullptr)) {
     UpdateDeviceResource(base::Bind(&base::DoNothing),
@@ -878,10 +814,8 @@
     const CloudRequestErrorCallback& on_failure) {
   DoCloudRequest(
       chromeos::http::request_type::kGet,
-      GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
-      nullptr,
-      base::Bind(&HandleFetchCommandsResult, on_success),
-      on_failure);
+      GetServiceURL("commands/queue", {{"deviceId", config_->device_id()}}),
+      nullptr, base::Bind(&HandleFetchCommandsResult, on_success), on_failure);
 }
 
 void DeviceRegistrationInfo::AbortLimboCommands(
@@ -1021,29 +955,11 @@
 
 void DeviceRegistrationInfo::SetRegistrationStatus(
     RegistrationStatus new_status) {
-  registration_status_ = new_status;
-  if (manager_)
-    manager_->SetStatus(StatusToString(registration_status_));
   VLOG_IF(1, new_status != registration_status_)
       << "Changing registration status to " << StatusToString(new_status);
-}
-
-void DeviceRegistrationInfo::SetDeviceId(const std::string& device_id) {
-  device_id_ = device_id;
-  if (manager_)
-    manager_->SetDeviceId(device_id_);
-}
-
-void DeviceRegistrationInfo::OnConfigChanged() {
-  if (!manager_)
-    return;
-  manager_->SetOemName(config_->oem_name());
-  manager_->SetModelName(config_->model_name());
-  manager_->SetModelId(config_->model_id());
-  manager_->SetName(config_->name());
-  manager_->SetDescription(config_->description());
-  manager_->SetLocation(config_->location());
-  manager_->SetAnonymousAccessRole(config_->local_anonymous_access_role());
+  registration_status_ = new_status;
+  for (const auto& cb : on_registration_changed_)
+    cb.Run(registration_status_);
 }
 
 void DeviceRegistrationInfo::OnCommandDefsChanged() {
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
index ab72c87..29c73d2 100644
--- a/buffet/device_registration_info.h
+++ b/buffet/device_registration_info.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <base/macros.h>
 #include <base/memory/weak_ptr.h>
@@ -25,14 +26,6 @@
 #include "buffet/registration_status.h"
 #include "buffet/storage_interface.h"
 
-namespace org {
-namespace chromium {
-namespace Buffet {
-class ManagerAdaptor;
-}
-}
-}
-
 namespace base {
 class DictionaryValue;
 }  // namespace base
@@ -52,24 +45,21 @@
 // The DeviceRegistrationInfo class represents device registration information.
 class DeviceRegistrationInfo : public NotificationDelegate {
  public:
-  // This is a helper class for unit testing.
-  class TestHelper;
+  using OnRegistrationChangedCallback =
+      base::Callback<void(RegistrationStatus)>;
 
   DeviceRegistrationInfo(
       const std::shared_ptr<CommandManager>& command_manager,
       const std::shared_ptr<StateManager>& state_manager,
       std::unique_ptr<BuffetConfig> config,
       const std::shared_ptr<chromeos::http::Transport>& transport,
-      const std::shared_ptr<StorageInterface>& state_store,
-      bool notifications_enabled,
-      org::chromium::Buffet::ManagerAdaptor* manager);
+      bool notifications_enabled);
 
   virtual ~DeviceRegistrationInfo();
 
-  // Returns our current best known registration status.
-  RegistrationStatus GetRegistrationStatus() const {
-    return registration_status_;
-  }
+  // Add callback to listen for changes in registration status.
+  void AddOnRegistrationChangedCallback(
+      const OnRegistrationChangedCallback& callback);
 
   // Returns the authorization HTTP header that can be used to talk
   // to GCD server for authenticated device communication.
@@ -103,11 +93,8 @@
     const std::string& subpath = {},
     const chromeos::data_encoding::WebParamList& params = {}) const;
 
-  // Returns the registered device ID (GUID) or empty string.
-  const std::string& GetDeviceId() const;
-
-  // Loads the device registration information from cache.
-  bool Load();
+  // Starts GCD device if credentials avalible.
+  void Start();
 
   // Checks whether we have credentials generated during registration.
   bool HaveRegistrationCredentials(chromeos::ErrorPtr* error);
@@ -124,9 +111,8 @@
   // is omitted, a default value is used when possible.
   // Returns a device ID on success.
   // The values are all strings for now.
-  std::string RegisterDevice(
-    const std::map<std::string, std::string>& params,
-    chromeos::ErrorPtr* error);
+  std::string RegisterDevice(const std::map<std::string, std::string>& params,
+                             chromeos::ErrorPtr* error);
 
   // Updates a command.
   void UpdateCommand(const std::string& command_id,
@@ -147,6 +133,8 @@
   }
 
  private:
+  friend class DeviceRegistrationInfoTest;
+
   // Cause DeviceRegistrationInfo to attempt to StartDevice on its own later.
   void ScheduleStartDevice(const base::TimeDelta& later);
 
@@ -157,9 +145,6 @@
   void StartDevice(chromeos::ErrorPtr* error,
                    const base::TimeDelta& retry_delay);
 
-  // Saves the device registration to cache.
-  bool Save() const;
-
   // Checks for the valid device registration as well as refreshes
   // the device access token, if available.
   bool CheckRegistration(chromeos::ErrorPtr* error);
@@ -232,7 +217,6 @@
 
   void SetRegistrationStatus(RegistrationStatus new_status);
   void SetDeviceId(const std::string& device_id);
-  void OnConfigChanged();
 
   // Callback called when command definitions are changed to re-publish new CDD.
   void OnCommandDefsChanged();
@@ -242,19 +226,12 @@
   void OnDisconnected() override;
   void OnPermanentFailure() override;
 
-  // Data that is cached here, persisted in the state store.
-  std::string refresh_token_;
-  std::string device_id_;
-  std::string device_robot_account_;
-
   // Transient data
   std::string access_token_;
   base::Time access_token_expiration_;
 
   // HTTP transport used for communications.
   std::shared_ptr<chromeos::http::Transport> transport_;
-  // Serialization interface to save and load device registration info.
-  std::shared_ptr<StorageInterface> storage_;
   // Global command manager.
   std::shared_ptr<CommandManager> command_manager_;
   // Device state manager.
@@ -267,12 +244,12 @@
 
   // Tracks our current registration status.
   RegistrationStatus registration_status_{RegistrationStatus::kUnconfigured};
-  org::chromium::Buffet::ManagerAdaptor* manager_;
 
   base::RepeatingTimer<DeviceRegistrationInfo> command_poll_timer_;
   base::RepeatingTimer<DeviceRegistrationInfo> state_push_timer_;
 
-  friend class TestHelper;
+  std::vector<OnRegistrationChangedCallback> on_registration_changed_;
+
   base::WeakPtrFactory<DeviceRegistrationInfo> weak_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(DeviceRegistrationInfo);
 };
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
index 934ae51..f41d408 100644
--- a/buffet/device_registration_info_unittest.cc
+++ b/buffet/device_registration_info_unittest.cc
@@ -162,34 +162,26 @@
 
 }  // anonymous namespace
 
-// This is a helper class that allows the unit tests to access private
-// methods and data since TestHelper is declared as a friend to
-// DeviceRegistrationInfo.
-class DeviceRegistrationInfo::TestHelper {
- public:
-  static bool Save(DeviceRegistrationInfo* info) {
-    return info->Save();
-  }
-
-  static void PublishCommands(DeviceRegistrationInfo* info,
-                              const base::ListValue& commands) {
-    return info->PublishCommands(commands);
-  }
-
-  static bool CheckRegistration(DeviceRegistrationInfo* info,
-                                chromeos::ErrorPtr* error) {
-    return info->CheckRegistration(error);
-  }
-};
-
 class DeviceRegistrationInfoTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    storage_ = std::make_shared<MemStorage>();
-    storage_->Save(data_);
+    std::unique_ptr<StorageInterface> storage{new MemStorage};
+    storage_ = storage.get();
+    storage->Save(data_);
     transport_ = std::make_shared<chromeos::http::fake::Transport>();
     command_manager_ = std::make_shared<CommandManager>();
     state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_);
+
+    std::unique_ptr<BuffetConfig> config{new BuffetConfig{std::move(storage)}};
+    config_ = config.get();
+    dev_reg_ = std::unique_ptr<DeviceRegistrationInfo>(
+        new DeviceRegistrationInfo(command_manager_, state_manager_,
+                                   std::move(config), transport_, true));
+
+    ReloadConfig();
+  }
+
+  void ReloadConfig() {
     chromeos::KeyValueStore config_store;
     config_store.SetString("client_id", test_data::kClientId);
     config_store.SetString("client_secret", test_data::kClientSecret);
@@ -199,23 +191,28 @@
     config_store.SetString("description", "Easy to clean");
     config_store.SetString("location", "Kitchen");
     config_store.SetString("local_anonymous_access_role", "viewer");
-    config_store.SetBoolean("local_local_discovery_enabled", true);
-    config_store.SetBoolean("local_local_pairing_enabled", false);
     config_store.SetString("model_id", "AAAAA");
     config_store.SetString("oauth_url", test_data::kOAuthURL);
     config_store.SetString("service_url", test_data::kServiceURL);
-    std::unique_ptr<BuffetConfig> config{new BuffetConfig};
-    config->Load(config_store);
-    dev_reg_ = std::unique_ptr<DeviceRegistrationInfo>(
-        new DeviceRegistrationInfo(command_manager_, state_manager_,
-                                   std::move(config),
-                                   transport_, storage_,
-                                   true,
-                                   nullptr));
+    config_->Load(config_store);
+    dev_reg_->Start();
+  }
+
+  void PublishCommands(const base::ListValue& commands) {
+    return dev_reg_->PublishCommands(commands);
+  }
+
+  bool CheckRegistration(chromeos::ErrorPtr* error) const {
+    return dev_reg_->CheckRegistration(error);
+  }
+
+  RegistrationStatus GetRegistrationStatus() const {
+    return dev_reg_->registration_status_;
   }
 
   base::DictionaryValue data_;
-  std::shared_ptr<MemStorage> storage_;
+  StorageInterface* storage_{nullptr};
+  BuffetConfig* config_{nullptr};
   std::shared_ptr<chromeos::http::fake::Transport> transport_;
   std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
   std::shared_ptr<CommandManager> command_manager_;
@@ -225,7 +222,6 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
-  EXPECT_TRUE(dev_reg_->Load());
   EXPECT_EQ(test_data::kServiceURL, dev_reg_->GetServiceURL());
   std::string url = test_data::kServiceURL;
   url += "registrationTickets";
@@ -242,35 +238,7 @@
   }));
 }
 
-TEST_F(DeviceRegistrationInfoTest, VerifySave) {
-  auto expected = R"({
-     'description': 'l',
-     'device_id': 'e',
-     'local_anonymous_access_role': 'user',
-     'local_discovery_enabled': true,
-     'local_pairing_enabled': true,
-     'location': 'm',
-     'name': 'k',
-     'refresh_token': 'd',
-     'robot_account': 'h'
-  })";
-
-  storage_->Save(*unittests::CreateDictionaryValue(expected));
-
-  // This test isn't really trying to test Load, it is just the easiest
-  // way to initialize the properties in dev_reg_.
-  EXPECT_TRUE(dev_reg_->Load());
-
-  // Clear the storage to get a clean test.
-  base::DictionaryValue empty;
-  storage_->Save(empty);
-  EXPECT_TRUE(DeviceRegistrationInfo::TestHelper::Save(dev_reg_.get()));
-
-  EXPECT_JSON_EQ(expected, *storage_->Load());
-}
-
 TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
-  EXPECT_TRUE(dev_reg_->Load());
   EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL());
   std::string url = test_data::kOAuthURL;
   url += "auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fclouddevices&";
@@ -287,65 +255,59 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckRegistration) {
-  EXPECT_TRUE(dev_reg_->Load());
-  EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration(
-      dev_reg_.get(), nullptr));
+  EXPECT_FALSE(CheckRegistration(nullptr));
   EXPECT_EQ(0, transport_->GetRequestCount());
 
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
+  ReloadConfig();
 
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
                          base::Bind(OAuth2Handler));
   transport_->ResetRequestCount();
-  EXPECT_TRUE(DeviceRegistrationInfo::TestHelper::CheckRegistration(
-      dev_reg_.get(), nullptr));
+  EXPECT_TRUE(CheckRegistration(nullptr));
   EXPECT_EQ(1, transport_->GetRequestCount());
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
-  EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus());
+  ReloadConfig();
+  EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
 
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
                          base::Bind(OAuth2HandlerFail));
   transport_->ResetRequestCount();
   chromeos::ErrorPtr error;
-  EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration(
-      dev_reg_.get(), &error));
+  EXPECT_FALSE(CheckRegistration(&error));
   EXPECT_EQ(1, transport_->GetRequestCount());
   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "unable_to_authenticate"));
-  EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus());
+  EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) {
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
-  EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus());
+  ReloadConfig();
+  EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
 
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
                          base::Bind(OAuth2HandlerDeregister));
   transport_->ResetRequestCount();
   chromeos::ErrorPtr error;
-  EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration(
-      dev_reg_.get(), &error));
+  EXPECT_FALSE(CheckRegistration(&error));
   EXPECT_EQ(1, transport_->GetRequestCount());
   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant"));
-  EXPECT_EQ(RegistrationStatus::kInvalidCredentials,
-            dev_reg_->GetRegistrationStatus());
+  EXPECT_EQ(RegistrationStatus::kInvalidCredentials, GetRegistrationStatus());
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
+  ReloadConfig();
 
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
@@ -367,7 +329,7 @@
 TEST_F(DeviceRegistrationInfoTest, GetDeviceId) {
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
+  ReloadConfig();
 
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
@@ -375,12 +337,10 @@
   transport_->AddHandler(dev_reg_->GetDeviceURL(),
                          chromeos::http::request_type::kGet,
                          base::Bind(DeviceInfoHandler));
-  EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetDeviceId());
+  EXPECT_EQ(test_data::kDeviceId, config_->device_id());
 }
 
 TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
-  EXPECT_TRUE(dev_reg_->Load());
-
   auto update_ticket = [](const ServerRequest& request,
                           ServerResponse* response) {
     EXPECT_EQ(test_data::kApiKey, request.GetFormField("key"));
@@ -487,16 +447,13 @@
   transport_->AddHandler(dev_reg_->GetOAuthURL("token"),
                          chromeos::http::request_type::kPost,
                          base::Bind(OAuth2Handler));
-  storage_->reset_save_count();
-
   std::map<std::string, std::string> params;
   params["ticket_id"] = test_data::kClaimTicketId;
   std::string device_id = dev_reg_->RegisterDevice(params, nullptr);
 
   EXPECT_EQ(test_data::kDeviceId, device_id);
-  EXPECT_GT(storage_->save_count(), 1);  // The state must have been saved.
   EXPECT_EQ(3, transport_->GetRequestCount());
-  EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus());
+  EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
 
   // Validate the device info saved to storage...
   auto storage_data = storage_->Load();
@@ -514,18 +471,15 @@
 TEST_F(DeviceRegistrationInfoTest, OOBRegistrationStatus) {
   // After we've been initialized, we should be either offline or unregistered,
   // depending on whether or not we've found credentials.
-  EXPECT_TRUE(dev_reg_->Load());
-  EXPECT_EQ(RegistrationStatus::kUnconfigured,
-            dev_reg_->GetRegistrationStatus());
+  EXPECT_EQ(RegistrationStatus::kUnconfigured, GetRegistrationStatus());
   // Put some credentials into our state, make sure we call that offline.
   SetDefaultDeviceRegistration(&data_);
   storage_->Save(data_);
-  EXPECT_TRUE(dev_reg_->Load());
-  EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus());
+  ReloadConfig();
+  EXPECT_EQ(RegistrationStatus::kConnecting, GetRegistrationStatus());
 }
 
 TEST_F(DeviceRegistrationInfoTest, UpdateCommand) {
-  EXPECT_TRUE(dev_reg_->Load());
   auto json_cmds = unittests::CreateDictionaryValue(R"({
     'robot': {
       '_jump': {
@@ -546,8 +500,7 @@
   ASSERT_NE(nullptr, commands_json.get());
   const base::ListValue* command_list = nullptr;
   ASSERT_TRUE(commands_json->GetAsList(&command_list));
-  DeviceRegistrationInfo::TestHelper::PublishCommands(dev_reg_.get(),
-                                                      *command_list);
+  PublishCommands(*command_list);
   auto command = command_manager_->FindCommand("1234");
   ASSERT_NE(nullptr, command);
   StringPropType string_type;
diff --git a/buffet/main.cc b/buffet/main.cc
index 7d70ffd..a4ecf59 100644
--- a/buffet/main.cc
+++ b/buffet/main.cc
@@ -42,7 +42,6 @@
   }
 
  private:
-  BuffetConfig config_;
   std::unique_ptr<buffet::Manager> manager_;
   const base::FilePath config_path_;
   const base::FilePath state_path_;
diff --git a/buffet/manager.cc b/buffet/manager.cc
index 90562aa..2002eb2 100644
--- a/buffet/manager.cc
+++ b/buffet/manager.cc
@@ -61,20 +61,24 @@
   state_change_queue_.reset(new StateChangeQueue(kMaxStateChangeQueueSize));
   state_manager_ = std::make_shared<StateManager>(state_change_queue_.get());
   state_manager_->Startup();
-  std::unique_ptr<BuffetConfig> config{new BuffetConfig};
+
+  std::unique_ptr<BuffetConfig> config{new BuffetConfig{state_path}};
+  config->AddOnChangedCallback(
+      base::Bind(&Manager::OnConfigChanged, weak_ptr_factory_.GetWeakPtr()));
   config->Load(config_path);
-  std::unique_ptr<FileStorage> state_store{new FileStorage{state_path}};
+
   // TODO(avakulenko): Figure out security implications of storing
   // device info state data unencrypted.
   device_info_.reset(new DeviceRegistrationInfo(
       command_manager_, state_manager_, std::move(config),
-      chromeos::http::Transport::CreateDefault(), std::move(state_store),
-      xmpp_enabled, &dbus_adaptor_));
+      chromeos::http::Transport::CreateDefault(), xmpp_enabled));
+  device_info_->AddOnRegistrationChangedCallback(base::Bind(
+      &Manager::OnRegistrationChanged, weak_ptr_factory_.GetWeakPtr()));
 
   base_api_handler_.reset(
       new BaseApiHandler{device_info_->AsWeakPtr(), command_manager_});
 
-  device_info_->Load();
+  device_info_->Start();
   dbus_adaptor_.RegisterWithDBusObject(&dbus_object_);
   dbus_object_.RegisterAsync(cb);
 }
@@ -91,7 +95,8 @@
     return;
   }
 
-  response->Return(registered ? device_info_->GetDeviceId() : std::string());
+  response->Return(registered ? device_info_->GetConfig().device_id()
+                              : std::string());
 }
 
 void Manager::GetDeviceInfo(DBusMethodResponse<std::string> response) {
@@ -252,4 +257,19 @@
   dbus_adaptor_.SetCommandDefs(json);
 }
 
+void Manager::OnRegistrationChanged(RegistrationStatus status) {
+  dbus_adaptor_.SetStatus(StatusToString(status));
+}
+
+void Manager::OnConfigChanged(const BuffetConfig& config) {
+  dbus_adaptor_.SetDeviceId(config.device_id());
+  dbus_adaptor_.SetOemName(config.oem_name());
+  dbus_adaptor_.SetModelName(config.model_name());
+  dbus_adaptor_.SetModelId(config.model_id());
+  dbus_adaptor_.SetName(config.name());
+  dbus_adaptor_.SetDescription(config.description());
+  dbus_adaptor_.SetLocation(config.location());
+  dbus_adaptor_.SetAnonymousAccessRole(config.local_anonymous_access_role());
+}
+
 }  // namespace buffet
diff --git a/buffet/manager.h b/buffet/manager.h
index 44790d3..3961b68 100644
--- a/buffet/manager.h
+++ b/buffet/manager.h
@@ -81,6 +81,8 @@
   std::string TestMethod(const std::string& message) override;
 
   void OnCommandDefsChanged();
+  void OnRegistrationChanged(RegistrationStatus status);
+  void OnConfigChanged(const BuffetConfig& config);
 
   org::chromium::Buffet::ManagerAdaptor dbus_adaptor_{this};
   chromeos::dbus_utils::DBusObject dbus_object_;
diff --git a/buffet/storage_impls.cc b/buffet/storage_impls.cc
index ea29d69..b0bcfd9 100644
--- a/buffet/storage_impls.cc
+++ b/buffet/storage_impls.cc
@@ -39,7 +39,6 @@
 bool MemStorage::Save(const base::DictionaryValue& config) {
   cache_.Clear();
   cache_.MergeDictionary(&config);
-  ++save_count_;
   return true;
 }
 
diff --git a/buffet/storage_impls.h b/buffet/storage_impls.h
index bacedd0..7f205da 100644
--- a/buffet/storage_impls.h
+++ b/buffet/storage_impls.h
@@ -33,11 +33,8 @@
   virtual ~MemStorage() = default;
   std::unique_ptr<base::DictionaryValue> Load() override;
   bool Save(const base::DictionaryValue& config) override;
-  int save_count() { return save_count_; }
-  void reset_save_count() { save_count_ = 0; }
 
  private:
-  int save_count_ = 0;
   base::DictionaryValue cache_;
   DISALLOW_COPY_AND_ASSIGN(MemStorage);
 };