shill: Add CellularCapabilityUniversal

Add the CellularCapabilityUniversal class so that shill can talk to a
ModemManager1 DBUS interface.  Ensure that a modems can be created
with either the new or the old modem manager running.

Register to receive DBus property changes from ModemManager1

BUG=chromium-os:28596, chromium-os:26650
TEST=Run unit tests, test that modem is created with Y3300

Change-Id: I8717318e944589bc85e763bd7234336559256dbc
Reviewed-on: https://gerrit.chromium.org/gerrit/19888
Commit-Ready: Jason Glasgow <jglasgow@chromium.org>
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Tested-by: Jason Glasgow <jglasgow@chromium.org>
diff --git a/Makefile b/Makefile
index f06688a..f6ef921 100644
--- a/Makefile
+++ b/Makefile
@@ -105,7 +105,9 @@
 	cellular.o \
 	cellular_capability.o \
 	cellular_capability_cdma.o \
+	cellular_capability_classic.o \
 	cellular_capability_gsm.o \
+	cellular_capability_universal.o \
 	cellular_error.o \
 	cellular_service.o \
 	connection.o \
@@ -212,8 +214,8 @@
 	async_connection_unittest.o \
 	byte_string_unittest.o \
 	cellular_capability_cdma_unittest.o \
+	cellular_capability_classic_unittest.o \
 	cellular_capability_gsm_unittest.o \
-	cellular_capability_unittest.o \
 	cellular_service_unittest.o \
 	cellular_unittest.o \
 	crypto_des_cbc_unittest.o \
@@ -290,6 +292,7 @@
 	mock_vpn_service.o \
 	mock_wifi.o \
 	mock_wifi_service.o \
+	modem_1_unittest.o \
 	modem_info_unittest.o \
 	modem_manager_unittest.o \
 	modem_unittest.o \
diff --git a/cellular.cc b/cellular.cc
index d7058e6..34a4c3f 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -15,12 +15,12 @@
 #include <base/logging.h>
 #include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
-#include <mm/mm-modem.h>
 #include <mobile_provider.h>
 
 #include "shill/adaptor_interfaces.h"
 #include "shill/cellular_capability_cdma.h"
 #include "shill/cellular_capability_gsm.h"
+#include "shill/cellular_capability_universal.h"
 #include "shill/cellular_service.h"
 #include "shill/control_interface.h"
 #include "shill/device.h"
@@ -35,8 +35,6 @@
 #include "shill/technology.h"
 
 using base::Bind;
-using base::Closure;
-using std::map;
 using std::string;
 using std::vector;
 
@@ -210,13 +208,13 @@
       capability_.reset(new CellularCapabilityCDMA(this, proxy_factory));
       break;
     case kTypeUniversal:
-      LOG(ERROR) << "Cannot InitCapability on MM1 modem";
+      capability_.reset(new CellularCapabilityUniversal(this, proxy_factory));
       break;
     default: NOTREACHED();
   }
 }
 
-void Cellular::Activate(const std::string &carrier,
+void Cellular::Activate(const string &carrier,
                         Error *error, const ResultCallback &callback) {
   capability_->Activate(carrier, error, callback);
 }
@@ -327,7 +325,7 @@
   }
   CHECK_EQ(kStateRegistered, state_);
 
-  if (!capability_->allow_roaming() &&
+  if (!capability_->AllowRoaming() &&
       service_->roaming_state() == flimflam::kRoamingStateRoaming) {
     Error::PopulateAndLog(error, Error::kNotOnHomeNetwork,
                           "Roaming disallowed; connection request ignored.");
@@ -344,7 +342,7 @@
 void Cellular::OnConnected() {
   VLOG(2) << __func__;
   SetState(kStateConnected);
-  if (!capability_->allow_roaming() &&
+  if (!capability_->AllowRoaming() &&
       service_->roaming_state() == flimflam::kRoamingStateRoaming) {
     Disconnect(NULL);
   } else {
@@ -413,6 +411,15 @@
   }
 }
 
+void Cellular::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  capability_->OnDBusPropertiesChanged(interface,
+                                       changed_properties,
+                                       invalidated_properties);
+}
+
 void Cellular::OnModemManagerPropertiesChanged(
     const DBusPropertiesMap &properties) {
   capability_->OnModemManagerPropertiesChanged(properties);
diff --git a/cellular.h b/cellular.h
index 1c31831..472fa48 100644
--- a/cellular.h
+++ b/cellular.h
@@ -6,6 +6,7 @@
 #define SHILL_CELLULAR_
 
 #include <string>
+#include <vector>
 
 #include <base/basictypes.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
@@ -143,6 +144,10 @@
   // destroying or updating the CellularService.
   void HandleNewRegistrationState();
 
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
   virtual void OnModemManagerPropertiesChanged(
       const DBusPropertiesMap &properties);
 
@@ -181,6 +186,7 @@
   friend class CellularCapabilityTest;
   friend class CellularCapabilityCDMATest;
   friend class CellularCapabilityGSMTest;
+  friend class CellularServiceTest;
   friend class ModemTest;
   FRIEND_TEST(CellularCapabilityCDMATest, CreateFriendlyServiceName);
   FRIEND_TEST(CellularCapabilityCDMATest, GetRegistrationState);
diff --git a/cellular_capability.cc b/cellular_capability.cc
index f5d7630..3259f93 100644
--- a/cellular_capability.cc
+++ b/cellular_capability.cc
@@ -10,20 +10,12 @@
 #include "shill/cellular.h"
 #include "shill/error.h"
 #include "shill/property_accessor.h"
-#include "shill/proxy_factory.h"
 
-using base::Bind;
-using base::Callback;
 using base::Closure;
 using std::string;
 
 namespace shill {
 
-const char CellularCapability::kConnectPropertyApn[] = "apn";
-const char CellularCapability::kConnectPropertyApnUsername[] = "username";
-const char CellularCapability::kConnectPropertyApnPassword[] = "password";
-const char CellularCapability::kConnectPropertyHomeOnly[] = "home_only";
-const char CellularCapability::kConnectPropertyPhoneNumber[] = "number";
 const char CellularCapability::kPropertyIMSI[] = "imsi";
 // All timeout values are in milliseconds
 const int CellularCapability::kTimeoutActivate = 120000;
@@ -35,30 +27,12 @@
 
 CellularCapability::CellularCapability(Cellular *cellular,
                                        ProxyFactory *proxy_factory)
-    : allow_roaming_(false),
-      scanning_supported_(false),
-      cellular_(cellular),
+    : cellular_(cellular),
       proxy_factory_(proxy_factory),
-      weak_ptr_factory_(this) {
-  PropertyStore *store = cellular->mutable_store();
-  store->RegisterConstString(flimflam::kCarrierProperty, &carrier_);
+      allow_roaming_(false) {
   HelpRegisterDerivedBool(flimflam::kCellularAllowRoamingProperty,
                           &CellularCapability::GetAllowRoaming,
                           &CellularCapability::SetAllowRoaming);
-  store->RegisterConstBool(flimflam::kSupportNetworkScanProperty,
-                           &scanning_supported_);
-  store->RegisterConstString(flimflam::kEsnProperty, &esn_);
-  store->RegisterConstString(flimflam::kFirmwareRevisionProperty,
-                             &firmware_revision_);
-  store->RegisterConstString(flimflam::kHardwareRevisionProperty,
-                             &hardware_revision_);
-  store->RegisterConstString(flimflam::kImeiProperty, &imei_);
-  store->RegisterConstString(flimflam::kImsiProperty, &imsi_);
-  store->RegisterConstString(flimflam::kManufacturerProperty, &manufacturer_);
-  store->RegisterConstString(flimflam::kMdnProperty, &mdn_);
-  store->RegisterConstString(flimflam::kMeidProperty, &meid_);
-  store->RegisterConstString(flimflam::kMinProperty, &min_);
-  store->RegisterConstString(flimflam::kModelIDProperty, &model_id_);
 }
 
 CellularCapability::~CellularCapability() {}
@@ -79,7 +53,11 @@
     return;
   }
   allow_roaming_ = value;
-  if (!value && GetRoamingStateString() == flimflam::kRoamingStateRoaming) {
+  // Use AllowRoaming() instead of allow_roaming_ in order to
+  // incorporate provider preferences when evaluating if a disconnect
+  // is required.
+  if (!AllowRoaming() &&
+      GetRoamingStateString() == flimflam::kRoamingStateRoaming) {
     Error error;
     cellular()->Disconnect(&error);
   }
@@ -108,34 +86,6 @@
   callback.Run(error);
 }
 
-void CellularCapability::InitProxies() {
-  VLOG(2) << __func__;
-  proxy_.reset(proxy_factory()->CreateModemProxy(
-      cellular()->dbus_path(), cellular()->dbus_owner()));
-  simple_proxy_.reset(proxy_factory()->CreateModemSimpleProxy(
-      cellular()->dbus_path(), cellular()->dbus_owner()));
-  proxy_->set_state_changed_callback(
-      Bind(&CellularCapability::OnModemStateChangedSignal,
-           weak_ptr_factory_.GetWeakPtr()));
-}
-
-void CellularCapability::ReleaseProxies() {
-  VLOG(2) << __func__;
-  proxy_.reset();
-  simple_proxy_.reset();
-}
-
-void CellularCapability::FinishEnable(const ResultCallback &callback) {
-  callback.Run(Error());
-  GetRegistrationState();
-  GetSignalQuality();
-}
-
-void CellularCapability::FinishDisable(const ResultCallback &callback) {
-  ReleaseProxies();
-  callback.Run(Error());
-}
-
 void CellularCapability::OnUnsupportedOperation(
     const char *operation,
     Error *error) {
@@ -144,97 +94,6 @@
   Error::PopulateAndLog(error, Error::kNotSupported, message);
 }
 
-// always called from an async context
-void CellularCapability::EnableModem(const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  CHECK(!callback.is_null());
-  Error error;
-  proxy_->Enable(true, &error, callback, kTimeoutEnable);
-  if (error.IsFailure())
-    callback.Run(error);
-}
-
-// always called from an async context
-void CellularCapability::DisableModem(const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  CHECK(!callback.is_null());
-  Error error;
-  proxy_->Enable(false, &error, callback, kTimeoutDefault);
-  if (error.IsFailure())
-      callback.Run(error);
-}
-
-// always called from an async context
-void CellularCapability::GetModemStatus(const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  CHECK(!callback.is_null());
-  DBusPropertyMapCallback cb = Bind(&CellularCapability::OnGetModemStatusReply,
-                                    weak_ptr_factory_.GetWeakPtr(), callback);
-  Error error;
-  simple_proxy_->GetModemStatus(&error, cb, kTimeoutDefault);
-  if (error.IsFailure())
-      callback.Run(error);
-}
-
-// always called from an async context
-void CellularCapability::GetModemInfo(const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  CHECK(!callback.is_null());
-  ModemInfoCallback cb = Bind(&CellularCapability::OnGetModemInfoReply,
-                              weak_ptr_factory_.GetWeakPtr(), callback);
-  Error error;
-  proxy_->GetModemInfo(&error, cb, kTimeoutDefault);
-  if (error.IsFailure())
-      callback.Run(error);
-}
-
-void CellularCapability::StopModem(Error *error,
-                                   const ResultCallback &callback) {
-  VLOG(2) << __func__;
-
-  CellularTaskList *tasks = new CellularTaskList();
-  ResultCallback cb =
-      Bind(&CellularCapability::StepCompletedCallback,
-           weak_ptr_factory_.GetWeakPtr(), callback, false, tasks);
-  ResultCallback cb_ignore_error =
-      Bind(&CellularCapability::StepCompletedCallback,
-           weak_ptr_factory_.GetWeakPtr(), callback, true, tasks);
-  tasks->push_back(Bind(&CellularCapability::Disconnect,
-                        weak_ptr_factory_.GetWeakPtr(),
-                        static_cast<Error *>(NULL), cb_ignore_error));
-  tasks->push_back(Bind(&CellularCapability::DisableModem,
-                        weak_ptr_factory_.GetWeakPtr(), cb));
-  tasks->push_back(Bind(&CellularCapability::FinishDisable,
-                        weak_ptr_factory_.GetWeakPtr(), cb));
-
-  RunNextStep(tasks);
-}
-
-void CellularCapability::Connect(const DBusPropertiesMap &properties,
-                                 Error *error,
-                                 const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  ResultCallback cb = Bind(&CellularCapability::OnConnectReply,
-                           weak_ptr_factory_.GetWeakPtr(),
-                           callback);
-  simple_proxy_->Connect(properties, error, cb, kTimeoutConnect);
-}
-
-void CellularCapability::Disconnect(Error *error,
-                                    const ResultCallback &callback) {
-  VLOG(2) << __func__;
-  ResultCallback cb = Bind(&CellularCapability::OnDisconnectReply,
-                           weak_ptr_factory_.GetWeakPtr(),
-                           callback);
-  proxy_->Disconnect(error, cb, kTimeoutDefault);
-}
-
-void CellularCapability::Activate(const string &/*carrier*/,
-                                  Error *error,
-                                  const ResultCallback &/*callback*/) {
-  OnUnsupportedOperation(__func__, error);
-}
-
 void CellularCapability::RegisterOnNetwork(
     const string &/*network_id*/,
     Error *error, const ResultCallback &/*callback*/) {
@@ -273,91 +132,4 @@
   OnUnsupportedOperation(__func__, error);
 }
 
-void CellularCapability::OnGetModemStatusReply(
-    const ResultCallback &callback,
-    const DBusPropertiesMap &props,
-    const Error &error) {
-  VLOG(2) << __func__ << " " << props.size() << " props. error " << error;
-  if (error.IsSuccess()) {
-    DBusProperties::GetString(props, "carrier", &carrier_);
-    DBusProperties::GetString(props, "meid", &meid_);
-    DBusProperties::GetString(props, "imei", &imei_);
-    DBusProperties::GetString(props, kPropertyIMSI, &imsi_);
-    DBusProperties::GetString(props, "esn", &esn_);
-    DBusProperties::GetString(props, "mdn", &mdn_);
-    DBusProperties::GetString(props, "min", &min_);
-    DBusProperties::GetString(props, "firmware_revision", &firmware_revision_);
-
-    uint32 state;
-    if (DBusProperties::GetUint32(props, "state", &state))
-      cellular()->set_modem_state(static_cast<Cellular::ModemState>(state));
-
-    UpdateStatus(props);
-  }
-  callback.Run(error);
-}
-
-void CellularCapability::OnGetModemInfoReply(
-    const ResultCallback &callback,
-    const ModemHardwareInfo &info,
-    const Error &error) {
-  VLOG(2) << __func__ << "(" << error << ")";
-  if (error.IsSuccess()) {
-    manufacturer_ = info._1;
-    model_id_ = info._2;
-    hardware_revision_ = info._3;
-    VLOG(2) << __func__ << ": " << info._1 << ", " << info._2 << ", "
-            << info._3;
-  }
-  callback.Run(error);
-}
-
-// TODO(ers): use the supplied callback when Connect is fully asynchronous
-void CellularCapability::OnConnectReply(const ResultCallback &callback,
-                                        const Error &error) {
-  VLOG(2) << __func__ << "(" << error << ")";
-  if (error.IsSuccess())
-    cellular()->OnConnected();
-  else
-    cellular()->OnConnectFailed(error);
-  if (!callback.is_null())
-    callback.Run(error);
-}
-
-// TODO(ers): use the supplied callback when Disonnect is fully asynchronous
-void CellularCapability::OnDisconnectReply(const ResultCallback &callback,
-                                           const Error &error) {
-  VLOG(2) << __func__ << "(" << error << ")";
-  if (error.IsSuccess())
-    cellular()->OnDisconnected();
-  else
-    cellular()->OnDisconnectFailed();
-  if (!callback.is_null())
-    callback.Run(error);
-}
-
-void CellularCapability::OnModemStateChangedSignal(
-    uint32 old_state, uint32 new_state, uint32 reason) {
-  VLOG(2) << __func__ << "(" << old_state << ", " << new_state << ", "
-          << reason << ")";
-  // TODO(petkov): Complete this (crosbug.com/19662)
-#if 0
-  modem_state_ = static_cast<ModemState>(new_state);
-  if (old_state == new_state) {
-    return;
-  }
-  switch (new_state) {
-    case kModemStateEnabled:
-      if (old_state == kModemStateDisabled ||
-          old_state == kModemStateEnabling) {
-        Start();
-      }
-      // TODO(petkov): Handle the case when the state is downgraded to Enabled.
-      break;
-    default:
-      break;
-  }
-#endif
-}
-
 }  // namespace shill
diff --git a/cellular_capability.h b/cellular_capability.h
index ef0060f..52b7b08 100644
--- a/cellular_capability.h
+++ b/cellular_capability.h
@@ -11,26 +11,56 @@
 #include <base/basictypes.h>
 #include <base/callback.h>
 #include <base/memory/scoped_ptr.h>
-#include <base/memory/weak_ptr.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "shill/callbacks.h"
 #include "shill/dbus_properties.h"
-#include "shill/modem_proxy_interface.h"
-#include "shill/modem_simple_proxy_interface.h"
 
 namespace shill {
 
 class Cellular;
 class Error;
-class EventDispatcher;
 class ProxyFactory;
 
 typedef std::vector<base::Closure> CellularTaskList;
 
 // Cellular devices instantiate subclasses of CellularCapability that
 // handle the specific modem technologies and capabilities.
+//
+// The CellularCapability is directly subclassed by:
+// *  CelllularCapabilityUniversal which handles all modems managed by
+//    a modem manager using the the org.chromium.ModemManager1 DBUS
+//    interface
+// *  CellularCapabilityClassic which handles all modems managed by a
+//    modem manager using the older org.chromium.ModemManager DBUS
+//    interface.  This class is further subclassed to represent CDMA
+//    and GSM modems
+//
+// Pictorially:
+//
+// CellularCapability
+//       |
+//       |-- CellularCapabilityUniversal
+//       |
+//       |-- CellularCapabilityClassic
+//                    |
+//                    |-- CellularCapabilityGSM
+//                    |
+//                    |-- CellularCapabilityCDMA
+//
 class CellularCapability {
  public:
+
+  // SimLockStatus represents the fields in the Cellular.SIMLockStatus
+  // DBUS property of the shill device.
+  struct SimLockStatus {
+    SimLockStatus() : enabled(false), retries_left(0) {}
+
+    bool enabled;
+    std::string lock_type;
+    uint32 retries_left;
+  };
+
   static const int kTimeoutActivate;
   static const int kTimeoutConnect;
   static const int kTimeoutDefault;
@@ -38,14 +68,8 @@
   static const int kTimeoutRegister;
   static const int kTimeoutScan;
 
-  static const char kConnectPropertyApn[];
-  static const char kConnectPropertyApnUsername[];
-  static const char kConnectPropertyApnPassword[];
-  static const char kConnectPropertyHomeOnly[];
-  static const char kConnectPropertyPhoneNumber[];
   static const char kPropertyIMSI[];
 
-
   // |cellular| is the parent Cellular device.
   CellularCapability(Cellular *cellular, ProxyFactory *proxy_factory);
   virtual ~CellularCapability();
@@ -60,8 +84,6 @@
 
   virtual void SetupConnectProperties(DBusPropertiesMap *properties) = 0;
 
-  bool allow_roaming() const { return allow_roaming_; }
-
   // StartModem attempts to put the modem in a state in which it is
   // usable for creating services and establishing connections (if
   // network conditions permit). It potentially consists of multiple
@@ -75,21 +97,21 @@
   // StopModem disconnects and disables a modem asynchronously.
   // |callback| is invoked when this completes and the result is passed
   // to the callback.
-  virtual void StopModem(Error *error, const ResultCallback &callback);
+  virtual void StopModem(Error *error, const ResultCallback &callback) = 0;
   virtual void Connect(const DBusPropertiesMap &properties, Error *error,
-                       const ResultCallback &callback);
-  virtual void Disconnect(Error *error, const ResultCallback &callback);
+                       const ResultCallback &callback) = 0;
+  virtual void Disconnect(Error *error, const ResultCallback &callback) = 0;
 
   // Activates the modem. Returns an Error on failure.
   // The default implementation fails by returning a kNotSupported error
   // to the caller.
   virtual void Activate(const std::string &carrier,
-                        Error *error, const ResultCallback &callback);
+                        Error *error, const ResultCallback &callback) = 0;
 
   // Network registration.
   virtual void RegisterOnNetwork(const std::string &network_id,
                                  Error *error,
-                                 const ResultCallback &callback);
+                                 const ResultCallback &callback) = 0;
   virtual bool IsRegistered() = 0;
 
   virtual std::string CreateFriendlyServiceName() = 0;
@@ -106,8 +128,25 @@
                          const std::string &new_pin,
                          Error *error, const ResultCallback &callback);
 
-  // Network scanning. The default implementation fails by invoking
-  // the reply handler with an error.
+  // Asks the modem to scan for networks.
+  //
+  // The default implementation fails by filling error with
+  // kNotSupported.
+  //
+  // Subclasses should implement this by fetching scan results
+  // asynchronously.  When the results are ready, update the
+  // flimflam::kFoundNetworksProperty and send a property change
+  // notification.  Finally, callback must be invoked to inform the
+  // caller that the scan has completed.
+  //
+  // Errors are not generally reported, but on error the
+  // kFoundNetworksProperty should be cleared and a property change
+  // notification sent out.
+  //
+  // TODO(jglasgow): Refactor to reuse code by putting notification
+  // logic into Cellular or CellularCapability.
+  //
+  // TODO(jglasgow): Implement real error handling.
   virtual void Scan(Error *error, const ResultCallback &callback);
 
   // Returns an empty string if the network technology is unknown.
@@ -119,31 +158,29 @@
 
   virtual std::string GetTypeString() const = 0;
 
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) = 0;
   virtual void OnModemManagerPropertiesChanged(
       const DBusPropertiesMap &properties) = 0;
 
- protected:
-  // The following five methods are only ever called as
-  // callbacks (from the main loop), which is why they
-  // don't take an Error * argument.
-  virtual void EnableModem(const ResultCallback &callback);
-  virtual void DisableModem(const ResultCallback &callback);
-  virtual void GetModemStatus(const ResultCallback &callback);
-  virtual void GetModemInfo(const ResultCallback &callback);
-  virtual void GetProperties(const ResultCallback &callback) = 0;
+  // Should this device allow roaming?
+  // The decision to allow roaming or not is based on the home
+  // provider as well as on the user modifiable "allow_roaming"
+  // property.
+  virtual bool AllowRoaming() = 0;
 
+ protected:
   virtual void GetRegistrationState() = 0;
 
-  void FinishEnable(const ResultCallback &callback);
-  void FinishDisable(const ResultCallback &callback);
-  virtual void InitProxies();
-  virtual void ReleaseProxies();
+  // Releases all proxies held by the object.  This is most useful
+  // during unit tests.
+  virtual void ReleaseProxies() = 0;
 
   static void OnUnsupportedOperation(const char *operation, Error *error);
 
-  virtual void OnConnectReply(const ResultCallback &callback,
-                              const Error &error);
-  // Run the next task in a list.
+  // Runs the next task in a list.
   // Precondition: |tasks| is not empty.
   void RunNextStep(CellularTaskList *tasks);
   // StepCompletedCallback is called after a task completes.
@@ -154,73 +191,34 @@
   // the result of the just-completed task.
   void StepCompletedCallback(const ResultCallback &callback, bool ignore_error,
                              CellularTaskList *tasks, const Error &error);
-  // Properties
-  bool allow_roaming_;
-  bool scanning_supported_;
-  std::string carrier_;
-  std::string meid_;
-  std::string imei_;
-  std::string imsi_;
-  std::string esn_;
-  std::string mdn_;
-  std::string min_;
-  std::string model_id_;
-  std::string manufacturer_;
-  std::string firmware_revision_;
-  std::string hardware_revision_;
+
+  // accessor for subclasses to read the allow roaming property
+  bool allow_roaming_property() const { return allow_roaming_; }
 
  private:
   friend class CellularCapabilityGSMTest;
   friend class CellularCapabilityTest;
   friend class CellularTest;
-  FRIEND_TEST(CellularCapabilityGSMTest, SetStorageIdentifier);
-  FRIEND_TEST(CellularCapabilityGSMTest, UpdateStatus);
   FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
-  FRIEND_TEST(CellularCapabilityTest, EnableModemFail);
-  FRIEND_TEST(CellularCapabilityTest, EnableModemSucceed);
-  FRIEND_TEST(CellularCapabilityTest, FinishEnable);
-  FRIEND_TEST(CellularCapabilityTest, GetModemInfo);
-  FRIEND_TEST(CellularCapabilityTest, GetModemStatus);
-  FRIEND_TEST(CellularCapabilityTest, TryApns);
-  FRIEND_TEST(CellularServiceTest, FriendlyName);
-  FRIEND_TEST(CellularTest, StartCDMARegister);
-  FRIEND_TEST(CellularTest, StartConnected);
-  FRIEND_TEST(CellularTest, StartGSMRegister);
-  FRIEND_TEST(CellularTest, StartLinked);
   FRIEND_TEST(CellularTest, Connect);
-  FRIEND_TEST(CellularTest, ConnectFailure);
-  FRIEND_TEST(CellularTest, Disconnect);
+  FRIEND_TEST(CellularTest, TearDown);
 
   void HelpRegisterDerivedBool(
       const std::string &name,
       bool(CellularCapability::*get)(Error *error),
       void(CellularCapability::*set)(const bool &value, Error *error));
 
+  // DBUS accessors to read/modify the allow roaming property
   bool GetAllowRoaming(Error */*error*/) { return allow_roaming_; }
   void SetAllowRoaming(const bool &value, Error *error);
 
-  // Method reply and signal callbacks from Modem interface
-  virtual void OnModemStateChangedSignal(
-      uint32 old_state, uint32 new_state, uint32 reason);
-  virtual void OnGetModemInfoReply(const ResultCallback &callback,
-                                   const ModemHardwareInfo &info,
-                                   const Error &error);
-
-  // Method reply callbacks from Modem.Simple interface
-  virtual void OnGetModemStatusReply(const ResultCallback &callback,
-                                     const DBusPropertiesMap &props,
-                                     const Error &error);
-  virtual void OnDisconnectReply(const ResultCallback &callback,
-                                 const Error &error);
-
   Cellular *cellular_;
 
   // Store cached copies of singletons for speed/ease of testing.
   ProxyFactory *proxy_factory_;
 
-  scoped_ptr<ModemProxyInterface> proxy_;
-  scoped_ptr<ModemSimpleProxyInterface> simple_proxy_;
-  base::WeakPtrFactory<CellularCapability> weak_ptr_factory_;
+  // User preference to allow or disallow roaming
+  bool allow_roaming_;
 
   DISALLOW_COPY_AND_ASSIGN(CellularCapability);
 };
diff --git a/cellular_capability_cdma.cc b/cellular_capability_cdma.cc
index 1db4837..801f688 100644
--- a/cellular_capability_cdma.cc
+++ b/cellular_capability_cdma.cc
@@ -26,7 +26,7 @@
 
 CellularCapabilityCDMA::CellularCapabilityCDMA(Cellular *cellular,
                                                ProxyFactory *proxy_factory)
-    : CellularCapability(cellular, proxy_factory),
+    : CellularCapabilityClassic(cellular, proxy_factory),
       weak_ptr_factory_(this),
       activation_state_(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED),
       registration_state_evdo_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
@@ -38,7 +38,7 @@
 }
 
 void CellularCapabilityCDMA::InitProxies() {
-  CellularCapability::InitProxies();
+  CellularCapabilityClassic::InitProxies();
   proxy_.reset(proxy_factory()->CreateModemCDMAProxy(
       cellular()->dbus_path(), cellular()->dbus_owner()));
   proxy_->set_signal_quality_callback(
@@ -76,10 +76,15 @@
 }
 
 void CellularCapabilityCDMA::ReleaseProxies() {
-  CellularCapability::ReleaseProxies();
+  CellularCapabilityClassic::ReleaseProxies();
   proxy_.reset();
 }
 
+bool CellularCapabilityCDMA::AllowRoaming() {
+  return allow_roaming_property();
+}
+
+
 void CellularCapabilityCDMA::OnServiceCreated() {
   VLOG(2) << __func__;
   cellular()->service()->SetOLP(olp_);
diff --git a/cellular_capability_cdma.h b/cellular_capability_cdma.h
index 1dc2cf2..d38dca3 100644
--- a/cellular_capability_cdma.h
+++ b/cellular_capability_cdma.h
@@ -9,13 +9,16 @@
 #include <base/memory/weak_ptr.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include <string>
+
 #include "shill/cellular_capability.h"
+#include "shill/cellular_capability_classic.h"
 #include "shill/cellular_service.h"
 #include "shill/modem_cdma_proxy_interface.h"
 
 namespace shill {
 
-class CellularCapabilityCDMA : public CellularCapability {
+class CellularCapabilityCDMA : public CellularCapabilityClassic {
  public:
   CellularCapabilityCDMA(Cellular *cellular, ProxyFactory *proxy_factory);
 
@@ -46,6 +49,7 @@
  protected:
   virtual void InitProxies();
   virtual void ReleaseProxies();
+  virtual bool AllowRoaming();
 
  private:
   friend class CellularCapabilityCDMATest;
diff --git a/cellular_capability_classic.cc b/cellular_capability_classic.cc
new file mode 100644
index 0000000..be8a9ae
--- /dev/null
+++ b/cellular_capability_classic.cc
@@ -0,0 +1,316 @@
+// Copyright (c) 2012 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 "shill/cellular_capability_classic.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular.h"
+#include "shill/error.h"
+#include "shill/property_accessor.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using base::Callback;
+using base::Closure;
+using std::string;
+
+namespace shill {
+
+const char CellularCapabilityClassic::kConnectPropertyApn[] = "apn";
+const char CellularCapabilityClassic::kConnectPropertyApnUsername[] =
+    "username";
+const char CellularCapabilityClassic::kConnectPropertyApnPassword[] =
+    "password";
+const char CellularCapabilityClassic::kConnectPropertyHomeOnly[] = "home_only";
+const char CellularCapabilityClassic::kConnectPropertyPhoneNumber[] = "number";
+
+CellularCapabilityClassic::CellularCapabilityClassic(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory)
+    : CellularCapability(cellular, proxy_factory),
+      weak_ptr_factory_(this) {
+  PropertyStore *store = cellular->mutable_store();
+  store->RegisterConstString(flimflam::kCarrierProperty, &carrier_);
+  store->RegisterConstBool(flimflam::kSupportNetworkScanProperty,
+                           &scanning_supported_);
+  store->RegisterConstString(flimflam::kEsnProperty, &esn_);
+  store->RegisterConstString(flimflam::kFirmwareRevisionProperty,
+                             &firmware_revision_);
+  store->RegisterConstString(flimflam::kHardwareRevisionProperty,
+                             &hardware_revision_);
+  store->RegisterConstString(flimflam::kImeiProperty, &imei_);
+  store->RegisterConstString(flimflam::kImsiProperty, &imsi_);
+  store->RegisterConstString(flimflam::kManufacturerProperty, &manufacturer_);
+  store->RegisterConstString(flimflam::kMdnProperty, &mdn_);
+  store->RegisterConstString(flimflam::kMeidProperty, &meid_);
+  store->RegisterConstString(flimflam::kMinProperty, &min_);
+  store->RegisterConstString(flimflam::kModelIDProperty, &model_id_);
+}
+
+CellularCapabilityClassic::~CellularCapabilityClassic() {}
+
+void CellularCapabilityClassic::InitProxies() {
+  VLOG(2) << __func__;
+  proxy_.reset(proxy_factory()->CreateModemProxy(
+      cellular()->dbus_path(), cellular()->dbus_owner()));
+  simple_proxy_.reset(proxy_factory()->CreateModemSimpleProxy(
+      cellular()->dbus_path(), cellular()->dbus_owner()));
+  proxy_->set_state_changed_callback(
+      Bind(&CellularCapabilityClassic::OnModemStateChangedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CellularCapabilityClassic::ReleaseProxies() {
+  VLOG(2) << __func__;
+  proxy_.reset();
+  simple_proxy_.reset();
+}
+
+void CellularCapabilityClassic::FinishEnable(const ResultCallback &callback) {
+  callback.Run(Error());
+  GetRegistrationState();
+  GetSignalQuality();
+}
+
+void CellularCapabilityClassic::FinishDisable(const ResultCallback &callback) {
+  ReleaseProxies();
+  callback.Run(Error());
+}
+
+void CellularCapabilityClassic::OnUnsupportedOperation(
+    const char *operation,
+    Error *error) {
+  string message("The ");
+  message.append(operation).append(" operation is not supported.");
+  Error::PopulateAndLog(error, Error::kNotSupported, message);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::EnableModem(const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  proxy_->Enable(true, &error, callback, kTimeoutEnable);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::DisableModem(const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  proxy_->Enable(false, &error, callback, kTimeoutDefault);
+  if (error.IsFailure())
+      callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::GetModemStatus(const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  CHECK(!callback.is_null());
+  DBusPropertyMapCallback cb = Bind(
+      &CellularCapabilityClassic::OnGetModemStatusReply,
+      weak_ptr_factory_.GetWeakPtr(), callback);
+  Error error;
+  simple_proxy_->GetModemStatus(&error, cb, kTimeoutDefault);
+  if (error.IsFailure())
+      callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::GetModemInfo(const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  CHECK(!callback.is_null());
+  ModemInfoCallback cb = Bind(&CellularCapabilityClassic::OnGetModemInfoReply,
+                              weak_ptr_factory_.GetWeakPtr(), callback);
+  Error error;
+  proxy_->GetModemInfo(&error, cb, kTimeoutDefault);
+  if (error.IsFailure())
+      callback.Run(error);
+}
+
+void CellularCapabilityClassic::StopModem(Error *error,
+                                   const ResultCallback &callback) {
+  VLOG(2) << __func__;
+
+  CellularTaskList *tasks = new CellularTaskList();
+  ResultCallback cb =
+      Bind(&CellularCapabilityClassic::StepCompletedCallback,
+           weak_ptr_factory_.GetWeakPtr(), callback, false, tasks);
+  ResultCallback cb_ignore_error =
+      Bind(&CellularCapabilityClassic::StepCompletedCallback,
+           weak_ptr_factory_.GetWeakPtr(), callback, true, tasks);
+  tasks->push_back(Bind(&CellularCapabilityClassic::Disconnect,
+                        weak_ptr_factory_.GetWeakPtr(),
+                        static_cast<Error *>(NULL), cb_ignore_error));
+  tasks->push_back(Bind(&CellularCapabilityClassic::DisableModem,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityClassic::FinishDisable,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+
+  RunNextStep(tasks);
+}
+
+void CellularCapabilityClassic::Connect(const DBusPropertiesMap &properties,
+                                 Error *error,
+                                 const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  ResultCallback cb = Bind(&CellularCapabilityClassic::OnConnectReply,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           callback);
+  simple_proxy_->Connect(properties, error, cb, kTimeoutConnect);
+}
+
+void CellularCapabilityClassic::Disconnect(Error *error,
+                                    const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  ResultCallback cb = Bind(&CellularCapabilityClassic::OnDisconnectReply,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           callback);
+  proxy_->Disconnect(error, cb, kTimeoutDefault);
+}
+
+void CellularCapabilityClassic::Activate(const string &/*carrier*/,
+                                  Error *error,
+                                  const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::RegisterOnNetwork(
+    const string &/*network_id*/,
+    Error *error, const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::RequirePIN(const std::string &/*pin*/,
+                                    bool /*require*/,
+                                    Error *error,
+                                    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::EnterPIN(const string &/*pin*/,
+                                  Error *error,
+                                  const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::UnblockPIN(const string &/*unblock_code*/,
+                                    const string &/*pin*/,
+                                    Error *error,
+                                    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::ChangePIN(const string &/*old_pin*/,
+                                   const string &/*new_pin*/,
+                                   Error *error,
+                                   const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::Scan(Error *error,
+                              const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityClassic::OnDBusPropertiesChanged(
+    const std::string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const std::vector<std::string> &invalidated_properties) {
+  DCHECK(false) << "Unexpected call to OnDBusPropertiesChanged.";
+}
+
+void CellularCapabilityClassic::OnGetModemStatusReply(
+    const ResultCallback &callback,
+    const DBusPropertiesMap &props,
+    const Error &error) {
+  VLOG(2) << __func__ << " " << props.size() << " props. error " << error;
+  if (error.IsSuccess()) {
+    DBusProperties::GetString(props, "carrier", &carrier_);
+    DBusProperties::GetString(props, "meid", &meid_);
+    DBusProperties::GetString(props, "imei", &imei_);
+    DBusProperties::GetString(props, kPropertyIMSI, &imsi_);
+    DBusProperties::GetString(props, "esn", &esn_);
+    DBusProperties::GetString(props, "mdn", &mdn_);
+    DBusProperties::GetString(props, "min", &min_);
+    DBusProperties::GetString(props, "firmware_revision", &firmware_revision_);
+
+    uint32 state;
+    if (DBusProperties::GetUint32(props, "state", &state))
+      cellular()->set_modem_state(static_cast<Cellular::ModemState>(state));
+
+    UpdateStatus(props);
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityClassic::OnGetModemInfoReply(
+    const ResultCallback &callback,
+    const ModemHardwareInfo &info,
+    const Error &error) {
+  VLOG(2) << __func__ << "(" << error << ")";
+  if (error.IsSuccess()) {
+    manufacturer_ = info._1;
+    model_id_ = info._2;
+    hardware_revision_ = info._3;
+    VLOG(2) << __func__ << ": " << info._1 << ", " << info._2 << ", "
+            << info._3;
+  }
+  callback.Run(error);
+}
+
+// TODO(ers): use the supplied callback when Connect is fully asynchronous
+void CellularCapabilityClassic::OnConnectReply(const ResultCallback &callback,
+                                               const Error &error) {
+  VLOG(2) << __func__ << "(" << error << ")";
+  if (error.IsSuccess())
+    cellular()->OnConnected();
+  else
+    cellular()->OnConnectFailed(error);
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+// TODO(ers): use the supplied callback when Disonnect is fully asynchronous
+void CellularCapabilityClassic::OnDisconnectReply(
+    const ResultCallback &callback,
+    const Error &error) {
+  VLOG(2) << __func__ << "(" << error << ")";
+  if (error.IsSuccess())
+    cellular()->OnDisconnected();
+  else
+    cellular()->OnDisconnectFailed();
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+void CellularCapabilityClassic::OnModemStateChangedSignal(
+    uint32 old_state, uint32 new_state, uint32 reason) {
+  VLOG(2) << __func__ << "(" << old_state << ", " << new_state << ", "
+          << reason << ")";
+  // TODO(petkov): Complete this (crosbug.com/19662)
+#if 0
+  modem_state_ = static_cast<ModemState>(new_state);
+  if (old_state == new_state) {
+    return;
+  }
+  switch (new_state) {
+    case kModemStateEnabled:
+      if (old_state == kModemStateDisabled ||
+          old_state == kModemStateEnabling) {
+        Start();
+      }
+      // TODO(petkov): Handle the case when the state is downgraded to Enabled.
+      break;
+    default:
+      break;
+  }
+#endif
+}
+
+}  // namespace shill
diff --git a/cellular_capability_classic.h b/cellular_capability_classic.h
new file mode 100644
index 0000000..a796ffa
--- /dev/null
+++ b/cellular_capability_classic.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2012 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.
+
+#ifndef SHILL_CELLULAR_CAPABILITY_CLASSIC_
+#define SHILL_CELLULAR_CAPABILITY_CLASSIC_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <base/callback.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular_capability.h"
+#include "shill/dbus_properties.h"
+#include "shill/modem_proxy_interface.h"
+#include "shill/modem_simple_proxy_interface.h"
+
+namespace shill {
+
+class Cellular;
+class Error;
+class EventDispatcher;
+class ProxyFactory;
+
+// CellularCapabilityClassic handles modems using the
+// org.chromium.ModemManager DBUS interface.
+class CellularCapabilityClassic : public CellularCapability {
+ public:
+  static const char kConnectPropertyApn[];
+  static const char kConnectPropertyApnUsername[];
+  static const char kConnectPropertyApnPassword[];
+  static const char kConnectPropertyHomeOnly[];
+  static const char kConnectPropertyPhoneNumber[];
+
+  // |cellular| is the parent Cellular device.
+  CellularCapabilityClassic(Cellular *cellular, ProxyFactory *proxy_factory);
+  virtual ~CellularCapabilityClassic();
+
+  virtual void StopModem(Error *error, const ResultCallback &callback);
+  virtual void Connect(const DBusPropertiesMap &properties, Error *error,
+                       const ResultCallback &callback);
+  virtual void Disconnect(Error *error, const ResultCallback &callback);
+
+  virtual void Activate(const std::string &carrier,
+                        Error *error, const ResultCallback &callback);
+
+  // Network registration.
+  virtual void RegisterOnNetwork(const std::string &network_id,
+                                 Error *error,
+                                 const ResultCallback &callback);
+
+  // PIN management. The default implementation fails by returning an error.
+  virtual void RequirePIN(const std::string &pin, bool require,
+                          Error *error, const ResultCallback &callback);
+  virtual void EnterPIN(const std::string &pin,
+                        Error *error, const ResultCallback &callback);
+  virtual void UnblockPIN(const std::string &unblock_code,
+                          const std::string &pin,
+                          Error *error, const ResultCallback &callback);
+  virtual void ChangePIN(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error, const ResultCallback &callback);
+
+  virtual void Scan(Error *error, const ResultCallback &callback);
+
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
+
+ protected:
+  // The following five methods are only ever called as
+  // callbacks (from the main loop), which is why they
+  // don't take an Error * argument.
+  virtual void EnableModem(const ResultCallback &callback);
+  virtual void DisableModem(const ResultCallback &callback);
+  virtual void GetModemStatus(const ResultCallback &callback);
+  virtual void GetModemInfo(const ResultCallback &callback);
+  virtual void GetProperties(const ResultCallback &callback) = 0;
+
+  void FinishEnable(const ResultCallback &callback);
+  void FinishDisable(const ResultCallback &callback);
+  virtual void InitProxies();
+  virtual void ReleaseProxies();
+
+  static void OnUnsupportedOperation(const char *operation, Error *error);
+
+  virtual void OnConnectReply(const ResultCallback &callback,
+                              const Error &error);
+
+  // Properties
+  bool scanning_supported_;
+  std::string meid_;
+  std::string imsi_;
+  std::string imei_;
+  std::string esn_;
+  std::string mdn_;
+  std::string min_;
+  std::string model_id_;
+  std::string manufacturer_;
+  std::string firmware_revision_;
+  std::string hardware_revision_;
+  std::string carrier_;
+
+ private:
+  friend class CellularTest;
+  friend class CellularCapabilityTest;
+  friend class CellularCapabilityGSMTest;
+  FRIEND_TEST(CellularCapabilityGSMTest, SetProxy);
+  FRIEND_TEST(CellularCapabilityGSMTest, SetStorageIdentifier);
+  FRIEND_TEST(CellularCapabilityGSMTest, UpdateStatus);
+  FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityTest, EnableModemFail);
+  FRIEND_TEST(CellularCapabilityTest, EnableModemSucceed);
+  FRIEND_TEST(CellularCapabilityTest, FinishEnable);
+  FRIEND_TEST(CellularCapabilityTest, GetModemInfo);
+  FRIEND_TEST(CellularCapabilityTest, GetModemStatus);
+  FRIEND_TEST(CellularCapabilityTest, TryApns);
+  FRIEND_TEST(CellularServiceTest, FriendlyName);
+  FRIEND_TEST(CellularTest, StartCDMARegister);
+  FRIEND_TEST(CellularTest, StartConnected);
+  FRIEND_TEST(CellularTest, StartGSMRegister);
+  FRIEND_TEST(CellularTest, StartLinked);
+  FRIEND_TEST(CellularTest, Connect);
+  FRIEND_TEST(CellularTest, ConnectFailure);
+  FRIEND_TEST(CellularTest, Disconnect);
+
+  void HelpRegisterDerivedBool(
+      const std::string &name,
+      bool(CellularCapability::*get)(Error *error),
+      void(CellularCapability::*set)(const bool &value, Error *error));
+
+  // Method reply and signal callbacks from Modem interface
+  virtual void OnModemStateChangedSignal(
+      uint32 old_state, uint32 new_state, uint32 reason);
+  virtual void OnGetModemInfoReply(const ResultCallback &callback,
+                                   const ModemHardwareInfo &info,
+                                   const Error &error);
+
+  // Method reply callbacks from Modem.Simple interface
+  virtual void OnGetModemStatusReply(const ResultCallback &callback,
+                                     const DBusPropertiesMap &props,
+                                     const Error &error);
+  virtual void OnDisconnectReply(const ResultCallback &callback,
+                                 const Error &error);
+
+  Cellular *cellular_;
+  base::WeakPtrFactory<CellularCapabilityClassic> weak_ptr_factory_;
+
+  scoped_ptr<ModemProxyInterface> proxy_;
+  scoped_ptr<ModemSimpleProxyInterface> simple_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityClassic);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CAPABILITY_CLASSIC_
diff --git a/cellular_capability_unittest.cc b/cellular_capability_classic_unittest.cc
similarity index 97%
rename from cellular_capability_unittest.cc
rename to cellular_capability_classic_unittest.cc
index 474c080..ad5ea4a 100644
--- a/cellular_capability_unittest.cc
+++ b/cellular_capability_classic_unittest.cc
@@ -79,7 +79,9 @@
 
   virtual void SetUp() {
     static_cast<Device *>(cellular_)->rtnl_handler_ = &rtnl_handler_;
-    capability_ = cellular_->capability_.get();
+
+    capability_ = dynamic_cast<CellularCapabilityClassic *>(
+        cellular_->capability_.get());
     device_adaptor_ =
         dynamic_cast<NiceMock<DeviceMockAdaptor> *>(cellular_->adaptor());
   }
@@ -199,7 +201,8 @@
 
   void SetCellularType(Cellular::Type type) {
     cellular_->InitCapability(type, &proxy_factory_);
-    capability_ = cellular_->capability_.get();
+    capability_ = dynamic_cast<CellularCapabilityClassic *>(
+        cellular_->capability_.get());
   }
 
   NiceMockControl control_;
@@ -215,7 +218,7 @@
   scoped_ptr<MockModemGSMCardProxy> gsm_card_proxy_;
   scoped_ptr<MockModemGSMNetworkProxy> gsm_network_proxy_;
   TestProxyFactory proxy_factory_;
-  CellularCapability *capability_;  // Owned by |cellular_|.
+  CellularCapabilityClassic *capability_;  // Owned by |cellular_|.
   NiceMock<DeviceMockAdaptor> *device_adaptor_;  // Owned by |cellular_|.
   mobile_provider_db *provider_db_;
 };
diff --git a/cellular_capability_gsm.cc b/cellular_capability_gsm.cc
index bfda762..461b3bf 100644
--- a/cellular_capability_gsm.cc
+++ b/cellular_capability_gsm.cc
@@ -44,7 +44,7 @@
 
 CellularCapabilityGSM::CellularCapabilityGSM(Cellular *cellular,
                                              ProxyFactory *proxy_factory)
-    : CellularCapability(cellular, proxy_factory),
+    : CellularCapabilityClassic(cellular, proxy_factory),
       weak_ptr_factory_(this),
       registration_state_(MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN),
       access_technology_(MM_MODEM_GSM_ACCESS_TECH_UNKNOWN),
@@ -90,7 +90,7 @@
 }
 
 void CellularCapabilityGSM::InitProxies() {
-  CellularCapability::InitProxies();
+  CellularCapabilityClassic::InitProxies();
   card_proxy_.reset(
       proxy_factory()->CreateModemGSMCardProxy(cellular()->dbus_path(),
                                                cellular()->dbus_owner()));
@@ -145,7 +145,7 @@
 
 void CellularCapabilityGSM::ReleaseProxies() {
   VLOG(2) << __func__;
-  CellularCapability::ReleaseProxies();
+  CellularCapabilityClassic::ReleaseProxies();
   card_proxy_.reset();
   network_proxy_.reset();
 }
@@ -201,7 +201,7 @@
   (*properties)[kConnectPropertyPhoneNumber].writer().append_string(
       kPhoneNumber);
 
-  if (!allow_roaming_)
+  if (!AllowRoaming())
     (*properties)[kConnectPropertyHomeOnly].writer().append_bool(true);
 
   if (!apn_try_list_.empty()) {
@@ -242,7 +242,13 @@
     cellular()->service()->SetLastGoodApn(apn_try_list_.front());
     apn_try_list_.clear();
   }
-  CellularCapability::OnConnectReply(callback, error);
+  CellularCapabilityClassic::OnConnectReply(callback, error);
+}
+
+bool CellularCapabilityGSM::AllowRoaming() {
+  bool requires_roaming =
+      home_provider_ ? home_provider_->requires_roaming : false;
+  return requires_roaming || allow_roaming_property();
 }
 
 // always called from an async context
@@ -569,14 +575,18 @@
                                         const GSMScanResults &results,
                                         const Error &error) {
   VLOG(2) << __func__;
-  if (error.IsFailure()) {
-    callback.Run(error);
-    return;
-  }
+
+  // Error handling is weak.  The current expectation is that on any
+  // error, found_networks_ should be cleared and a property change
+  // notification sent out.
+  //
+  // TODO(jglasgow): fix error handling
   found_networks_.clear();
-  for (GSMScanResults::const_iterator it = results.begin();
-       it != results.end(); ++it) {
-    found_networks_.push_back(ParseScanResult(*it));
+  if (!error.IsFailure()) {
+    for (GSMScanResults::const_iterator it = results.begin();
+         it != results.end(); ++it) {
+      found_networks_.push_back(ParseScanResult(*it));
+    }
   }
   cellular()->adaptor()->EmitStringmapsChanged(flimflam::kFoundNetworksProperty,
                                                found_networks_);
diff --git a/cellular_capability_gsm.h b/cellular_capability_gsm.h
index 413a270..ec9260a 100644
--- a/cellular_capability_gsm.h
+++ b/cellular_capability_gsm.h
@@ -6,6 +6,7 @@
 #define SHILL_CELLULAR_CAPABILITY_GSM_
 
 #include <deque>
+#include <string>
 
 #include <base/memory/scoped_ptr.h>
 #include <base/memory/weak_ptr.h>
@@ -14,6 +15,7 @@
 #include "shill/accessor_interface.h"
 #include "shill/cellular.h"
 #include "shill/cellular_capability.h"
+#include "shill/cellular_capability_classic.h"
 #include "shill/modem_gsm_card_proxy_interface.h"
 #include "shill/modem_gsm_network_proxy_interface.h"
 
@@ -21,7 +23,7 @@
 
 namespace shill {
 
-class CellularCapabilityGSM : public CellularCapability {
+class CellularCapabilityGSM : public CellularCapabilityClassic {
  public:
   CellularCapabilityGSM(Cellular *cellular, ProxyFactory *proxy_factory);
 
@@ -75,6 +77,8 @@
   virtual void OnConnectReply(const ResultCallback &callback,
                               const Error &error);
 
+  virtual bool AllowRoaming();
+
  private:
   friend class CellularTest;
   friend class CellularCapabilityGSMTest;
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
new file mode 100644
index 0000000..85956b3
--- /dev/null
+++ b/cellular_capability_universal.cc
@@ -0,0 +1,806 @@
+// Copyright (c) 2012 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 "shill/cellular_capability_universal.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/stl_util.h>
+#include <base/string_number_conversions.h>
+#include <base/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mobile_provider.h>
+#include <mm/ModemManager-names.h>
+
+#include <string>
+#include <vector>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular_service.h"
+#include "shill/error.h"
+#include "shill/property_accessor.h"
+#include "shill/proxy_factory.h"
+
+#ifdef MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
+#error "Do not include mm-modem.h"
+#endif
+
+// The following are constants that should be found in
+// mm/ModemManager-names.h  The are reproduced here as #define because
+// that is how they will appear eventually in ModemManager-names.h
+#define MM_MODEM_SIMPLE_CONNECT_PIN "pin"
+#define MM_MODEM_SIMPLE_CONNECT_OPERATOR_ID "operator-id"
+#define MM_MODEM_SIMPLE_CONNECT_BANDS "bands"
+#define MM_MODEM_SIMPLE_CONNECT_ALLWOED_MODES "allowed-modes"
+#define MM_MODEM_SIMPLE_CONNECT_PREFERRED_MODE "preferred-mode"
+#define MM_MODEM_SIMPLE_CONNECT_APN "apn"
+#define MM_MODEM_SIMPLE_CONNECT_IP_TYPE "ip-type"
+#define MM_MODEM_SIMPLE_CONNECT_USER "user"
+#define MM_MODEM_SIMPLE_CONNECT_PASSWORD "password"
+#define MM_MODEM_SIMPLE_CONNECT_NUMBER "number"
+#define MM_MODEM_SIMPLE_CONNECT_ALLOW_ROAMING "allow-roaming"
+#define MM_MODEM_SIMPLE_CONNECT_RM_PROTOCOL "rm-protocol"
+
+using base::Bind;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+// static
+unsigned int CellularCapabilityUniversal::friendly_service_name_id_ = 0;
+
+static const char kPhoneNumber[] = "*99#";
+
+static string AccessTechnologyToString(uint32 access_technologies) {
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_LTE)
+    return flimflam::kNetworkTechnologyLte;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOB))
+    return flimflam::kNetworkTechnologyEvdo;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT)
+    return flimflam::kNetworkTechnology1Xrtt;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)
+    return flimflam::kNetworkTechnologyHspaPlus;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_HSPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSDPA))
+    return flimflam::kNetworkTechnologyHspa;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+    return flimflam::kNetworkTechnologyUmts;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_EDGE)
+    return flimflam::kNetworkTechnologyEdge;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
+    return flimflam::kNetworkTechnologyGprs;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT |
+                              MM_MODEM_ACCESS_TECHNOLOGY_GSM))
+      return flimflam::kNetworkTechnologyGsm;
+  return "";
+}
+
+CellularCapabilityUniversal::CellularCapabilityUniversal(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory)
+    : CellularCapability(cellular, proxy_factory),
+      weak_ptr_factory_(this),
+      registration_state_(MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN),
+      cdma_registration_state_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
+      access_technologies_(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN),
+      home_provider_(NULL),
+      scanning_supported_(true),
+      scanning_(false),
+      scan_interval_(0) {
+  VLOG(2) << "Cellular capability constructed: Universal";
+  PropertyStore *store = cellular->mutable_store();
+
+  store->RegisterConstString(flimflam::kCarrierProperty, &carrier_);
+  store->RegisterConstBool(flimflam::kSupportNetworkScanProperty,
+                           &scanning_supported_);
+  store->RegisterConstString(flimflam::kEsnProperty, &esn_);
+  store->RegisterConstString(flimflam::kFirmwareRevisionProperty,
+                             &firmware_revision_);
+  store->RegisterConstString(flimflam::kHardwareRevisionProperty,
+                             &hardware_revision_);
+  store->RegisterConstString(flimflam::kImeiProperty, &imei_);
+  store->RegisterConstString(flimflam::kImsiProperty, &imsi_);
+  store->RegisterConstString(flimflam::kManufacturerProperty, &manufacturer_);
+  store->RegisterConstString(flimflam::kMdnProperty, &mdn_);
+  store->RegisterConstString(flimflam::kMeidProperty, &meid_);
+  store->RegisterConstString(flimflam::kMinProperty, &min_);
+  store->RegisterConstString(flimflam::kModelIDProperty, &model_id_);
+  store->RegisterConstString(flimflam::kSelectedNetworkProperty,
+                             &selected_network_);
+  store->RegisterConstStringmaps(flimflam::kFoundNetworksProperty,
+                                 &found_networks_);
+  store->RegisterConstBool(flimflam::kScanningProperty, &scanning_);
+  store->RegisterUint16(flimflam::kScanIntervalProperty, &scan_interval_);
+  HelpRegisterDerivedKeyValueStore(
+      flimflam::kSIMLockStatusProperty,
+      &CellularCapabilityUniversal::SimLockStatusToProperty,
+      NULL);
+  store->RegisterConstStringmaps(flimflam::kCellularApnListProperty,
+                                 &apn_list_);
+}
+
+KeyValueStore CellularCapabilityUniversal::SimLockStatusToProperty(
+    Error */*error*/) {
+  KeyValueStore status;
+  status.SetBool(flimflam::kSIMLockEnabledProperty, sim_lock_status_.enabled);
+  status.SetString(flimflam::kSIMLockTypeProperty, sim_lock_status_.lock_type);
+  status.SetUint(flimflam::kSIMLockRetriesLeftProperty,
+                 sim_lock_status_.retries_left);
+  return status;
+}
+
+void CellularCapabilityUniversal::HelpRegisterDerivedKeyValueStore(
+    const string &name,
+    KeyValueStore(CellularCapabilityUniversal::*get)(Error *error),
+    void(CellularCapabilityUniversal::*set)(
+        const KeyValueStore &value, Error *error)) {
+  cellular()->mutable_store()->RegisterDerivedKeyValueStore(
+      name,
+      KeyValueStoreAccessor(
+          new CustomAccessor<CellularCapabilityUniversal, KeyValueStore>(
+              this, get, set)));
+}
+
+void CellularCapabilityUniversal::InitProxies() {
+  modem_3gpp_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModem3gppProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  modem_cdma_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModemCdmaProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  modem_proxy_.reset(
+      proxy_factory()->CreateMM1ModemProxy(cellular()->dbus_path(),
+                                           cellular()->dbus_owner()));
+  modem_simple_proxy_.reset(
+      proxy_factory()->CreateMM1ModemSimpleProxy(cellular()->dbus_path(),
+                                                 cellular()->dbus_owner()));
+  // Do not create a SIM proxy until the device is enabled because we
+  // do not yet know the object path of the sim object.
+
+  // TODO(jglasgow): register callbacks
+}
+
+void CellularCapabilityUniversal::StartModem(Error *error,
+                                       const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::StopModem(Error *error,
+                                      const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Connect(const DBusPropertiesMap &properties,
+                                          Error *error,
+                                          const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Disconnect(Error *error,
+                                             const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Activate(const std::string &carrier,
+                                           Error *error,
+                                           const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::ReleaseProxies() {
+  VLOG(2) << __func__;
+  modem_3gpp_proxy_.reset();
+  modem_cdma_proxy_.reset();
+  modem_proxy_.reset();
+  modem_simple_proxy_.reset();
+  sim_proxy_.reset();
+}
+
+void CellularCapabilityUniversal::OnServiceCreated() {
+  // If IMSI is available, base the service's storage identifier on it.
+  if (!imsi_.empty()) {
+    cellular()->service()->SetStorageIdentifier(
+        string(flimflam::kTypeCellular) + "_" +
+        cellular()->address() + "_" + imsi_);
+  }
+  cellular()->service()->SetActivationState(
+      flimflam::kActivationStateActivated);
+  UpdateServingOperator();
+}
+
+void CellularCapabilityUniversal::UpdateStatus(
+    const DBusPropertiesMap &properties) {
+  if (ContainsKey(properties, kPropertyIMSI)) {
+    SetHomeProvider();
+  }
+}
+
+// Create the list of APNs to try, in the following order:
+// - last APN that resulted in a successful connection attempt on the
+//   current network (if any)
+// - the APN, if any, that was set by the user
+// - the list of APNs found in the mobile broadband provider DB for the
+//   home provider associated with the current SIM
+// - as a last resort, attempt to connect with no APN
+void CellularCapabilityUniversal::SetupApnTryList() {
+  apn_try_list_.clear();
+
+  DCHECK(cellular()->service().get());
+  const Stringmap *apn_info = cellular()->service()->GetLastGoodApn();
+  if (apn_info)
+    apn_try_list_.push_back(*apn_info);
+
+  apn_info = cellular()->service()->GetUserSpecifiedApn();
+  if (apn_info)
+    apn_try_list_.push_back(*apn_info);
+
+  apn_try_list_.insert(apn_try_list_.end(), apn_list_.begin(), apn_list_.end());
+}
+
+void CellularCapabilityUniversal::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  SetupApnTryList();
+  FillConnectPropertyMap(properties);
+}
+
+void CellularCapabilityUniversal::FillConnectPropertyMap(
+    DBusPropertiesMap *properties) {
+
+  // TODO(jglasgow): Is this really needed anymore?
+  (*properties)[MM_MODEM_SIMPLE_CONNECT_NUMBER].writer().append_string(
+      kPhoneNumber);
+
+  (*properties)[MM_MODEM_SIMPLE_CONNECT_ALLOW_ROAMING].writer().append_bool(
+      AllowRoaming());
+
+  if (!apn_try_list_.empty()) {
+    // Leave the APN at the front of the list, so that it can be recorded
+    // if the connect attempt succeeds.
+    Stringmap apn_info = apn_try_list_.front();
+    VLOG(2) << __func__ << ": Using APN " << apn_info[flimflam::kApnProperty];
+    (*properties)[MM_MODEM_SIMPLE_CONNECT_APN].writer().append_string(
+        apn_info[flimflam::kApnProperty].c_str());
+    if (ContainsKey(apn_info, flimflam::kApnUsernameProperty))
+      (*properties)[MM_MODEM_SIMPLE_CONNECT_USER].writer().append_string(
+          apn_info[flimflam::kApnUsernameProperty].c_str());
+    if (ContainsKey(apn_info, flimflam::kApnPasswordProperty))
+      (*properties)[MM_MODEM_SIMPLE_CONNECT_PASSWORD].writer().append_string(
+          apn_info[flimflam::kApnPasswordProperty].c_str());
+  }
+}
+
+void CellularCapabilityUniversal::OnConnectReply(const ResultCallback &callback,
+                                           const Error &error) {
+  if (error.IsFailure()) {
+    cellular()->service()->ClearLastGoodApn();
+    // The APN that was just tried (and failed) is still at the
+    // front of the list, about to be removed. If the list is empty
+    // after that, try one last time without an APN. This may succeed
+    // with some modems in some cases.
+    if (error.type() == Error::kInvalidApn && !apn_try_list_.empty()) {
+      apn_try_list_.pop_front();
+      VLOG(2) << "Connect failed with invalid APN, " << apn_try_list_.size()
+              << " remaining APNs to try";
+      DBusPropertiesMap props;
+      FillConnectPropertyMap(&props);
+      Error error;
+      Connect(props, &error, callback);
+      return;
+    }
+    cellular()->OnConnectFailed(error);
+  } else {
+    if (!apn_try_list_.empty()) {
+      cellular()->service()->SetLastGoodApn(apn_try_list_.front());
+      apn_try_list_.clear();
+    }
+    cellular()->OnConnected();
+  }
+
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+bool CellularCapabilityUniversal::AllowRoaming() {
+  bool requires_roaming =
+      home_provider_ ? home_provider_->requires_roaming : false;
+  return requires_roaming || allow_roaming_property();
+}
+
+void CellularCapabilityUniversal::GetRegistrationState() {
+  VLOG(2) << __func__;
+  string operator_code;
+  string operator_name;
+
+  const MMModem3gppRegistrationState state =
+      static_cast<MMModem3gppRegistrationState>(
+          modem_3gpp_proxy_->RegistrationState());
+  if (state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+      state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+    operator_code = modem_3gpp_proxy_->OperatorCode();
+    operator_name = modem_3gpp_proxy_->OperatorName();
+  }
+  On3GPPRegistrationChanged(state, operator_code, operator_name);
+}
+
+void CellularCapabilityUniversal::GetProperties(
+    const ResultCallback &callback) {
+  VLOG(2) << __func__;
+
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  uint32 technologies = modem_proxy_->AccessTechnologies();
+  // TODO(jglasgow): figure out the most likely one that we are using....
+  SetAccessTechnologies(technologies);
+  VLOG(2) << "AccessTechnologies: " << technologies;
+
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  uint32 locks = modem_3gpp_proxy_->EnabledFacilityLocks();
+  sim_lock_status_.enabled = locks & MM_MODEM_3GPP_FACILITY_SIM;
+  VLOG(2) << "GSM EnabledFacilityLocks: " << locks;
+
+  // TODO(jglasgow): Switch to asynchronous calls (crosbug.com/17583).
+  const DBus::Struct<unsigned int, bool> quality =
+      modem_proxy_->SignalQuality();
+  OnSignalQualityChanged(quality._1);
+
+  // TODO(jglasgow): Switch to asynchronous calls (crosbug.com/17583).
+  if (imei_.empty()) {
+    imei_ = modem_3gpp_proxy_->Imei();
+  }
+  if (imsi_.empty()) {
+    imsi_ = sim_proxy_->Imsi();
+  }
+  if (spn_.empty()) {
+    spn_ = sim_proxy_->OperatorName();
+    // TODO(jglasgow): May eventually want to get SPDI, etc
+  }
+  if (mdn_.empty()) {
+    // TODO(jglasgow): use OwnNumbers()
+  }
+  GetRegistrationState();
+
+  callback.Run(Error());
+}
+
+string CellularCapabilityUniversal::CreateFriendlyServiceName() {
+  VLOG(2) << __func__;
+  if (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME &&
+      !cellular()->home_provider().GetName().empty()) {
+    return cellular()->home_provider().GetName();
+  }
+  if (!serving_operator_.GetName().empty()) {
+    return serving_operator_.GetName();
+  }
+  if (!carrier_.empty()) {
+    return carrier_;
+  }
+  if (!serving_operator_.GetCode().empty()) {
+    return "cellular_" + serving_operator_.GetCode();
+  }
+  return base::StringPrintf("GSMNetwork%u", friendly_service_name_id_++);
+}
+
+void CellularCapabilityUniversal::SetHomeProvider() {
+  VLOG(2) << __func__ << "(IMSI: " << imsi_
+          << " SPN: " << spn_ << ")";
+  // TODO(petkov): The test for NULL provider_db should be done by
+  // mobile_provider_lookup_best_match.
+  if (imsi_.empty() || !cellular()->provider_db()) {
+    return;
+  }
+  mobile_provider *provider =
+      mobile_provider_lookup_best_match(
+          cellular()->provider_db(), spn_.c_str(), imsi_.c_str());
+  if (!provider) {
+    VLOG(2) << "GSM provider not found.";
+    return;
+  }
+  home_provider_ = provider;
+  Cellular::Operator oper;
+  if (provider->networks) {
+    oper.SetCode(provider->networks[0]);
+  }
+  if (provider->country) {
+    oper.SetCountry(provider->country);
+  }
+  if (spn_.empty()) {
+    const char *name = mobile_provider_get_name(provider);
+    if (name) {
+      oper.SetName(name);
+    }
+  } else {
+    oper.SetName(spn_);
+  }
+  cellular()->set_home_provider(oper);
+  InitAPNList();
+}
+
+void CellularCapabilityUniversal::UpdateOperatorInfo() {
+  VLOG(2) << __func__;
+  const string &network_id = serving_operator_.GetCode();
+  if (!network_id.empty()) {
+    VLOG(2) << "Looking up network id: " << network_id;
+    mobile_provider *provider =
+        mobile_provider_lookup_by_network(cellular()->provider_db(),
+                                          network_id.c_str());
+    if (provider) {
+      const char *provider_name = mobile_provider_get_name(provider);
+      if (provider_name && *provider_name) {
+        serving_operator_.SetName(provider_name);
+        if (provider->country) {
+          serving_operator_.SetCountry(provider->country);
+        }
+        VLOG(2) << "Operator name: " << serving_operator_.GetName()
+                << ", country: " << serving_operator_.GetCountry();
+      }
+    } else {
+      VLOG(2) << "GSM provider not found.";
+    }
+  }
+  UpdateServingOperator();
+}
+
+void CellularCapabilityUniversal::UpdateServingOperator() {
+  VLOG(2) << __func__;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetServingOperator(serving_operator_);
+  }
+}
+
+void CellularCapabilityUniversal::InitAPNList() {
+  VLOG(2) << __func__;
+  if (!home_provider_) {
+    return;
+  }
+  apn_list_.clear();
+  for (int i = 0; i < home_provider_->num_apns; ++i) {
+    Stringmap props;
+    mobile_apn *apn = home_provider_->apns[i];
+    if (apn->value) {
+      props[flimflam::kApnProperty] = apn->value;
+    }
+    if (apn->username) {
+      props[flimflam::kApnUsernameProperty] = apn->username;
+    }
+    if (apn->password) {
+      props[flimflam::kApnPasswordProperty] = apn->password;
+    }
+    // Find the first localized and non-localized name, if any.
+    const localized_name *lname = NULL;
+    const localized_name *name = NULL;
+    for (int j = 0; j < apn->num_names; ++j) {
+      if (apn->names[j]->lang) {
+        if (!lname) {
+          lname = apn->names[j];
+        }
+      } else if (!name) {
+        name = apn->names[j];
+      }
+    }
+    if (name) {
+      props[flimflam::kApnNameProperty] = name->name;
+    }
+    if (lname) {
+      props[flimflam::kApnLocalizedNameProperty] = lname->name;
+      props[flimflam::kApnLanguageProperty] = lname->lang;
+    }
+    apn_list_.push_back(props);
+  }
+  cellular()->adaptor()->EmitStringmapsChanged(
+      flimflam::kCellularApnListProperty, apn_list_);
+}
+
+// always called from an async context
+void CellularCapabilityUniversal::Register(const ResultCallback &callback) {
+  VLOG(2) << __func__ << " \"" << selected_network_ << "\"";
+  CHECK(!callback.is_null());
+  Error error;
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Register(selected_network_, &error, cb, kTimeoutRegister);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::RegisterOnNetwork(
+    const string &network_id,
+    Error *error,
+    const ResultCallback &callback) {
+  VLOG(2) << __func__ << "(" << network_id << ")";
+  CHECK(error);
+  desired_network_ = network_id;
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Register(network_id, error, cb, kTimeoutRegister);
+}
+
+void CellularCapabilityUniversal::OnRegisterReply(
+    const ResultCallback &callback,
+    const Error &error) {
+  VLOG(2) << __func__ << "(" << error << ")";
+
+  if (error.IsSuccess()) {
+    selected_network_ = desired_network_;
+    desired_network_.clear();
+    callback.Run(error);
+    return;
+  }
+  // If registration on the desired network failed,
+  // try to register on the home network.
+  if (!desired_network_.empty()) {
+    desired_network_.clear();
+    selected_network_.clear();
+    LOG(INFO) << "Couldn't register on selected network, trying home network";
+    Register(callback);
+    return;
+  }
+  callback.Run(error);
+}
+
+bool CellularCapabilityUniversal::IsRegistered() {
+  return (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+          registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING);
+}
+
+void CellularCapabilityUniversal::RequirePIN(
+    const std::string &pin, bool require,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->EnablePin(pin, require, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::EnterPIN(const string &pin,
+                                           Error *error,
+                                           const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->SendPin(pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::UnblockPIN(const string &unblock_code,
+                                             const string &pin,
+                                             Error *error,
+                                             const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->SendPuk(unblock_code, pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::ChangePIN(
+    const string &old_pin, const string &new_pin,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->ChangePin(old_pin, new_pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::Scan(Error *error,
+                                       const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  // TODO(petkov): Defer scan requests if a scan is in progress already.
+  CHECK(error);
+  DBusPropertyMapsCallback cb = Bind(&CellularCapabilityUniversal::OnScanReply,
+                                     weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Scan(error, cb, kTimeoutScan);
+}
+
+void CellularCapabilityUniversal::OnScanReply(const ResultCallback &callback,
+                                              const ScanResults &results,
+                                              const Error &error) {
+  VLOG(2) << __func__;
+
+  // Error handling is weak.  The current expectation is that on any
+  // error, found_networks_ should be cleared and a property change
+  // notification sent out.
+  //
+  // TODO(jglasgow): fix error handling
+  found_networks_.clear();
+  if (!error.IsFailure()) {
+    for (ScanResults::const_iterator it = results.begin();
+         it != results.end(); ++it) {
+      found_networks_.push_back(ParseScanResult(*it));
+    }
+  }
+  cellular()->adaptor()->EmitStringmapsChanged(flimflam::kFoundNetworksProperty,
+                                               found_networks_);
+  callback.Run(error);
+}
+
+Stringmap CellularCapabilityUniversal::ParseScanResult(
+    const ScanResult &result) {
+
+  static const char kStatusProperty[] = "status";
+  static const char kOperatorLongProperty[] = "operator-long";
+  static const char kOperatorShortProperty[] = "operator-short";
+  static const char kOperatorCodeProperty[] = "operator-code";
+  static const char kOperatorAccessTechnologyProperty[] = "access-technology";
+
+  /* ScanResults contain the following keys:
+
+     "status"
+     A MMModem3gppNetworkAvailability value representing network
+     availability status, given as an unsigned integer (signature "u").
+     This key will always be present.
+
+     "operator-long"
+     Long-format name of operator, given as a string value (signature
+     "s"). If the name is unknown, this field should not be present.
+
+     "operator-short"
+     Short-format name of operator, given as a string value
+     (signature "s"). If the name is unknown, this field should not
+     be present.
+
+     "operator-code"
+     Mobile code of the operator, given as a string value (signature
+     "s"). Returned in the format "MCCMNC", where MCC is the
+     three-digit ITU E.212 Mobile Country Code and MNC is the two- or
+     three-digit GSM Mobile Network Code. e.g. "31026" or "310260".
+
+     "access-technology"
+     A MMModemAccessTechnology value representing the generic access
+     technology used by this mobile network, given as an unsigned
+     integer (signature "u").
+  */
+  Stringmap parsed;
+
+  uint32 status;
+  if (DBusProperties::GetUint32(result, kStatusProperty, &status)) {
+    // numerical values are taken from 3GPP TS 27.007 Section 7.3.
+    static const char * const kStatusString[] = {
+      "unknown",    // MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN
+      "available",  // MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE
+      "current",    // MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT
+      "forbidden",  // MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN
+    };
+    parsed[flimflam::kStatusProperty] = kStatusString[status];
+  }
+
+  uint32 tech;  // MMModemAccessTechnology
+  if (DBusProperties::GetUint32(result, kOperatorAccessTechnologyProperty,
+                                &tech)) {
+    parsed[flimflam::kTechnologyProperty] = AccessTechnologyToString(tech);
+  }
+
+  string operator_long, operator_short, operator_code;
+  if (DBusProperties::GetString(result, kOperatorLongProperty, &operator_long))
+    parsed[flimflam::kLongNameProperty] = operator_long;
+  if (DBusProperties::GetString(result, kOperatorShortProperty,
+                                &operator_short))
+    parsed[flimflam::kShortNameProperty] = operator_short;
+  if (DBusProperties::GetString(result, kOperatorCodeProperty, &operator_code))
+    parsed[flimflam::kNetworkIdProperty] = operator_code;
+
+  // If the long name is not available but the network ID is, look up the long
+  // name in the mobile provider database.
+  if ((!ContainsKey(parsed, flimflam::kLongNameProperty) ||
+       parsed[flimflam::kLongNameProperty].empty()) &&
+      ContainsKey(parsed, flimflam::kNetworkIdProperty)) {
+    mobile_provider *provider =
+        mobile_provider_lookup_by_network(
+            cellular()->provider_db(),
+            parsed[flimflam::kNetworkIdProperty].c_str());
+    if (provider) {
+      const char *long_name = mobile_provider_get_name(provider);
+      if (long_name && *long_name) {
+        parsed[flimflam::kLongNameProperty] = long_name;
+      }
+    }
+  }
+  return parsed;
+}
+
+void CellularCapabilityUniversal::SetAccessTechnologies(
+    uint32 access_technologies) {
+  access_technologies_ = access_technologies;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
+  }
+}
+
+string CellularCapabilityUniversal::GetNetworkTechnologyString() const {
+  // Order is imnportant.  Return the highest speed technology
+  // TODO(jglasgow): change shill interfaces to a capability model
+
+  return AccessTechnologyToString(access_technologies_);
+}
+
+string CellularCapabilityUniversal::GetRoamingStateString() const {
+  switch (registration_state_) {
+    case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
+      return flimflam::kRoamingStateHome;
+    case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+      return flimflam::kRoamingStateRoaming;
+    default:
+      break;
+  }
+  return flimflam::kRoamingStateUnknown;
+}
+
+void CellularCapabilityUniversal::GetSignalQuality() {
+  // TODO(jglasgow): implement
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::OnModemManagerPropertiesChanged(
+    const DBusPropertiesMap &properties) {
+
+  // TODO(jglasgow): When CreateDeviceFromModemProperties is fixed DCHECK
+  LOG(ERROR) << "Unexpected call to OnModemPropertiesChanged.";
+
+  uint32 access_technologies = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES,
+                                &access_technologies)) {
+    SetAccessTechnologies(access_technologies);
+  }
+  // Unlockrequired and SimLock
+  bool emit = false;
+
+  uint32_t lock_required;  // This is really of type MMModemLock
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_UNLOCKREQUIRED,
+                                &lock_required)) {
+    // TODO(jglasgow): set sim_lock_status_.lock_type
+    emit = true;
+  }
+  // TODO(jglasgow): Update PIN retries which are a{uu} and require parsing
+  // Get the property MM_MODEM_PROPERTY_UNLOCKRETRIES
+  // Set sim_lock_status_.retries_left
+
+  if (emit) {
+    cellular()->adaptor()->EmitKeyValueStoreChanged(
+        flimflam::kSIMLockStatusProperty, SimLockStatusToProperty(NULL));
+  }
+}
+
+void CellularCapabilityUniversal::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<std::string> &invalidated_properties) {
+  // TODO(jglasgow): implement properties changed handling
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::OnModem3GPPPropertiesChanged(
+    const DBusPropertiesMap &properties) {
+  bool emit = false;
+  uint32 locks = 0;
+  if (DBusProperties::GetUint32(
+          properties, MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS,
+          &locks)) {
+    sim_lock_status_.enabled = locks & MM_MODEM_3GPP_FACILITY_SIM;
+    emit = true;
+  }
+  // TODO(jglasgow): coordinate with changes to Modem properties
+  if (emit) {
+    cellular()->adaptor()->EmitKeyValueStoreChanged(
+        flimflam::kSIMLockStatusProperty, SimLockStatusToProperty(NULL));
+  }
+}
+
+void CellularCapabilityUniversal::OnNetworkModeSignal(uint32 /*mode*/) {
+  // TODO(petkov): Implement this.
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::On3GPPRegistrationChanged(
+    MMModem3gppRegistrationState state,
+    const string &operator_code,
+    const string &operator_name) {
+  VLOG(2) << __func__ << ": regstate=" << state
+          << ", opercode=" << operator_code
+          << ", opername=" << operator_name;
+  registration_state_ = state;
+  serving_operator_.SetCode(operator_code);
+  serving_operator_.SetName(operator_name);
+  UpdateOperatorInfo();
+  cellular()->HandleNewRegistrationState();
+}
+
+void CellularCapabilityUniversal::OnSignalQualityChanged(uint32 quality) {
+  cellular()->HandleNewSignalQuality(quality);
+}
+
+}  // namespace shill
diff --git a/cellular_capability_universal.h b/cellular_capability_universal.h
new file mode 100644
index 0000000..5592f05
--- /dev/null
+++ b/cellular_capability_universal.h
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 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.
+
+#ifndef SHILL_CELLULAR_CAPABILITY_UNIVERSAL_
+#define SHILL_CELLULAR_CAPABILITY_UNIVERSAL_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <mm/ModemManager-enums.h>
+
+#include "shill/accessor_interface.h"
+#include "shill/cellular.h"
+#include "shill/cellular_capability.h"
+#include "shill/mm1_modem_modem3gpp_proxy_interface.h"
+#include "shill/mm1_modem_modemcdma_proxy_interface.h"
+#include "shill/mm1_modem_proxy_interface.h"
+#include "shill/mm1_modem_simple_proxy_interface.h"
+#include "shill/mm1_sim_proxy_interface.h"
+
+
+struct mobile_provider;
+
+namespace shill {
+
+// CellularCapabilityUniversal handles modems using the
+// org.chromium.ModemManager1 DBUS interface.  This class is used for
+// all types of modems, i.e. CDMA, GSM, and LTE modems.
+class CellularCapabilityUniversal : public CellularCapability {
+ public:
+  typedef std::vector<DBusPropertiesMap> ScanResults;
+  typedef DBusPropertiesMap ScanResult;
+
+  CellularCapabilityUniversal(Cellular *cellular, ProxyFactory *proxy_factory);
+
+  // Inherited from CellularCapability.
+  virtual void StartModem(Error *error, const ResultCallback &callback);
+  virtual void StopModem(Error *error, const ResultCallback &callback);
+  virtual void Connect(const DBusPropertiesMap &properties, Error *error,
+                       const ResultCallback &callback);
+  virtual void Disconnect(Error *error, const ResultCallback &callback);
+  virtual void Activate(const std::string &carrier,
+                        Error *error, const ResultCallback &callback);
+
+  virtual void OnServiceCreated();
+  virtual void UpdateStatus(const DBusPropertiesMap &properties);
+  virtual void SetupConnectProperties(DBusPropertiesMap *properties);
+  virtual void GetRegistrationState();
+  virtual void GetProperties(const ResultCallback &callback);
+  virtual void Register(const ResultCallback &callback);
+
+  virtual void RegisterOnNetwork(const std::string &network_id,
+                                 Error *error,
+                                 const ResultCallback &callback);
+  virtual bool IsRegistered();
+  virtual std::string CreateFriendlyServiceName();
+  virtual void RequirePIN(const std::string &pin, bool require,
+                          Error *error, const ResultCallback &callback);
+  virtual void EnterPIN(const std::string &pin,
+                        Error *error, const ResultCallback &callback);
+  virtual void UnblockPIN(const std::string &unblock_code,
+                          const std::string &pin,
+                          Error *error, const ResultCallback &callback);
+  virtual void ChangePIN(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error, const ResultCallback &callback);
+
+  virtual void Scan(Error *error, const ResultCallback &callback);
+  virtual std::string GetNetworkTechnologyString() const;
+  virtual std::string GetRoamingStateString() const;
+  virtual std::string GetTypeString() const { return "Universal"; }
+
+  virtual void GetSignalQuality();
+  virtual void OnModemManagerPropertiesChanged(
+      const DBusPropertiesMap &properties);
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
+  virtual void OnModem3GPPPropertiesChanged(
+      const DBusPropertiesMap &properties);
+
+ protected:
+  virtual void InitProxies();
+  virtual void ReleaseProxies();
+  // Override OnConnectReply in order to handle the possibility of
+  // retrying the Connect operation.
+  virtual void OnConnectReply(const ResultCallback &callback,
+                              const Error &error);
+
+  virtual bool AllowRoaming();
+
+ private:
+  friend class CellularTest;
+  friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityTest;
+  FRIEND_TEST(CellularCapabilityUniversalTest, CreateDeviceFromProperties);
+  FRIEND_TEST(CellularCapabilityUniversalTest, CreateFriendlyServiceName);
+  FRIEND_TEST(CellularCapabilityUniversalTest, GetIMEI);
+  FRIEND_TEST(CellularCapabilityUniversalTest, GetIMSI);
+  FRIEND_TEST(CellularCapabilityUniversalTest, GetMSISDN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, GetSPN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, RequirePIN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, EnterPIN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, UnblockPIN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, ChangePIN);
+  FRIEND_TEST(CellularCapabilityUniversalTest, InitAPNList);
+  FRIEND_TEST(CellularCapabilityUniversalTest, ParseScanResult);
+  FRIEND_TEST(CellularCapabilityUniversalTest, ParseScanResultProviderLookup);
+  FRIEND_TEST(CellularCapabilityUniversalTest, RegisterOnNetwork);
+  FRIEND_TEST(CellularCapabilityUniversalTest, Scan);
+  FRIEND_TEST(CellularCapabilityUniversalTest, SetAccessTechnologies);
+  FRIEND_TEST(CellularCapabilityUniversalTest, SetHomeProvider);
+  FRIEND_TEST(CellularCapabilityUniversalTest, UpdateOperatorInfo);
+  FRIEND_TEST(CellularCapabilityUniversalTest, GetRegistrationState);
+  FRIEND_TEST(CellularCapabilityUniversalTest, OnModemManagerPropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalTest, SetupApnTryList);
+  FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityTest, TryApns);
+  FRIEND_TEST(CellularTest, StartUniversalRegister);
+  FRIEND_TEST(ModemTest, CreateDeviceFromProperties);
+
+  // TOOD(jglasgow): document what this does!!!!!
+  void SetAccessTechnologies(uint32 access_technologies);
+
+  // Sets the upper level information about the home cellular provider from the
+  // modem's IMSI and SPN.
+  void SetHomeProvider();
+
+  // Updates the Universal operator name and country based on a newly
+  // obtained network id.
+  void UpdateOperatorInfo();
+
+  // Updates the serving operator on the active service.
+  void UpdateServingOperator();
+
+  // Initializes the |apn_list_| property based on the current |home_provider_|.
+  void InitAPNList();
+
+  Stringmap ParseScanResult(const ScanResult &result);
+
+  KeyValueStore SimLockStatusToProperty(Error *error);
+
+  void SetupApnTryList();
+  void FillConnectPropertyMap(DBusPropertiesMap *properties);
+
+  void HelpRegisterDerivedKeyValueStore(
+      const std::string &name,
+      KeyValueStore(CellularCapabilityUniversal::*get)(Error *error),
+      void(CellularCapabilityUniversal::*set)(
+          const KeyValueStore &value, Error *error));
+
+  // Signal callbacks
+  virtual void OnNetworkModeSignal(uint32 mode);
+
+  // Property Change notification handlers
+  // TODO(jglasgow): install generic property change notification handler
+  virtual void On3GPPRegistrationChanged(MMModem3gppRegistrationState state,
+                                        const std::string &operator_code,
+                                        const std::string &operator_name);
+  virtual void OnSignalQualityChanged(uint32 quality);
+
+  // Method callbacks
+  virtual void OnRegisterReply(const ResultCallback &callback,
+                               const Error &error);
+  virtual void OnScanReply(const ResultCallback &callback,
+                           const ScanResults &results,
+                           const Error &error);
+
+  scoped_ptr<mm1::ModemModem3gppProxyInterface> modem_3gpp_proxy_;
+  scoped_ptr<mm1::ModemModemCdmaProxyInterface> modem_cdma_proxy_;
+  scoped_ptr<mm1::ModemProxyInterface> modem_proxy_;
+  scoped_ptr<mm1::ModemSimpleProxyInterface> modem_simple_proxy_;
+  scoped_ptr<mm1::SimProxyInterface> sim_proxy_;
+
+  base::WeakPtrFactory<CellularCapabilityUniversal> weak_ptr_factory_;
+
+  MMModem3gppRegistrationState registration_state_;
+  MMModemCdmaRegistrationState cdma_registration_state_;
+  uint32 access_technologies_;  // Bits based on MMModemAccessTechnology
+  Cellular::Operator serving_operator_;
+  std::string spn_;
+  mobile_provider *home_provider_;
+  std::string desired_network_;
+
+  // Properties.
+  std::string carrier_;
+  std::string esn_;
+  std::string firmware_revision_;
+  std::string hardware_revision_;
+  std::string imei_;
+  std::string imsi_;
+  std::string manufacturer_;
+  std::string mdn_;
+  std::string meid_;
+  std::string min_;
+  std::string model_id_;
+  std::string selected_network_;
+  Stringmaps found_networks_;
+  std::deque<Stringmap> apn_try_list_;
+  bool scanning_supported_;
+  bool scanning_;
+  uint16 scan_interval_;
+  SimLockStatus sim_lock_status_;
+  Stringmaps apn_list_;
+
+  static unsigned int friendly_service_name_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityUniversal);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CAPABILITY_UNIVERSAL_
diff --git a/cellular_error.cc b/cellular_error.cc
index ca404e3..aa5f45a 100644
--- a/cellular_error.cc
+++ b/cellular_error.cc
@@ -4,6 +4,8 @@
 
 #include "shill/cellular_error.h"
 
+#include <string>
+
 #include <base/logging.h>
 #include <mm/mm-modem.h>
 
diff --git a/cellular_service_unittest.cc b/cellular_service_unittest.cc
index d1ed64f..95fd44d 100644
--- a/cellular_service_unittest.cc
+++ b/cellular_service_unittest.cc
@@ -8,6 +8,7 @@
 #include <gtest/gtest.h>
 
 #include "shill/cellular_capability.h"
+#include "shill/cellular_capability_gsm.h"
 #include "shill/nice_mock_control.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_metrics.h"
@@ -47,6 +48,10 @@
         dynamic_cast<NiceMock<ServiceMockAdaptor> *>(service_->adaptor());
   }
 
+  CellularCapabilityGSM *GetCapabilityGSM() {
+    return dynamic_cast<CellularCapabilityGSM *>(device_->capability_.get());
+  }
+
  protected:
   static const char kAddress[];
 
@@ -79,7 +84,7 @@
 
 TEST_F(CellularServiceTest, FriendlyName) {
   static const char kCarrier[] = "Cellular Carrier";
-  device_->capability_->carrier_ = kCarrier;
+  GetCapabilityGSM()->carrier_ = kCarrier;
   service_ = new CellularService(&control_, NULL, &metrics_, NULL, device_);
   EXPECT_EQ(kCarrier, service_->friendly_name());
 }
diff --git a/cellular_unittest.cc b/cellular_unittest.cc
index 0d85ae3..3a5fbcd 100644
--- a/cellular_unittest.cc
+++ b/cellular_unittest.cc
@@ -14,6 +14,7 @@
 #include <mobile_provider.h>
 
 #include "shill/cellular_capability_cdma.h"
+#include "shill/cellular_capability_classic.h"
 #include "shill/cellular_capability_gsm.h"
 #include "shill/cellular_service.h"
 #include "shill/error.h"
@@ -325,6 +326,11 @@
     device_->InitCapability(type, &proxy_factory_);
   }
 
+  CellularCapabilityClassic *GetCapabilityClassic() {
+    return dynamic_cast<CellularCapabilityClassic *>(
+        device_->capability_.get());
+  }
+
   CellularCapabilityCDMA *GetCapabilityCDMA() {
     return dynamic_cast<CellularCapabilityCDMA *>(device_->capability_.get());
   }
@@ -387,8 +393,8 @@
   Error error;
   device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
   dispatcher_.DispatchPendingEvents();
-  EXPECT_EQ(kMEID, device_->capability_->meid_);
-  EXPECT_EQ(kTestCarrier, device_->capability_->carrier_);
+  EXPECT_EQ(kMEID, GetCapabilityClassic()->meid_);
+  EXPECT_EQ(kTestCarrier, GetCapabilityClassic()->carrier_);
   EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
   ASSERT_TRUE(device_->service_.get());
   EXPECT_EQ(flimflam::kNetworkTechnology1Xrtt,
@@ -440,10 +446,10 @@
   device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
   EXPECT_TRUE(error.IsSuccess());
   dispatcher_.DispatchPendingEvents();
-  EXPECT_EQ(kIMEI, device_->capability_->imei_);
-  EXPECT_EQ(kIMSI, device_->capability_->imsi_);
+  EXPECT_EQ(kIMEI, GetCapabilityGSM()->imei_);
+  EXPECT_EQ(kIMSI, GetCapabilityGSM()->imsi_);
   EXPECT_EQ(kTestCarrier, GetCapabilityGSM()->spn_);
-  EXPECT_EQ(kMSISDN, device_->capability_->mdn_);
+  EXPECT_EQ(kMSISDN, GetCapabilityGSM()->mdn_);
   EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
   ASSERT_TRUE(device_->service_.get());
   EXPECT_EQ(flimflam::kNetworkTechnologyEdge,
@@ -461,7 +467,7 @@
       .WillOnce(Return(true));
   SetCellularType(Cellular::kTypeCDMA);
   device_->set_modem_state(Cellular::kModemStateConnected);
-  device_->capability_->meid_ = kMEID;
+  GetCapabilityClassic()->meid_ = kMEID;
   ExpectCdmaStartModem(flimflam::kNetworkTechnologyEvdo);
   Error error;
   device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
@@ -475,7 +481,7 @@
       .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
   SetCellularType(Cellular::kTypeCDMA);
   device_->set_modem_state(Cellular::kModemStateConnected);
-  device_->capability_->meid_ = kMEID;
+  GetCapabilityClassic()->meid_ = kMEID;
   ExpectCdmaStartModem(flimflam::kNetworkTechnologyEvdo);
   EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _))
       .WillOnce(Return(dhcp_config_));
@@ -508,7 +514,8 @@
 namespace {
 
 MATCHER(ContainsPhoneNumber, "") {
-  return ContainsKey(arg, CellularCapability::kConnectPropertyPhoneNumber);
+  return ContainsKey(arg,
+                     CellularCapabilityClassic::kConnectPropertyPhoneNumber);
 }
 
 }  // namespace
@@ -542,7 +549,7 @@
                       CellularCapability::kTimeoutConnect))
                 .Times(2)
                 .WillRepeatedly(Invoke(this, &CellularTest::InvokeConnect));
-  device_->capability_->simple_proxy_.reset(simple_proxy_.release());
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
   device_->service_->roaming_state_ = flimflam::kRoamingStateHome;
   device_->state_ = Cellular::kStateRegistered;
   device_->Connect(&error);
@@ -570,7 +577,7 @@
   EXPECT_CALL(*proxy_,
               Disconnect(_, _, CellularCapability::kTimeoutDefault))
       .WillOnce(Invoke(this, &CellularTest::InvokeDisconnect));
-  device_->capability_->proxy_.reset(proxy_.release());
+  GetCapabilityClassic()->proxy_.reset(proxy_.release());
   device_->Disconnect(&error);
   EXPECT_TRUE(error.IsSuccess());
   EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
@@ -585,7 +592,7 @@
   EXPECT_CALL(*simple_proxy_,
               Connect(_, _, _, CellularCapability::kTimeoutConnect))
                 .WillOnce(Invoke(this, &CellularTest::InvokeConnectFail));
-  device_->capability_->simple_proxy_.reset(simple_proxy_.release());
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
   Error error;
   device_->Connect(&error);
   EXPECT_EQ(Service::kStateFailure, device_->service_->state());
diff --git a/mm1_modem_proxy.cc b/mm1_modem_proxy.cc
index def4fe1..4ba6541 100644
--- a/mm1_modem_proxy.cc
+++ b/mm1_modem_proxy.cc
@@ -243,7 +243,8 @@
 void ModemProxy::Proxy::StateChanged(const int32_t &old,
                                      const int32_t &_new,
                                      const uint32_t &reason) {
-  state_changed_callback_.Run(old, _new, reason);
+  if (!state_changed_callback_.is_null())
+    state_changed_callback_.Run(old, _new, reason);
 }
 
 // Method callbacks inherited from
diff --git a/modem.cc b/modem.cc
index 899918a..a9f2e55 100644
--- a/modem.cc
+++ b/modem.cc
@@ -40,7 +40,8 @@
       provider_db_(provider_db),
       type_(Cellular::kTypeInvalid),
       pending_device_info_(false),
-      rtnl_handler_(RTNLHandler::GetInstance()) {
+      rtnl_handler_(RTNLHandler::GetInstance()),
+      proxy_factory_(ProxyFactory::GetInstance()) {
   LOG(INFO) << "Modem created: " << owner << " at " << path;
 }
 
@@ -52,9 +53,7 @@
 
 void Modem::Init() {
   dbus_properties_proxy_.reset(
-      ProxyFactory::GetInstance()->CreateDBusPropertiesProxy(this,
-                                                             path(),
-                                                             owner()));
+      proxy_factory_->CreateDBusPropertiesProxy(this, path(), owner()));
 }
 
 void Modem::OnDeviceInfoAvailable(const string &link_name) {
@@ -121,17 +120,18 @@
     return;
   }
 
-  if (type_ == Cellular::kTypeUniversal) {
-    // TODO(rochberg):
-    LOG(ERROR) << "Cannot construct Cellular object for MM1";
-    return;
-  }
-
   string address = address_bytes.HexEncode();
   device_ = ConstructCellular(link_name_, address, interface_index);
 
-  uint32 modem_state = Cellular::kModemStateUnknown;
-  DBusProperties::GetUint32(modem_properties, kPropertyState, &modem_state);
+  // Note: 0 happens to map to Cellular::kModemStateUnknown for all
+  // types of modems
+  uint32 modem_state = 0;
+  // TODO(jglasgow): refactor to avoid explicit if on modem type.  The
+  // ModemManager1 interface type is an "i" not a "u".  Handle that
+  // during refactoring.
+  if (type_ != Cellular::kTypeUniversal)
+    DBusProperties::GetUint32(modem_properties, kPropertyState, &modem_state);
+
   device_->set_modem_state(ConvertMmToCellularModemState(modem_state));
 
   // Give the device a chance to extract any capability-specific properties.
@@ -141,10 +141,14 @@
 }
 
 void Modem::OnDBusPropertiesChanged(
-    const string &/*interface*/,
-    const DBusPropertiesMap &/*changed_properties*/,
-    const vector<string> &/*invalidated_properties*/) {
-  // Ignored.
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  if (device().get()) {
+    device()->OnDBusPropertiesChanged(interface,
+                                      changed_properties,
+                                      invalidated_properties);
+  }
 }
 
 void Modem::OnModemManagerPropertiesChanged(
diff --git a/modem.h b/modem.h
index 0eb684c..92789ff 100644
--- a/modem.h
+++ b/modem.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include <base/basictypes.h>
+#include <base/file_util.h>
 #include <base/memory/scoped_ptr.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
@@ -82,7 +83,11 @@
 
  private:
   friend class ModemTest;
+  friend class Modem1Test;
+  FRIEND_TEST(Modem1Test, Init);
+  FRIEND_TEST(Modem1Test, CreateDeviceMM1);
   FRIEND_TEST(ModemManager1Test, Connect);
+  FRIEND_TEST(ModemManager1Test, AddRemoveInterfaces);
   FRIEND_TEST(ModemManagerClassicTest, Connect);
   FRIEND_TEST(ModemManagerCoreTest, ShouldAddModem);
   FRIEND_TEST(ModemTest, CreateDeviceEarlyFailures);
@@ -120,6 +125,9 @@
   bool pending_device_info_;
   RTNLHandler *rtnl_handler_;
 
+  // Store cached copies of singletons for speed/ease of testing.
+  ProxyFactory *proxy_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(Modem);
 };
 
@@ -148,8 +156,6 @@
 };
 
 class Modem1 : public Modem {
-  // TODO(rochberg) Need to register to receive DBus property changes.
-  // crosbug.com/28596
  public:
   Modem1(const std::string &owner,
          const std::string &path,
@@ -170,6 +176,10 @@
                            std::string *name) const;
 
  private:
+  friend class Modem1Test;
+
+  FilePath netfiles_path_;  // Used for testing
+
   DISALLOW_COPY_AND_ASSIGN(Modem1);
 };
 
diff --git a/modem_1.cc b/modem_1.cc
index e8fc65f..df71d67 100644
--- a/modem_1.cc
+++ b/modem_1.cc
@@ -14,6 +14,11 @@
 
 namespace shill {
 
+namespace {
+// The default place where the system keeps symbolic links for network device
+const char kDefaultNetfilesPath[] = "/sys/class/net";
+} // namespace {}
+
 Modem1::Modem1(const string &owner,
                const string &path,
                ControlInterface *control_interface,
@@ -22,7 +27,8 @@
                Manager *manager,
                mobile_provider_db *provider_db)
     : Modem(owner, path, control_interface, dispatcher, metrics, manager,
-            provider_db) {
+            provider_db),
+      netfiles_path_(kDefaultNetfilesPath) {
 }
 
 Modem1::~Modem1() {}
@@ -52,7 +58,7 @@
                          string *name) const {
   string device_prop;
   if (!DBusProperties::GetString(modem_props,
-                                 Modem::kPropertyLinkName,
+                                 MM_MODEM_PROPERTY_DEVICE,
                                  &device_prop)) {
     return false;
   }
@@ -61,16 +67,15 @@
   //  /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2
   FilePath device_path(device_prop);
 
-  // Each entry in /sys/class/net has the name of a network interface
-  // and is a symlink into the actual device structure:
+  // Each entry in |netfiles_path_" (typically /sys/class/net)
+  // has the name of a network interface and is a symlink into the
+  // actual device structure:
   //  eth0 -> ../../devices/pci0000:00/0000:00:1c.5/0000:01:00.0/net/eth0
   // Iterate over all of these and see if any of them point into
   // subdirectories of the sysfs path from the Device property.
-  FilePath netfiles_path("/sys/class/net");
-
   // FileEnumerator warns that it is a blocking interface; that
   // shouldn't be a problem here.
-  file_util::FileEnumerator netfiles(netfiles_path,
+  file_util::FileEnumerator netfiles(netfiles_path_,
                                      false, // don't recurse
                                      file_util::FileEnumerator::DIRECTORIES);
   for (FilePath link = netfiles.Next(); !link.empty(); link = netfiles.Next()) {
@@ -78,7 +83,7 @@
     if (!file_util::ReadSymbolicLink(link, &target))
       continue;
     if (!target.IsAbsolute())
-      target = netfiles_path.Append(target);
+      target = netfiles_path_.Append(target);
     if (file_util::ContainsPath(device_path, target)) {
       *name = link.BaseName().value();
       return true;
@@ -99,6 +104,8 @@
 
   // We cannot check the IP method to make sure it's not PPP. The IP
   // method will be checked later when the bearer object is fetched.
+  // TODO(jglasgow): We should pass i_to_p because there are lots of
+  // properties we might want
   CreateDeviceFromModemProperties(modem_properties->second);
 }
 
diff --git a/modem_1_unittest.cc b/modem_1_unittest.cc
new file mode 100644
index 0000000..b50370a
--- /dev/null
+++ b/modem_1_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 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 "shill/modem.h"
+
+#include <base/scoped_temp_dir.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <mm/ModemManager-enums.h>
+#include <mm/ModemManager-names.h>
+
+#include "shill/dbus_property_matchers.h"
+#include "shill/event_dispatcher.h"
+#include "shill/manager.h"
+#include "shill/mock_cellular.h"
+#include "shill/mock_control.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+#include "shill/mock_rtnl_handler.h"
+#include "shill/proxy_factory.h"
+#include "shill/rtnl_handler.h"
+
+using std::string;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+
+const int kTestInterfaceIndex = 5;
+const char kLinkName[] = "usb0";
+const char kOwner[] = ":1.18";
+const char kPath[] = "/org/chromium/ModemManager/Gobi/0";
+const unsigned char kAddress[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+const char kAddressAsString[] = "000102030405";
+
+}  // namespace
+
+class Modem1Test : public Test {
+ public:
+  Modem1Test()
+      : manager_(&control_interface_, &dispatcher_, &metrics_, &glib_),
+        info_(&control_interface_, &dispatcher_, &metrics_, &manager_),
+        proxy_(new MockDBusPropertiesProxy()),
+        proxy_factory_(this),
+        modem_(
+            new Modem1(
+                kOwner,
+                kPath,
+                &control_interface_,
+                &dispatcher_,
+                &metrics_,
+                &manager_,
+                static_cast<mobile_provider_db *>(NULL))) {}
+  virtual void SetUp();
+  virtual void TearDown();
+
+  void ReplaceSingletons() {
+    modem_->rtnl_handler_ = &rtnl_handler_;
+    modem_->proxy_factory_ = &proxy_factory_;
+  }
+
+ protected:
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(Modem1Test *test) : test_(test) {}
+
+    virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
+        DBusPropertiesProxyDelegate */*delegate*/,
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->proxy_.release();
+    }
+
+   private:
+    Modem1Test *test_;
+  };
+
+  MockGLib glib_;
+  MockControl control_interface_;
+  EventDispatcher dispatcher_;
+  MockMetrics metrics_;
+  MockManager manager_;
+  MockDeviceInfo info_;
+  scoped_ptr<MockDBusPropertiesProxy> proxy_;
+  TestProxyFactory proxy_factory_;
+  scoped_ptr<Modem1> modem_;
+  MockRTNLHandler rtnl_handler_;
+  ByteString expected_address_;
+  ScopedTempDir temp_dir_;
+  string device_;
+};
+
+void Modem1Test::SetUp() {
+  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  modem_->netfiles_path_ = temp_dir_.path();
+  device_ = temp_dir_.path().Append("devices").Append(kLinkName).value();
+  FilePath device_dir = FilePath(device_).Append("1-2/3-4");
+  ASSERT_TRUE(file_util::CreateDirectory(device_dir));
+  FilePath symlink(temp_dir_.path().Append(kLinkName));
+  ASSERT_TRUE(file_util::CreateSymbolicLink(device_dir, symlink));
+
+  EXPECT_EQ(kOwner, modem_->owner_);
+  EXPECT_EQ(kPath, modem_->path_);
+  ReplaceSingletons();
+  expected_address_ = ByteString(kAddress, arraysize(kAddress));
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(kLinkName)).
+      WillRepeatedly(Return(kTestInterfaceIndex));
+
+  EXPECT_CALL(manager_, device_info()).WillRepeatedly(Return(&info_));
+  EXPECT_CALL(info_, GetMACAddress(kTestInterfaceIndex, _)).
+      WillOnce(DoAll(SetArgumentPointee<1>(expected_address_),
+                     Return(true)));
+}
+
+void Modem1Test::TearDown() {
+  modem_.reset();
+}
+
+TEST_F(Modem1Test, CreateDeviceMM1) {
+  DBusInterfaceToProperties i_to_p;
+  DBusPropertiesMap modem_properties;
+  DBus::Variant lock;
+  lock.writer().append_uint32(MM_MODEM_LOCK_NONE);
+  modem_properties[MM_MODEM_PROPERTY_UNLOCKREQUIRED] = lock;
+  DBus::Variant device_variant;
+  device_variant.writer().append_string(device_.c_str());
+  modem_properties[MM_MODEM_PROPERTY_DEVICE] = device_variant;
+  i_to_p[MM_DBUS_INTERFACE_MODEM] = modem_properties;
+
+  modem_->CreateDeviceMM1(i_to_p);
+  EXPECT_TRUE(modem_->device().get());
+}
+
+}  // namespace shill
diff --git a/modem_unittest.cc b/modem_unittest.cc
index 9e609e2..e9430ac 100644
--- a/modem_unittest.cc
+++ b/modem_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "shill/modem.h"
+
 #include <vector>
 
 #include <gmock/gmock.h>
@@ -24,7 +26,6 @@
 #include "shill/mock_metrics.h"
 #include "shill/mock_modem.h"
 #include "shill/mock_rtnl_handler.h"
-#include "shill/modem.h"
 #include "shill/proxy_factory.h"
 #include "shill/rtnl_handler.h"
 
@@ -80,8 +81,6 @@
     modem_->rtnl_handler_ = &rtnl_handler_;
   }
 
-  void ExpectLinkRelatedCalls();
-
  protected:
   class TestProxyFactory : public ProxyFactory {
    public: