[shill] Enable Profile objects to manage a list of Services to persist.

The Manager will hang on to a list of active services.  They will be sorted,
someday, in an order that makes sense.  Every service will be associated with
a Profile, though it may the an ephemeral profile that won't persist state to
disk.  Profiles will maintain a map of service name to service references to
track the services whose state they persist to disk.  Services may move between
Profiles, and will certainly need to be bound to one after they are registered
with the Manager, so support for this is provided as well.

BUG=chromium-os:17436
TEST=unit tests

Change-Id: Id43a0e1d97302b6f574bd2213d4f3d176bb5223f
Reviewed-on: http://gerrit.chromium.org/gerrit/4033
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Chris Masone <cmasone@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index 8cd8a04..8e90222 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -96,8 +96,8 @@
              interface_index),
       service_(new CellularService(control_interface,
                                    dispatcher,
+                                   manager,
                                    this,
-                                   manager->ActiveProfile(),
                                    "service-" + link_name())),
       service_registered_(false) {
   store_.RegisterConstString(flimflam::kCarrierProperty, &carrier_);
diff --git a/cellular_service.cc b/cellular_service.cc
index cd087f0..48bbe02 100644
--- a/cellular_service.cc
+++ b/cellular_service.cc
@@ -21,10 +21,10 @@
 namespace shill {
 CellularService::CellularService(ControlInterface *control_interface,
                                  EventDispatcher *dispatcher,
+                                 Manager *manager,
                                  const CellularRefPtr &device,
-                                 const ProfileRefPtr &profile,
                                  const string &name)
-    : Service(control_interface, dispatcher, profile, name),
+    : Service(control_interface, dispatcher, manager, name),
       strength_(0),
       cellular_(device),
       type_(flimflam::kTypeCellular) {
diff --git a/cellular_service.h b/cellular_service.h
index 069cf6c..e8032b9 100644
--- a/cellular_service.h
+++ b/cellular_service.h
@@ -16,12 +16,16 @@
 
 namespace shill {
 
+class ControlInterface;
+class EventDispatcher;
+class Manager;
+
 class CellularService : public Service {
  public:
   CellularService(ControlInterface *control_interface,
                   EventDispatcher *dispatcher,
+                  Manager *manager,
                   const CellularRefPtr &device,
-                  const ProfileRefPtr &profile,
                   const std::string& name);
   ~CellularService();
   void Connect();
diff --git a/dbus_adaptor_unittest.cc b/dbus_adaptor_unittest.cc
index 3eb5aae..5401201 100644
--- a/dbus_adaptor_unittest.cc
+++ b/dbus_adaptor_unittest.cc
@@ -58,7 +58,7 @@
                                0)),
         service_(new MockService(&control_interface_,
                                  &dispatcher_,
-                                 new MockProfile(&control_interface_, &glib_),
+                                 &manager_,
                                  "mock")) {
     ex_stringmap_[ex_string_] = ex_string_;
     stringmap_v_ = DBusAdaptor::StringmapToVariant(ex_stringmap_);
diff --git a/default_profile.cc b/default_profile.cc
index 74facf3..78929b4 100644
--- a/default_profile.cc
+++ b/default_profile.cc
@@ -14,8 +14,9 @@
 
 DefaultProfile::DefaultProfile(ControlInterface *control_interface,
                                GLib *glib,
+                               Manager *manager,
                                const Manager::Properties &manager_props)
-    : Profile(control_interface, glib) {
+    : Profile(control_interface, glib, manager) {
   store_.RegisterConstString(flimflam::kCheckPortalListProperty,
                              &manager_props.check_portal_list);
   store_.RegisterConstString(flimflam::kCountryProperty,
diff --git a/default_profile.h b/default_profile.h
index 746860c..87e03fa 100644
--- a/default_profile.h
+++ b/default_profile.h
@@ -24,6 +24,7 @@
  public:
   DefaultProfile(ControlInterface *control_interface,
                  GLib *glib,
+                 Manager *manager,
                  const Manager::Properties &manager_props);
   virtual ~DefaultProfile();
 
diff --git a/default_profile_unittest.cc b/default_profile_unittest.cc
index 68d9f1d..44d755d 100644
--- a/default_profile_unittest.cc
+++ b/default_profile_unittest.cc
@@ -24,7 +24,10 @@
 class DefaultProfileTest : public PropertyStoreTest {
  public:
   DefaultProfileTest()
-      : profile_(new DefaultProfile(&control_interface_, &glib_, properties_)) {
+      : profile_(new DefaultProfile(&control_interface_,
+                                    &glib_,
+                                    &manager_,
+                                    properties_)) {
   }
 
   virtual ~DefaultProfileTest() {}
diff --git a/ephemeral_profile.cc b/ephemeral_profile.cc
index 1009877..c5a3630 100644
--- a/ephemeral_profile.cc
+++ b/ephemeral_profile.cc
@@ -21,19 +21,13 @@
 EphemeralProfile::EphemeralProfile(ControlInterface *control_interface,
                                    GLib *glib,
                                    Manager *manager)
-    : Profile(control_interface, glib),
-      manager_(manager) {
+    : Profile(control_interface, glib, manager) {
 }
 
 EphemeralProfile::~EphemeralProfile() {}
 
-bool EphemeralProfile::MoveToActiveProfile(const std::string &name) {
-  map<string, ServiceRefPtr>::iterator it = services_.find(name);
-  if (it == services_.end())
-    return false;
-  manager_->ActiveProfile()->AdoptService(name, it->second);
-  services_.erase(it);
-  return true;
+void EphemeralProfile::Finalize() {
+  services_.clear();
 }
 
 }  // namespace shill
diff --git a/ephemeral_profile.h b/ephemeral_profile.h
index 0503fa0..20fea1d 100644
--- a/ephemeral_profile.h
+++ b/ephemeral_profile.h
@@ -10,7 +10,6 @@
 
 #include <base/memory/scoped_ptr.h>
 
-#include "shill/manager.h"
 #include "shill/profile.h"
 #include "shill/property_store.h"
 #include "shill/refptr_types.h"
@@ -19,6 +18,7 @@
 namespace shill {
 
 class ControlInterface;
+class Manager;
 
 // An in-memory profile that is not persisted to disk, but allows the
 // promotion of entries contained herein to the currently active profile.
@@ -29,10 +29,10 @@
                    Manager *manager);
   virtual ~EphemeralProfile();
 
-  virtual bool MoveToActiveProfile(const std::string &name);
+  // Merely stop managing service persistence; flush nothing to disk.
+  virtual void Finalize();
 
  private:
-  Manager *manager_;
   DISALLOW_COPY_AND_ASSIGN(EphemeralProfile);
 };
 
diff --git a/ethernet.cc b/ethernet.cc
index a13655f..a54d704 100644
--- a/ethernet.cc
+++ b/ethernet.cc
@@ -39,8 +39,8 @@
       service_registered_(false),
       service_(new EthernetService(control_interface,
                                    dispatcher,
+                                   manager,
                                    this,
-                                   manager->ActiveProfile(),
                                    "service-" + link_name)),
       link_up_(false) {
   VLOG(2) << "Ethernet device " << link_name << " initialized.";
diff --git a/ethernet_service.cc b/ethernet_service.cc
index 047e99c..753a653 100644
--- a/ethernet_service.cc
+++ b/ethernet_service.cc
@@ -28,10 +28,10 @@
 
 EthernetService::EthernetService(ControlInterface *control_interface,
                                  EventDispatcher *dispatcher,
+                                 Manager *manager,
                                  const EthernetRefPtr &device,
-                                 const ProfileRefPtr &profile,
                                  const string &name)
-    : Service(control_interface, dispatcher, profile, name),
+    : Service(control_interface, dispatcher, manager, name),
       ethernet_(device),
       type_(flimflam::kTypeEthernet) {
   set_auto_connect(true);
diff --git a/ethernet_service.h b/ethernet_service.h
index 4d9a3cb..e909505 100644
--- a/ethernet_service.h
+++ b/ethernet_service.h
@@ -13,12 +13,16 @@
 
 namespace shill {
 
+class ControlInterface;
+class EventDispatcher;
+class Manager;
+
 class EthernetService : public Service {
  public:
   EthernetService(ControlInterface *control_interface,
                   EventDispatcher *dispatcher,
+                  Manager *manager,
                   const EthernetRefPtr &device,
-                  const ProfileRefPtr &profile,
                   const std::string& name);
   ~EthernetService();
   void Connect();
diff --git a/manager.cc b/manager.cc
index 820dba7..ce7a2a4 100644
--- a/manager.cc
+++ b/manager.cc
@@ -20,6 +20,7 @@
 #include "shill/default_profile.h"
 #include "shill/device.h"
 #include "shill/device_info.h"
+#include "shill/ephemeral_profile.h"
 #include "shill/error.h"
 #include "shill/profile.h"
 #include "shill/property_accessor.h"
@@ -36,7 +37,8 @@
   : adaptor_(control_interface->CreateManagerAdaptor(this)),
     device_info_(control_interface, dispatcher, this),
     modem_info_(control_interface, dispatcher, this, glib),
-    running_(false) {
+    running_(false),
+    ephemeral_profile_(new EphemeralProfile(control_interface, glib, this)) {
   HelpRegisterDerivedString(flimflam::kActiveProfileProperty,
                             &Manager::GetActiveProfileName,
                             NULL);
@@ -73,12 +75,20 @@
   // TODO(cmasone): Wire these up once we actually put in profile support.
   // known_properties_.push_back(flimflam::kProfilesProperty);
 
-  profiles_.push_back(new DefaultProfile(control_interface, glib, props_));
-
+  profiles_.push_back(new DefaultProfile(control_interface,
+                                         glib,
+                                         this,
+                                         props_));
   VLOG(2) << "Manager initialized.";
 }
 
-Manager::~Manager() {}
+Manager::~Manager() {
+  vector<ProfileRefPtr>::iterator it;
+  for (it = profiles_.begin(); it != profiles_.end(); ++it) {
+    (*it)->Finalize();
+  }
+  ephemeral_profile_->Finalize();
+}
 
 void Manager::Start() {
   LOG(INFO) << "Manager started.";
@@ -99,6 +109,12 @@
   return profiles_.back();
 }
 
+bool Manager::MoveToActiveProfile(const ProfileRefPtr &from,
+                                  const ServiceRefPtr &to_move) {
+  return ActiveProfile()->AdoptService(to_move) &&
+      from->AbandonService(to_move->UniqueName());
+}
+
 void Manager::RegisterDevice(const DeviceRefPtr &to_manage) {
   vector<DeviceRefPtr>::iterator it;
   for (it = devices_.begin(); it != devices_.end(); ++it) {
@@ -123,18 +139,30 @@
 }
 
 void Manager::RegisterService(const ServiceRefPtr &to_manage) {
+  // This should look for |to_manage| in the real profiles and, if found,
+  // do...something...to merge the meaningful state, I guess.
+
+  // If not found, add it to the ephemeral profile
+  ephemeral_profile_->AdoptService(to_manage);
+
+  // Now add to OUR list.
+  // TODO(cmasone): Keep this list sorted.
   vector<ServiceRefPtr>::iterator it;
   for (it = services_.begin(); it != services_.end(); ++it) {
-    if (to_manage.get() == it->get())
+    if (to_manage->UniqueName() == (*it)->UniqueName())
       return;
   }
   services_.push_back(to_manage);
 }
 
 void Manager::DeregisterService(const ServiceConstRefPtr &to_forget) {
+  // If the service is in the ephemeral profile, destroy it.
+  if (!ephemeral_profile_->AbandonService(to_forget->UniqueName())) {
+    // if it's in one of the real profiles...um...I guess mark it unconnectable?
+  }
   vector<ServiceRefPtr>::iterator it;
   for (it = services_.begin(); it != services_.end(); ++it) {
-    if (to_forget.get() == it->get()) {
+    if (to_forget->UniqueName() == (*it)->UniqueName()) {
       services_.erase(it);
       return;
     }
@@ -154,9 +182,8 @@
 ServiceRefPtr Manager::FindService(const std::string& name) {
   vector<ServiceRefPtr>::iterator it;
   for (it = services_.begin(); it != services_.end(); ++it) {
-    if (name == (*it)->UniqueName()) {
+    if (name == (*it)->UniqueName())
       return *it;
-    }
   }
   return NULL;
 }
@@ -208,8 +235,6 @@
 }
 
 vector<string> Manager::EnumerateAvailableServices() {
-  // TODO(cmasone): This should, instead, be returned by calling into the
-  // currently active profile.
   vector<string> service_rpc_ids;
   for (vector<ServiceRefPtr>::const_iterator it = services_.begin();
        it != services_.end();
@@ -220,7 +245,7 @@
 }
 
 vector<string> Manager::EnumerateWatchedServices() {
-  // TODO(cmasone): Implement this for real by querying the active profile.
+  // TODO(cmasone): Filter this list for services in appropriate states.
   return EnumerateAvailableServices();
 }
 
diff --git a/manager.h b/manager.h
index 85eb9c0..839e7fe 100644
--- a/manager.h
+++ b/manager.h
@@ -47,6 +47,8 @@
   void Stop();
 
   const ProfileRefPtr &ActiveProfile();
+  bool MoveToActiveProfile(const ProfileRefPtr &from,
+                           const ServiceRefPtr &to_move);
 
   void RegisterDevice(const DeviceRefPtr &to_manage);
   void DeregisterDevice(const DeviceConstRefPtr &to_forget);
@@ -58,6 +60,7 @@
                           std::vector<DeviceRefPtr> *found);
 
   ServiceRefPtr FindService(const std::string& name);
+  std::vector<std::string> EnumerateAvailableServices();
 
   PropertyStore *store() { return &store_; }
 
@@ -68,9 +71,7 @@
   std::string DefaultTechnology();
   std::vector<std::string> EnabledTechnologies();
   std::vector<std::string> EnumerateDevices();
-  // TODO(cmasone): these two should be implemented by asking the currently
-  // active profile, once we have such a thing.
-  std::vector<std::string> EnumerateAvailableServices();
+  // TODO(cmasone): This should be implemented by filtering |services_|.
   std::vector<std::string> EnumerateWatchedServices();
   std::string GetActiveProfileName();
 
@@ -86,6 +87,7 @@
   ModemInfo modem_info_;
   bool running_;
   std::vector<DeviceRefPtr> devices_;
+  // We store Services in a vector, because we want to keep them sorted.
   std::vector<ServiceRefPtr> services_;
   std::vector<ProfileRefPtr> profiles_;
   ProfileRefPtr ephemeral_profile_;
diff --git a/manager_unittest.cc b/manager_unittest.cc
index d862979..6c3eb9e 100644
--- a/manager_unittest.cc
+++ b/manager_unittest.cc
@@ -4,12 +4,15 @@
 
 #include "shill/manager.h"
 
+#include <set>
+
 #include <glib.h>
 
 #include <base/callback_old.h>
 #include <base/logging.h>
 #include <base/memory/ref_counted.h>
 #include <base/message_loop.h>
+#include <base/stl_util-inl.h>
 #include <chromeos/dbus/service_constants.h>
 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
@@ -23,10 +26,10 @@
 #include "shill/shill_event.h"
 
 using std::map;
+using std::set;
 using std::string;
 using std::vector;
 
-
 namespace shill {
 using ::testing::Test;
 using ::testing::_;
@@ -35,6 +38,8 @@
 
 class ManagerTest : public PropertyStoreTest {
  public:
+  static const char kMockServiceName[];
+
   ManagerTest()
       : factory_(this),
         mock_device_(new NiceMock<MockDevice>(&control_interface_,
@@ -62,6 +67,8 @@
   scoped_refptr<MockDevice> mock_device2_;
 };
 
+const char ManagerTest::kMockServiceName[] = "mock-service";
+
 TEST_F(ManagerTest, Contains) {
   EXPECT_TRUE(manager_.store()->Contains(flimflam::kStateProperty));
   EXPECT_FALSE(manager_.store()->Contains(""));
@@ -105,18 +112,26 @@
   scoped_refptr<MockService> mock_service(
       new NiceMock<MockService>(&control_interface_,
                                 &dispatcher_,
-                                new MockProfile(&control_interface_, &glib_),
+                                &manager_,
                                 kService1));
-
+  EXPECT_CALL(*mock_service.get(), GetRpcIdentifier())
+      .WillRepeatedly(Return(kService1));
   scoped_refptr<MockService> mock_service2(
       new NiceMock<MockService>(&control_interface_,
                                 &dispatcher_,
-                                new MockProfile(&control_interface_, &glib_),
+                                &manager_,
                                 kService2));
+  EXPECT_CALL(*mock_service2.get(), GetRpcIdentifier())
+      .WillRepeatedly(Return(kService2));
 
   manager_.RegisterService(mock_service);
   manager_.RegisterService(mock_service2);
 
+  vector<string> rpc_ids = manager_.EnumerateAvailableServices();
+  set<string> ids(rpc_ids.begin(), rpc_ids.end());
+  EXPECT_EQ(ids.count(mock_service->GetRpcIdentifier()), 1);
+  EXPECT_EQ(ids.count(mock_service2->GetRpcIdentifier()), 1);
+
   EXPECT_TRUE(manager_.FindService(kService1).get() != NULL);
   EXPECT_TRUE(manager_.FindService(kService2).get() != NULL);
 }
@@ -163,6 +178,43 @@
   }
 }
 
+TEST_F(ManagerTest, MoveService) {
+  // I want to ensure that the Profiles are managing this Service object
+  // lifetime properly, so I can't hold a ref to it here.
+  ProfileRefPtr profile(new Profile(&control_interface_, &glib_, &manager_));
+  {
+    ServiceRefPtr s2(
+        new MockService(&control_interface_,
+                        &dispatcher_,
+                        &manager_,
+                        kMockServiceName));
+    profile->AdoptService(s2);
+    s2->set_profile(profile);
+  }
+
+  // Now, move the |service| to another profile.
+  manager_.MoveToActiveProfile(profile, profile->FindService(kMockServiceName));
+
+  // Force destruction of the original Profile, to ensure that the Service
+  // is kept alive and populated with data.
+  profile = NULL;
+  {
+    ServiceRefPtr serv(manager_.ActiveProfile()->FindService(kMockServiceName));
+    ASSERT_TRUE(serv.get() != NULL);
+    Error error(Error::kInvalidProperty, "");
+    ::DBus::Error dbus_error;
+    map<string, ::DBus::Variant> props;
+    bool expected = true;
+    serv->store()->SetBoolProperty(flimflam::kAutoConnectProperty,
+                                   expected,
+                                   &error);
+    DBusAdaptor::GetProperties(serv->store(), &props, &dbus_error);
+    ASSERT_TRUE(ContainsKey(props, flimflam::kAutoConnectProperty));
+    EXPECT_EQ(props[flimflam::kAutoConnectProperty].reader().get_bool(),
+              expected);
+  }
+}
+
 TEST_F(ManagerTest, Dispatch) {
   {
     ::DBus::Error error;
diff --git a/mock_profile.cc b/mock_profile.cc
index dd2ce98..628d9d2 100644
--- a/mock_profile.cc
+++ b/mock_profile.cc
@@ -18,8 +18,10 @@
 class GLib;
 class Manager;
 
-MockProfile::MockProfile(ControlInterface *control_interface, GLib *glib)
-    : Profile(control_interface, glib) {
+MockProfile::MockProfile(ControlInterface *control_interface,
+                         GLib *glib,
+                         Manager *manager)
+    : Profile(control_interface, glib, manager) {
 }
 
 MockProfile::~MockProfile() {}
diff --git a/mock_profile.h b/mock_profile.h
index 58baf91..1a4575e 100644
--- a/mock_profile.h
+++ b/mock_profile.h
@@ -20,7 +20,9 @@
 
 class MockProfile : public Profile {
  public:
-  MockProfile(ControlInterface *control_interface, GLib *glib);
+  MockProfile(ControlInterface *control_interface,
+              GLib *glib,
+              Manager *manager);
   virtual ~MockProfile();
 
   MOCK_METHOD1(MoveToActiveProfile, bool(const std::string &));
diff --git a/mock_service.cc b/mock_service.cc
index 5b262b4..e5d0052 100644
--- a/mock_service.cc
+++ b/mock_service.cc
@@ -13,17 +13,20 @@
 #include "shill/refptr_types.h"
 
 using std::string;
+using testing::Return;
 
 namespace shill {
 
 class ControlInterface;
 class EventDispatcher;
+class Manager;
 
 MockService::MockService(ControlInterface *control_interface,
                          EventDispatcher *dispatcher,
-                         const ProfileRefPtr &profile,
+                         Manager *manager,
                          const string& name)
-    : Service(control_interface, dispatcher, profile, name) {
+    : Service(control_interface, dispatcher, manager, name) {
+  ON_CALL(*this, GetRpcIdentifier()).WillByDefault(Return(""));
 }
 
 MockService::~MockService() {}
diff --git a/mock_service.h b/mock_service.h
index 0a7cd74..5137b19 100644
--- a/mock_service.h
+++ b/mock_service.h
@@ -15,20 +15,22 @@
 
 class ControlInterface;
 class EventDispatcher;
+class Manager;
 
 class MockService : public Service {
  public:
   // A constructor for the Service object
   MockService(ControlInterface *control_interface,
               EventDispatcher *dispatcher,
-              const ProfileRefPtr &profile,
+              Manager *manager,
               const std::string& name);
   virtual ~MockService();
 
-  MOCK_METHOD0(Connect, void(void));
-  MOCK_METHOD0(Disconnect, void(void));
-  MOCK_METHOD0(CalculateState, std::string(void));
-  MOCK_METHOD0(GetDeviceRpcId, std::string(void));
+  MOCK_METHOD0(Connect, void());
+  MOCK_METHOD0(Disconnect, void());
+  MOCK_METHOD0(CalculateState, std::string());
+  MOCK_METHOD0(GetDeviceRpcId, std::string());
+  MOCK_CONST_METHOD0(GetRpcIdentifier, std::string());
  private:
   DISALLOW_COPY_AND_ASSIGN(MockService);
 };
diff --git a/profile.cc b/profile.cc
index 799f253..cf6fab7 100644
--- a/profile.cc
+++ b/profile.cc
@@ -4,18 +4,25 @@
 
 #include "shill/profile.h"
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include <base/logging.h>
+#include <base/stl_util-inl.h>
 #include <base/string_util.h>
+#include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/adaptor_interfaces.h"
 #include "shill/control_interface.h"
+#include "shill/manager.h"
 #include "shill/property_accessor.h"
 #include "shill/service.h"
 
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
 
@@ -23,8 +30,10 @@
 const char Profile::kUserStorageDirFormat[] = "/home/%s/user/flimflam";
 
 Profile::Profile(ControlInterface *control_interface,
-                 GLib *glib)
-    : adaptor_(control_interface->CreateProfileAdaptor(this)),
+                 GLib *glib,
+                 Manager *manager)
+    : manager_(manager),
+      adaptor_(control_interface->CreateProfileAdaptor(this)),
       storage_(glib) {
   // flimflam::kCheckPortalListProperty: Registered in DefaultProfile
   // flimflam::kCountryProperty: Registered in DefaultProfile
@@ -33,20 +42,58 @@
   // flimflam::kOfflineModeProperty: Registered in DefaultProfile
   // flimflam::kPortalURLProperty: Registered in DefaultProfile
 
-  // TODO(cmasone): Implement these once we figure out where Profiles fit.
-  // HelpRegisterDerivedStrings(flimflam::kServicesProperty,
-  //                            &Manager::EnumerateAvailableServices,
-  //                            NULL);
+  HelpRegisterDerivedStrings(flimflam::kServicesProperty,
+                             &Profile::EnumerateAvailableServices,
+                             NULL);
   // HelpRegisterDerivedStrings(flimflam::kEntriesProperty,
-  //                            &Manager::EnumerateEntries,
+  //                            &Profile::EnumerateEntries,
   //                            NULL);
 }
 
 Profile::~Profile() {}
 
-void Profile::AdoptService(const std::string &name,
-                           const ServiceRefPtr &service) {
-  services_[name] = service;
+bool Profile::AdoptService(const ServiceRefPtr &service) {
+  if (ContainsKey(services_, service->UniqueName()))
+    return false;
+  service->set_profile(this);
+  services_[service->UniqueName()] = service;
+  return true;
+}
+
+bool Profile::AbandonService(const string &name) {
+  map<string, ServiceRefPtr>::iterator to_abandon = services_.find(name);
+  if (to_abandon != services_.end()) {
+    services_.erase(to_abandon);
+    return true;
+  }
+  return false;
+}
+
+bool Profile::DemoteService(const string &name) {
+  map<string, ServiceRefPtr>::iterator to_demote = services_.find(name);
+  if (to_demote == services_.end())
+    return false;
+  return true;  // TODO(cmasone): mark |to_demote| as inactive or something.
+}
+
+bool Profile::MergeService(const ServiceRefPtr &service) {
+  map<string, ServiceRefPtr>::iterator it;
+  for (it = services_.begin(); it != services_.end(); ++it) {
+    if (Mergeable(it->second, service))
+      return true;  // TODO(cmasone): Perform merge.
+  }
+  return false;
+}
+
+ServiceRefPtr Profile::FindService(const std::string& name) {
+  if (ContainsKey(services_, name))
+    return services_[name];
+  return NULL;
+}
+
+void Profile::Finalize() {
+  // TODO(cmasone): Flush all of |services_| to disk if needed.
+  services_.clear();
 }
 
 bool Profile::IsValidIdentifierToken(const std::string &token) {
@@ -106,4 +153,25 @@
   return true;
 }
 
+vector<string> Profile::EnumerateAvailableServices() {
+  return manager_->EnumerateAvailableServices();
+}
+
+vector<string> Profile::EnumerateEntries() {
+  vector<string> rpc_ids;
+  map<string, ServiceRefPtr>::const_iterator it;
+  for (it = services_.begin(); it != services_.end(); ++it) {
+    rpc_ids.push_back(it->second->GetRpcIdentifier());
+  }
+  return rpc_ids;
+}
+
+void Profile::HelpRegisterDerivedStrings(const string &name,
+                                     Strings(Profile::*get)(void),
+                                     bool(Profile::*set)(const Strings&)) {
+  store_.RegisterDerivedStrings(
+      name,
+      StringsAccessor(new CustomAccessor<Profile, Strings>(this, get, set)));
+}
+
 }  // namespace shill
diff --git a/profile.h b/profile.h
index 8b18103..ff96aaf 100644
--- a/profile.h
+++ b/profile.h
@@ -24,6 +24,7 @@
 class ControlInterface;
 class Error;
 class GLib;
+class Manager;
 class ProfileAdaptorInterface;
 
 class Profile : public base::RefCounted<Profile> {
@@ -36,19 +37,38 @@
   static const char kGlobalStorageDir[];
   static const char kUserStorageDirFormat[];
 
-  Profile(ControlInterface *control_interface, GLib *glib);
+  Profile(ControlInterface *control_interface, GLib *glib, Manager *manager);
   virtual ~Profile();
 
   const std::string &name() { return name_; }
 
   PropertyStore *store() { return &store_; }
 
-  // Begin managing the persistence of |service|, addressable by |name|.
-  void AdoptService(const std::string &name, const ServiceRefPtr &service);
+  // Begin managing the persistence of |service|.
+  // Returns true if |service| is new to this profile and was added,
+  // false if the |service| already existed.
+  bool AdoptService(const ServiceRefPtr &service);
 
-  virtual bool MoveToActiveProfile(const std::string &name) {
-    return false;
-  }
+  // Cease managing the persistence of the Service named |name|.
+  // Returns true if |name| was found and abandoned, false if not found.
+  bool AbandonService(const std::string &name);
+
+  // Continue persisting the Service named |name|, but don't consider it
+  // usable for connectivity.
+  // Returns true if |name| was found and demoted, false if not found.
+  bool DemoteService(const std::string &name);
+
+  // Determine if |service| represents a service that's already in |services_|.
+  // If so, merge them smartly and return true.  If not, return false.
+  bool MergeService(const ServiceRefPtr &service);
+
+  ServiceRefPtr FindService(const std::string& name);
+
+  std::vector<std::string> EnumerateAvailableServices();
+  std::vector<std::string> EnumerateEntries();
+
+  // Flush any pending entry info to disk and stop managing service persistence.
+  virtual void Finalize();
 
   // Parses a profile identifier. There're two acceptable forms of the |raw|
   // identifier: "identifier" and "~user/identifier". Both "user" and
@@ -68,13 +88,12 @@
   static bool GetStoragePath(const Identifier &identifier, FilePath *path);
 
  protected:
+  Manager *manager_;
+
   // Properties to be get/set via PropertyStore calls that must also be visible
   // in subclasses.
   PropertyStore store_;
 
-  // Entries representing services that are persisted to disk.
-  // A std::map because we will need random access for GetEntry(), but usually
-  // will want to iterate over all Services.
   std::map<std::string, ServiceRefPtr> services_;
 
  private:
@@ -85,6 +104,15 @@
 
   static bool IsValidIdentifierToken(const std::string &token);
 
+  // Returns true if |candidate| can be merged into |service|.
+  bool Mergeable(const ServiceRefPtr &service, const ServiceRefPtr &candiate) {
+    return false;
+  }
+
+  void HelpRegisterDerivedStrings(const std::string &name,
+                                  Strings(Profile::*get)(void),
+                                  bool(Profile::*set)(const Strings&));
+
   scoped_ptr<ProfileAdaptorInterface> adaptor_;
 
   // Persistent store associated with the profile.
diff --git a/profile_unittest.cc b/profile_unittest.cc
index 8729d19..115afe8 100644
--- a/profile_unittest.cc
+++ b/profile_unittest.cc
@@ -4,15 +4,32 @@
 
 #include "shill/profile.h"
 
+#include <string>
+#include <vector>
+
 #include <base/string_util.h>
 #include <gtest/gtest.h>
 
+#include "shill/mock_profile.h"
+#include "shill/mock_service.h"
+#include "shill/property_store_unittest.h"
+
+using std::set;
 using std::string;
-using testing::Test;
+using std::vector;
+using testing::Return;
+using testing::StrictMock;
 
 namespace shill {
 
-class ProfileTest : public Test {
+class ProfileTest : public PropertyStoreTest {
+ public:
+  ProfileTest()
+      : profile_(new Profile(&control_interface_, &glib_, &manager_)) {
+  }
+
+ protected:
+  ProfileRefPtr profile_;
 };
 
 TEST_F(ProfileTest, IsValidIdentifierToken) {
@@ -82,4 +99,67 @@
   EXPECT_EQ("/home/chronos/user/flimflam/someprofile.profile", path.value());
 }
 
+TEST_F(ProfileTest, ServiceManagement) {
+  const char kService1[] = "service1";
+  const char kService2[] = "wifi_service2";
+  {
+    ServiceRefPtr service1(
+        new StrictMock<MockService>(&control_interface_,
+                                    &dispatcher_,
+                                    &manager_,
+                                    kService1));
+    ServiceRefPtr service2(
+        new StrictMock<MockService>(&control_interface_,
+                                    &dispatcher_,
+                                    &manager_,
+                                    kService2));
+    ASSERT_TRUE(profile_->AdoptService(service1));
+    ASSERT_TRUE(profile_->AdoptService(service2));
+    ASSERT_FALSE(profile_->AdoptService(service1));
+  }
+  ASSERT_TRUE(profile_->FindService(kService1).get() != NULL);
+  ASSERT_TRUE(profile_->FindService(kService2).get() != NULL);
+
+  ASSERT_TRUE(profile_->AbandonService(kService1));
+  ASSERT_TRUE(profile_->FindService(kService1).get() == NULL);
+  ASSERT_TRUE(profile_->FindService(kService2).get() != NULL);
+
+  ASSERT_FALSE(profile_->AbandonService(kService1));
+  ASSERT_TRUE(profile_->AbandonService(kService2));
+  ASSERT_TRUE(profile_->FindService(kService1).get() == NULL);
+  ASSERT_TRUE(profile_->FindService(kService2).get() == NULL);
+}
+
+TEST_F(ProfileTest, EntryEnumeration) {
+  const char *kServices[] = { "service1",
+                              "wifi_service2" };
+  scoped_refptr<MockService> service1(
+      new StrictMock<MockService>(&control_interface_,
+                                  &dispatcher_,
+                                  &manager_,
+                                  kServices[0]));
+  EXPECT_CALL(*service1.get(), GetRpcIdentifier())
+      .WillRepeatedly(Return(kServices[0]));
+  scoped_refptr<MockService> service2(
+      new StrictMock<MockService>(&control_interface_,
+                                  &dispatcher_,
+                                  &manager_,
+                                  kServices[1]));
+  EXPECT_CALL(*service2.get(), GetRpcIdentifier())
+      .WillRepeatedly(Return(kServices[1]));
+  ASSERT_TRUE(profile_->AdoptService(service1));
+  ASSERT_TRUE(profile_->AdoptService(service2));
+
+  ASSERT_EQ(profile_->EnumerateEntries().size(), 2);
+
+  ASSERT_TRUE(profile_->AbandonService(kServices[0]));
+  ASSERT_EQ(profile_->EnumerateEntries()[0], kServices[1]);
+
+  ASSERT_FALSE(profile_->AbandonService(kServices[0]));
+  ASSERT_EQ(profile_->EnumerateEntries()[0], kServices[1]);
+
+  ASSERT_TRUE(profile_->AbandonService(kServices[1]));
+  ASSERT_EQ(profile_->EnumerateEntries().size(), 0);
+}
+
 } // namespace shill
diff --git a/service.cc b/service.cc
index 395af35..c842ca2 100644
--- a/service.cc
+++ b/service.cc
@@ -17,6 +17,7 @@
 
 #include "shill/control_interface.h"
 #include "shill/error.h"
+#include "shill/manager.h"
 #include "shill/profile.h"
 #include "shill/property_accessor.h"
 #include "shill/refptr_types.h"
@@ -58,7 +59,7 @@
 
 Service::Service(ControlInterface *control_interface,
                  EventDispatcher *dispatcher,
-                 const ProfileRefPtr &profile,
+                 Manager *manager,
                  const string& name)
     : auto_connect_(false),
       check_portal_(kCheckPortalAuto),
@@ -66,14 +67,14 @@
       favorite_(false),
       priority_(kPriorityNone),
       save_credentials_(true),
-      profile_(profile),
       dispatcher_(dispatcher),
       name_(name),
       available_(false),
       configured_(false),
       configuration_(NULL),
       connection_(NULL),
-      adaptor_(control_interface->CreateServiceAdaptor(this)) {
+      adaptor_(control_interface->CreateServiceAdaptor(this)),
+      manager_(manager) {
 
   store_.RegisterBool(flimflam::kAutoConnectProperty, &auto_connect_);
 
@@ -144,7 +145,7 @@
 
 Service::~Service() {}
 
-string Service::GetRpcIdentifier() {
+string Service::GetRpcIdentifier() const {
   return adaptor_->GetRpcIdentifier();
 }
 
@@ -313,4 +314,8 @@
   return true;
 }
 
+const ProfileRefPtr &Service::profile() const { return profile_; }
+
+void Service::set_profile(const ProfileRefPtr &p) { profile_ = p; }
+
 }  // namespace shill
diff --git a/service.h b/service.h
index f22e646..62979cc 100644
--- a/service.h
+++ b/service.h
@@ -25,6 +25,7 @@
 class Endpoint;
 class Error;
 class EventDispatcher;
+class Manager;
 class ServiceAdaptorInterface;
 class StoreInterface;
 
@@ -83,7 +84,7 @@
   // A constructor for the Service object
   Service(ControlInterface *control_interface,
           EventDispatcher *dispatcher,
-          const ProfileRefPtr &profile,
+          Manager *manager,
           const std::string& name);
   virtual ~Service();
 
@@ -94,9 +95,9 @@
 
   // Returns a string that is guaranteed to uniquely identify this Service
   // instance.
-  const std::string &UniqueName() { return name_; }
+  const std::string &UniqueName() const { return name_; }
 
-  std::string GetRpcIdentifier();
+  virtual std::string GetRpcIdentifier() const;
 
   // Returns the unique persistent storage identifier for the service.
   std::string GetStorageIdentifier();
@@ -110,6 +111,9 @@
   bool auto_connect() const { return auto_connect_; }
   void set_auto_connect(bool connect) { auto_connect_ = connect; }
 
+  const ProfileRefPtr &profile() const;
+  void set_profile(const ProfileRefPtr &p);
+
   PropertyStore *store() { return &store_; }
 
  protected:
@@ -196,6 +200,7 @@
   Configuration *configuration_;
   Connection *connection_;
   scoped_ptr<ServiceAdaptorInterface> adaptor_;
+  Manager *manager_;
 
   DISALLOW_COPY_AND_ASSIGN(Service);
 };
diff --git a/service_unittest.cc b/service_unittest.cc
index 9b5b023..f116db6 100644
--- a/service_unittest.cc
+++ b/service_unittest.cc
@@ -18,7 +18,6 @@
 #include "shill/manager.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_control.h"
-#include "shill/mock_profile.h"
 #include "shill/mock_service.h"
 #include "shill/mock_store.h"
 #include "shill/property_store_unittest.h"
@@ -45,7 +44,7 @@
   ServiceTest()
       : service_(new MockService(&control_interface_,
                                  &dispatcher_,
-                                 new MockProfile(&control_interface_, &glib_),
+                                 &manager_,
                                  kMockServiceName)) {
   }
 
@@ -153,46 +152,6 @@
   }
 }
 
-TEST_F(ServiceTest, MoveService) {
-  // I want to ensure that the Profiles are managing this Service object
-  // lifetime properly, so I can't hold a ref to it here.
-  ProfileRefPtr profile(new Profile(&control_interface_, &glib_));
-  {
-    ServiceRefPtr s2(
-        new MockService(&control_interface_,
-                        &dispatcher_,
-                        new MockProfile(&control_interface_, &glib_),
-                        kMockServiceName));
-    profile->services_[kMockServiceName] = s2;
-  }
-
-  // Now, move the service to another profile.
-  ProfileRefPtr profile2(new Profile(&control_interface_, &glib_));
-  map<string, ServiceRefPtr>::iterator it =
-      profile->services_.find(kMockServiceName);
-  ASSERT_TRUE(it != profile->services_.end());
-
-  profile2->AdoptService(it->first, it->second);
-  // Force destruction of the original Profile, to ensure that the Service
-  // is kept alive and populated with data.
-  profile = NULL;
-  {
-    map<string, ServiceRefPtr>::iterator it =
-        profile2->services_.find(kMockServiceName);
-    Error error(Error::kInvalidProperty, "");
-    ::DBus::Error dbus_error;
-    map<string, ::DBus::Variant> props;
-    bool expected = true;
-    it->second->store()->SetBoolProperty(flimflam::kAutoConnectProperty,
-                                         expected,
-                                         &error);
-    DBusAdaptor::GetProperties(it->second->store(), &props, &dbus_error);
-    ASSERT_FALSE(props.find(flimflam::kAutoConnectProperty) == props.end());
-    EXPECT_EQ(props[flimflam::kAutoConnectProperty].reader().get_bool(),
-              expected);
-  }
-}
-
 TEST_F(ServiceTest, GetStorageIdentifier) {
   EXPECT_EQ(kMockServiceName, service_->GetStorageIdentifier());
 }
diff --git a/wifi.cc b/wifi.cc
index 04a0f56..832127d 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -293,8 +293,8 @@
       ServiceRefPtr service(
           new WiFiService(control_interface_,
                           dispatcher_,
+                          manager_,
                           this,
-                          manager_->ActiveProfile(),
                           endpoint.ssid(),
                           endpoint.network_mode(),
                           kSupplicantKeyModeNone,
diff --git a/wifi_service.cc b/wifi_service.cc
index c3ff605..b536165 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -20,13 +20,13 @@
 
 WiFiService::WiFiService(ControlInterface *control_interface,
                          EventDispatcher *dispatcher,
+                         Manager *manager,
                          const WiFiRefPtr &device,
-                         const ProfileRefPtr &profile,
                          const std::vector<uint8_t> ssid,
                          uint32_t mode,
                          const std::string &key_management,
                          const std::string &name)
-    : Service(control_interface, dispatcher, profile, name),
+    : Service(control_interface, dispatcher, manager, name),
       task_factory_(this),
       wifi_(device),
       ssid_(ssid),
diff --git a/wifi_service.h b/wifi_service.h
index c144a83..5678029 100644
--- a/wifi_service.h
+++ b/wifi_service.h
@@ -15,12 +15,16 @@
 
 namespace shill {
 
+class ControlInterface;
+class EventDispatcher;
+class Manager;
+
 class WiFiService : public Service {
  public:
   WiFiService(ControlInterface *control_interface,
               EventDispatcher *dispatcher,
+              Manager *manager,
               const WiFiRefPtr &device,
-              const ProfileRefPtr &profile,
               const std::vector<uint8_t> ssid,
               uint32_t mode,
               const std::string &key_management,