shill: Implement CellularCapabilityUniversalCDMA.

This CL implements the logic necessary to connect to a CDMA network
while running modemmanager-next. This code currently works only on
Gobi3k modems with Chromebooks running modemmanager-next with the QMI
plugin. This CL does not implement activation logic.

BUG=chromium:219821
TEST=1. Build and run unit tests.
     2. On a Chromebook with a Gobi3k modem which has been activated on
        a CDMA network (currently only Verizon Wireless):
        1. Build and install ChromeOS with USE="qmi -gobi",
        2. Make sure that the modem is running a Verizon firmware.
        3. On the Settings page, clicking on Mobile Data should reveal
           Verizon Wireless as the current cellular network.
        4. Make sure that we can connect/disconnect to Verizon Wireless.
           All actions that do not require network activation should be
           working.

Change-Id: I55f3bf9640e2d88615280591c027043a05c10bf5
Reviewed-on: https://gerrit.chromium.org/gerrit/41797
Reviewed-by: Arman Uguray <armansito@chromium.org>
Tested-by: Arman Uguray <armansito@chromium.org>
Commit-Queue: Arman Uguray <armansito@chromium.org>
diff --git a/Makefile b/Makefile
index f38ddf8..1c51d4c 100644
--- a/Makefile
+++ b/Makefile
@@ -334,6 +334,14 @@
 	arp_packet_unittest.o \
 	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_universal_unittest.o \
+	cellular_capability_universal_cdma_unittest.o \
+	cellular_operator_info_unittest.o \
+	cellular_service_unittest.o \
+	cellular_unittest.o \
 	certificate_file_unittest.o \
 	crypto_des_cbc_unittest.o \
 	crypto_provider_unittest.o \
@@ -532,6 +540,7 @@
 	cellular_capability_classic.o \
 	cellular_capability_gsm.o \
 	cellular_capability_universal.o \
+	cellular_capability_universal_cdma.o \
 	cellular_error.o \
 	cellular_operator_info.o \
 	cellular_service.o \
@@ -562,6 +571,7 @@
 	cellular_capability_cdma_unittest.o \
 	cellular_capability_classic_unittest.o \
 	cellular_capability_gsm_unittest.o \
+	cellular_capability_universal_cdma_unittest.o \
 	cellular_capability_universal_unittest.o \
 	cellular_operator_info_unittest.o \
 	cellular_service_unittest.o \
diff --git a/cellular.cc b/cellular.cc
index 46ac230..4f56668 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -21,6 +21,7 @@
 #include "shill/cellular_capability_cdma.h"
 #include "shill/cellular_capability_gsm.h"
 #include "shill/cellular_capability_universal.h"
+#include "shill/cellular_capability_universal_cdma.h"
 #include "shill/cellular_service.h"
 #include "shill/control_interface.h"
 #include "shill/device.h"
@@ -298,6 +299,12 @@
           proxy_factory_,
           modem_info_));
       break;
+    case kTypeUniversalCDMA:
+      capability_.reset(new CellularCapabilityUniversalCDMA(
+          this,
+          proxy_factory_,
+          modem_info_));
+      break;
     default: NOTREACHED();
   }
 }
diff --git a/cellular.h b/cellular.h
index 1d65df5..2be9331 100644
--- a/cellular.h
+++ b/cellular.h
@@ -35,6 +35,7 @@
     kTypeGSM,
     kTypeCDMA,
     kTypeUniversal,  // ModemManager1
+    kTypeUniversalCDMA,
     kTypeInvalid,
   };
 
@@ -221,6 +222,7 @@
   friend class CellularCapabilityCDMATest;
   friend class CellularCapabilityGSMTest;
   friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
   friend class CellularServiceTest;
   friend class ModemTest;
   FRIEND_TEST(CellularCapabilityCDMATest, CreateFriendlyServiceName);
@@ -233,6 +235,10 @@
   FRIEND_TEST(CellularCapabilityTest, FinishEnable);
   FRIEND_TEST(CellularCapabilityTest, GetModemInfo);
   FRIEND_TEST(CellularCapabilityTest, GetModemStatus);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, CreateFriendlyServiceName);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, OnCDMARegistrationChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOLP);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOperatorInfo);
   FRIEND_TEST(CellularCapabilityUniversalMainTest, AllowRoaming);
   FRIEND_TEST(CellularCapabilityUniversalMainTest, CreateFriendlyServiceName);
   FRIEND_TEST(CellularCapabilityUniversalMainTest, Connect);
diff --git a/cellular_capability.h b/cellular_capability.h
index 3597df6..7c02ff9 100644
--- a/cellular_capability.h
+++ b/cellular_capability.h
@@ -31,17 +31,19 @@
 // The CellularCapability is directly subclassed by:
 // *  CelllularCapabilityUniversal which handles all modems managed by
 //    a modem manager using the the org.chromium.ModemManager1 DBUS
-//    interface
+//    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
+//    and GSM modems.
 //
 // Pictorially:
 //
 // CellularCapability
 //       |
 //       |-- CellularCapabilityUniversal
+//       |            |
+//       |            |-- CellularCapabilityUniversalCDMA
 //       |
 //       |-- CellularCapabilityClassic
 //                    |
@@ -49,6 +51,10 @@
 //                    |
 //                    |-- CellularCapabilityCDMA
 //
+// TODO(armansito): Currently, 3GPP logic is handled by
+// CellularCapabilityUniversal. Eventually CellularCapabilityUniversal will
+// only serve as the abstract base class for ModemManager1 3GPP and CDMA
+// capabilities.
 class CellularCapability {
  public:
   // SimLockStatus represents the fields in the Cellular.SIMLockStatus
@@ -219,6 +225,7 @@
   friend class CellularCapabilityGSMTest;
   friend class CellularCapabilityTest;
   friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
   friend class CellularTest;
   FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
   FRIEND_TEST(CellularTest, Connect);
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
index 7cfc30c..1e2094c 100644
--- a/cellular_capability_universal.cc
+++ b/cellular_capability_universal.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -132,7 +132,6 @@
     : CellularCapability(cellular, proxy_factory, modem_info),
       weak_ptr_factory_(this),
       registration_state_(MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN),
-      cdma_registration_state_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
       capabilities_(MM_MODEM_CAPABILITY_NONE),
       current_capabilities_(MM_MODEM_CAPABILITY_NONE),
       access_technologies_(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN),
@@ -215,9 +214,6 @@
   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()));
@@ -608,7 +604,6 @@
 void CellularCapabilityUniversal::ReleaseProxies() {
   SLOG(Cellular, 2) << __func__;
   modem_3gpp_proxy_.reset();
-  modem_cdma_proxy_.reset();
   modem_proxy_.reset();
   modem_simple_proxy_.reset();
   sim_proxy_.reset();
@@ -943,12 +938,10 @@
   CellularService::OLP olp;
   olp.CopyFrom(*result);
   string post_data = olp.GetPostData();
-  ReplaceSubstringsAfterOffset(&post_data, 0, "${esn}", esn_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${iccid}", sim_identifier_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${imei}", imei_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${imsi}", imsi_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${mdn}", mdn_);
-  ReplaceSubstringsAfterOffset(&post_data, 0, "${meid}", meid_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${min}", min_);
   olp.SetPostData(post_data);
   cellular()->service()->SetOLP(olp);
diff --git a/cellular_capability_universal.h b/cellular_capability_universal.h
index 4f934af..297c9e9 100644
--- a/cellular_capability_universal.h
+++ b/cellular_capability_universal.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -20,7 +20,6 @@
 #include "shill/cellular_capability.h"
 #include "shill/mm1_bearer_proxy_interface.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"
@@ -80,13 +79,13 @@
   virtual void OnServiceCreated();
   virtual void SetupConnectProperties(DBusPropertiesMap *properties);
   virtual void GetProperties();
+  virtual bool IsRegistered();
   virtual bool IsServiceActivationRequired() const;
   virtual void Register(const ResultCallback &callback);
 
   virtual void RegisterOnNetwork(const std::string &network_id,
                                  Error *error,
                                  const ResultCallback &callback);
-  virtual bool IsRegistered();
   virtual void SetUnregistered(bool searching);
   virtual std::string CreateFriendlyServiceName();
   virtual void RequirePIN(const std::string &pin, bool require,
@@ -117,6 +116,23 @@
   virtual void InitProxies();
   virtual void ReleaseProxies();
 
+  // Updates the |sim_path_| variable and creates a new proxy to the
+  // DBUS ModemManager1.Sim interface.
+  // TODO(armansito): Put this method in a 3GPP-only subclass.
+  virtual void OnSimPathChanged(const std::string &sim_path);
+
+  // Updates the online payment portal information, if any, for the cellular
+  // provider.
+  virtual void UpdateOLP();
+
+  const std::string &mdn() const { return mdn_; };
+
+  const std::string &meid() const { return meid_; }
+  void set_meid(const std::string &meid) { meid_ = meid; }
+
+  const std::string &esn() const { return esn_; }
+  void set_esn(const std::string &esn) { esn_ = esn; }
+
  private:
   // Constants used in scan results.  Make available to unit tests.
   // TODO(jglasgow): Generate from modem manager into ModemManager-names.h.
@@ -146,6 +162,9 @@
   friend class CellularTest;
   friend class CellularCapabilityTest;
   friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, PropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOLP);
   FRIEND_TEST(CellularCapabilityUniversalMainTest, AllowRoaming);
   FRIEND_TEST(CellularCapabilityUniversalMainTest,
               ActivationWaitForRegisterTimeout);
@@ -217,10 +236,6 @@
 
   // Methods used in acquiring information related to the carrier;
 
-  // Updates the online payment portal information, if any, for the cellular
-  // provider.
-  void UpdateOLP();
-
   // Updates the Universal operator name and country based on a newly
   // obtained network id.
   void UpdateOperatorInfo();
@@ -273,9 +288,6 @@
 
   void OnSignalQualityChanged(uint32 quality);
 
-  // Updates the sim_path_ variable and creates a new proxy to the
-  // DBUS ModemManager1.Sim interface
-  void OnSimPathChanged(const std::string &sim_path);
   void OnModemCapabilitesChanged(uint32 capabilities);
   void OnModemCurrentCapabilitiesChanged(uint32 current_capabilities);
   void OnMdnChanged(const std::string &mdn);
@@ -305,6 +317,7 @@
   void OnFacilityLocksChanged(uint32 locks);
 
   // SIM property change handlers
+  // TODO(armansito): Put these methods in a 3GPP-only subclass.
   void OnSimPropertiesChanged(
       const DBusPropertiesMap &props,
       const std::vector<std::string> &invalidated_properties);
@@ -353,7 +366,6 @@
   void OnResetAfterActivationReply(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_;
@@ -361,7 +373,6 @@
   base::WeakPtrFactory<CellularCapabilityUniversal> weak_ptr_factory_;
 
   MMModem3gppRegistrationState registration_state_;
-  MMModemCdmaRegistrationState cdma_registration_state_;
 
   // Bits based on MMModemCapabilities
   uint32 capabilities_;          // technologies supported, may require reload
diff --git a/cellular_capability_universal_cdma.cc b/cellular_capability_universal_cdma.cc
new file mode 100644
index 0000000..69e9bf1
--- /dev/null
+++ b/cellular_capability_universal_cdma.cc
@@ -0,0 +1,364 @@
+// Copyright (c) 2013 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_cdma.h"
+
+#include <chromeos/dbus/service_constants.h>
+#include <base/stringprintf.h>
+#include <base/string_number_conversions.h>
+#include <base/string_util.h>
+
+#include "shill/cellular_operator_info.h"
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/logging.h"
+#include "shill/proxy_factory.h"
+
+#ifdef MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
+#error "Do not include mm-modem.h"
+#endif
+
+using base::UintToString;
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace {
+
+string FormattedSID(const string &sid) {
+  return "[SID=" + sid + "]";
+}
+
+}  // namespace
+
+// static
+unsigned int
+CellularCapabilityUniversalCDMA::friendly_service_name_id_cdma_ = 0;
+
+CellularCapabilityUniversalCDMA::CellularCapabilityUniversalCDMA(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory,
+    ModemInfo *modem_info)
+    : CellularCapabilityUniversal(cellular,
+                                  proxy_factory,
+                                  modem_info),
+      cdma_1x_registration_state_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
+      cdma_evdo_registration_state_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
+      nid_(0),
+      sid_(0) {
+  SLOG(Cellular, 2) << "Cellular capability constructed: Universal CDMA";
+  // TODO(armansito): Need to expose PRL version here to carry through
+  // activation. PRL version is not obtainable from ModemManager and will
+  // need to be stored in a special place (such as CellularOperatorInfo).
+  // See crbug.com/197330.
+}
+
+void CellularCapabilityUniversalCDMA::InitProxies() {
+  SLOG(Cellular, 2) << __func__;
+  modem_cdma_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModemCdmaProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  CellularCapabilityUniversal::InitProxies();
+}
+
+void CellularCapabilityUniversalCDMA::ReleaseProxies() {
+  SLOG(Cellular, 2) << __func__;
+  modem_cdma_proxy_.reset();
+  CellularCapabilityUniversal::ReleaseProxies();
+}
+
+void CellularCapabilityUniversalCDMA::Activate(
+    const string &carrier,
+    Error *error,
+    const ResultCallback &callback) {
+  // TODO(armansito): Implement activation.
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversalCDMA::CompleteActivation(Error *error) {
+  // TODO(armansito): Implement activation.
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversalCDMA::DisconnectCleanup() {
+  SLOG(Cellular, 2) << __func__;
+  // TODO(armansito): Handle activation logic here.
+}
+
+void CellularCapabilityUniversalCDMA::OnServiceCreated() {
+  SLOG(Cellular, 2) << __func__;
+  // TODO (armansito): Set storage identifier here  based on the superclass
+  // implementation.
+  bool activation_required = IsServiceActivationRequired();
+  cellular()->service()->SetActivationState(
+      activation_required ?
+      flimflam::kActivationStateNotActivated :
+      flimflam::kActivationStateActivated);
+  cellular()->service()->SetActivateOverNonCellularNetwork(activation_required);
+  UpdateServingOperator();
+  UpdateOLP();
+}
+
+void CellularCapabilityUniversalCDMA::UpdateOLP() {
+  SLOG(Cellular,2) << __func__;
+  if (!modem_info()->cellular_operator_info())
+    return;
+
+  const CellularService::OLP *result =
+      modem_info()->cellular_operator_info()->GetOLPBySID(
+          UintToString(sid_));
+  if (!result)
+    return;
+
+  CellularService::OLP olp;
+  olp.CopyFrom(*result);
+  string post_data = olp.GetPostData();
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${esn}", esn());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${mdn}", mdn());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${meid}", meid());
+  olp.SetPostData(post_data);
+  cellular()->service()->SetOLP(olp);
+}
+
+void CellularCapabilityUniversalCDMA::GetProperties() {
+  SLOG(Cellular, 2) << __func__;
+  CellularCapabilityUniversal::GetProperties();
+
+  scoped_ptr<DBusPropertiesProxyInterface> properties_proxy(
+      proxy_factory()->CreateDBusPropertiesProxy(cellular()->dbus_path(),
+                                                 cellular()->dbus_owner()));
+  DBusPropertiesMap properties(
+      properties_proxy->GetAll(MM_DBUS_INTERFACE_MODEM_MODEMCDMA));
+  OnModemCDMAPropertiesChanged(properties, vector<string>());
+}
+
+string CellularCapabilityUniversalCDMA::CreateFriendlyServiceName() {
+  SLOG(Cellular, 2) << __func__ << ": " << GetRoamingStateString();
+
+  if (provider_.GetCode().empty()) {
+    UpdateOperatorInfo();
+  }
+
+  string name = provider_.GetName();
+  if (!name.empty()) {
+    // TODO(armansito): We may need to show the provider name in a
+    // specific way if roaming.
+    return name;
+  }
+
+  string code = provider_.GetCode();
+  if (!code.empty()) {
+    return "cellular_sid_" + code;
+  }
+
+  return base::StringPrintf("CDMANetwork%u", friendly_service_name_id_cdma_++);
+}
+
+void CellularCapabilityUniversalCDMA::UpdateOperatorInfo() {
+  SLOG(Cellular, 2) << __func__;
+
+  if (sid_ == 0 || !modem_info()->cellular_operator_info()) {
+    SLOG(Cellular, 2) << "No provider is currently available.";
+    provider_.SetCode("");
+    return;
+  }
+
+  string sid = UintToString(sid_);
+  const CellularOperatorInfo::CellularOperator *provider =
+      modem_info()->cellular_operator_info()->GetCellularOperatorBySID(sid);
+  if (!provider) {
+    SLOG(Cellular, 2) << "CDMA provider with "
+                      << FormattedSID(sid)
+                      << " not found.";
+    return;
+  }
+
+  if (!provider->name_list().empty()) {
+    provider_.SetName(provider->name_list()[0].name);
+  }
+  provider_.SetCode(sid);
+  provider_.SetCountry(provider->country());
+
+  // TODO(armansito): The CDMA interface only returns information about the
+  // current serving carrier, so for now both the home provider and the
+  // serving operator will be the same in case of roaming. We should figure
+  // out if there is a way to (and whether or not it is necessary to)
+  // determine if we're roaming.
+  cellular()->set_home_provider(provider_);
+
+  UpdateServingOperator();
+}
+
+void CellularCapabilityUniversalCDMA::UpdateServingOperator() {
+  SLOG(Cellular, 2) << __func__;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetServingOperator(cellular()->home_provider());
+  }
+}
+
+void CellularCapabilityUniversalCDMA::Register(const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::RegisterOnNetwork(
+    const string &network_id,
+    Error *error,
+    const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+bool CellularCapabilityUniversalCDMA::IsRegistered() {
+  return (cdma_1x_registration_state_ ==
+              MM_MODEM_CDMA_REGISTRATION_STATE_HOME ||
+          cdma_1x_registration_state_ ==
+              MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING ||
+          cdma_evdo_registration_state_ ==
+              MM_MODEM_CDMA_REGISTRATION_STATE_HOME ||
+          cdma_evdo_registration_state_ ==
+              MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+}
+
+void CellularCapabilityUniversalCDMA::SetUnregistered(bool /*searching*/) {
+  cdma_1x_registration_state_ = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  cdma_evdo_registration_state_ = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+}
+
+void CellularCapabilityUniversalCDMA::RequirePIN(
+    const string &pin, bool require,
+    Error *error, const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::EnterPIN(
+    const string &pin,
+    Error *error,
+    const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::UnblockPIN(
+    const string &unblock_code,
+    const string &pin,
+    Error *error,
+    const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::ChangePIN(
+    const string &old_pin, const string &new_pin,
+    Error *error, const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::Scan(Error *error,
+                                           const ResultCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+void CellularCapabilityUniversalCDMA::OnSimPathChanged(
+    const string &sim_path) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+string CellularCapabilityUniversalCDMA::GetRoamingStateString() const {
+  uint32 state = cdma_evdo_registration_state_;
+  if (state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+    state = cdma_1x_registration_state_;
+  }
+  switch (state) {
+    case MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN:
+    case MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED:
+      break;
+    case MM_MODEM_CDMA_REGISTRATION_STATE_HOME:
+      return flimflam::kRoamingStateHome;
+    case MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING:
+      return flimflam::kRoamingStateRoaming;
+    default:
+      NOTREACHED();
+  }
+  return flimflam::kRoamingStateUnknown;
+}
+
+void CellularCapabilityUniversalCDMA::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  SLOG(Cellular, 2) << __func__ << "(" << interface << ")";
+  if (interface == MM_DBUS_INTERFACE_MODEM_MODEMCDMA) {
+    OnModemCDMAPropertiesChanged(changed_properties, invalidated_properties);
+  } else {
+    CellularCapabilityUniversal::OnDBusPropertiesChanged(
+        interface, changed_properties, invalidated_properties);
+  }
+}
+
+void CellularCapabilityUniversalCDMA::OnModemCDMAPropertiesChanged(
+    const DBusPropertiesMap &properties,
+    const std::vector<std::string> &/*invalidated_properties*/) {
+  SLOG(Cellular, 2) << __func__;
+  string str_value;
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEMCDMA_PROPERTY_MEID,
+                                &str_value))
+    set_meid(str_value);
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEMCDMA_PROPERTY_ESN,
+                                &str_value))
+    set_esn(str_value);
+
+  uint32_t sid = sid_;
+  uint32_t nid = nid_;
+  MMModemCdmaRegistrationState state_1x = cdma_1x_registration_state_;
+  MMModemCdmaRegistrationState state_evdo = cdma_evdo_registration_state_;
+  bool registration_changed = false;
+  uint32_t uint_value;
+  if (DBusProperties::GetUint32(
+      properties,
+      MM_MODEM_MODEMCDMA_PROPERTY_CDMA1XREGISTRATIONSTATE,
+      &uint_value)) {
+    state_1x = static_cast<MMModemCdmaRegistrationState>(uint_value);
+    registration_changed = true;
+  }
+  if (DBusProperties::GetUint32(
+      properties,
+      MM_MODEM_MODEMCDMA_PROPERTY_EVDOREGISTRATIONSTATE,
+      &uint_value)) {
+    state_evdo = static_cast<MMModemCdmaRegistrationState>(uint_value);
+    registration_changed = true;
+  }
+  if (DBusProperties::GetUint32(
+      properties,
+      MM_MODEM_MODEMCDMA_PROPERTY_SID,
+      &uint_value)) {
+    sid = uint_value;
+    registration_changed = true;
+  }
+  if (DBusProperties::GetUint32(
+      properties,
+      MM_MODEM_MODEMCDMA_PROPERTY_NID,
+      &uint_value)) {
+    nid = uint_value;
+    registration_changed = true;
+  }
+  if (registration_changed)
+    OnCDMARegistrationChanged(state_1x, state_evdo, sid, nid);
+}
+
+void CellularCapabilityUniversalCDMA::OnCDMARegistrationChanged(
+      MMModemCdmaRegistrationState state_1x,
+      MMModemCdmaRegistrationState state_evdo,
+      uint32_t sid, uint32_t nid) {
+  SLOG(Cellular, 2) << __func__
+                    << ": state_1x=" << state_1x
+                    << ", state_evdo=" << state_evdo;
+  cdma_1x_registration_state_ = state_1x;
+  cdma_evdo_registration_state_ = state_evdo;
+  sid_ = sid;
+  nid_ = nid;
+  UpdateOperatorInfo();
+  cellular()->HandleNewRegistrationState();
+}
+
+}  // namespace shill
diff --git a/cellular_capability_universal_cdma.h b/cellular_capability_universal_cdma.h
new file mode 100644
index 0000000..c794b69
--- /dev/null
+++ b/cellular_capability_universal_cdma.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2013 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_CDMA_H_
+#define SHILL_CELLULAR_CAPABILITY_UNIVERSAL_CDMA_H_
+
+#include "shill/cellular_capability_universal.h"
+#include "shill/cellular.h"
+#include "shill/mm1_modem_modemcdma_proxy_interface.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace shill {
+
+class CellularCapabilityUniversalCDMA : public CellularCapabilityUniversal {
+ public:
+  CellularCapabilityUniversalCDMA(
+      Cellular *cellular,
+      ProxyFactory *proxy_factory,
+      ModemInfo *modem_info);
+
+  // Inherited from CellularCapability.
+  virtual void Activate(const std::string &carrier,
+                        Error *error, const ResultCallback &callback);
+  virtual void CompleteActivation(Error *error);
+  virtual std::string CreateFriendlyServiceName();
+  virtual void DisconnectCleanup();
+  virtual void GetProperties();
+  virtual std::string GetRoamingStateString() const;
+  virtual bool IsRegistered();
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
+  virtual void OnServiceCreated();
+  virtual void SetUnregistered(bool searching);
+
+  // TODO(armansito): Remove once 3GPP is implemented in its own class
+  virtual void Register(const ResultCallback &callback);
+  virtual void RegisterOnNetwork(const std::string &network_id,
+                                 Error *error,
+                                 const ResultCallback &callback);
+  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 OnSimPathChanged(const std::string &sim_path);
+
+ protected:
+  virtual void InitProxies();
+  virtual void ReleaseProxies();
+
+  virtual void UpdateOLP();
+
+ private:
+  friend class CellularCapabilityUniversalCDMATest;
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, CreateFriendlyServiceName);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, OnCDMARegistrationChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, PropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOLP);
+  FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOperatorInfo);
+
+  // CDMA property change handlers
+  virtual void OnModemCDMAPropertiesChanged(
+      const DBusPropertiesMap &properties,
+      const std::vector<std::string> &invalidated_properties);
+  void OnCDMARegistrationChanged(
+      MMModemCdmaRegistrationState state_1x,
+      MMModemCdmaRegistrationState state_evdo,
+      uint32_t sid, uint32_t nid);
+
+  void UpdateOperatorInfo();
+  void UpdateServingOperator();
+
+  scoped_ptr<mm1::ModemModemCdmaProxyInterface> modem_cdma_proxy_;
+
+  MMModemCdmaRegistrationState cdma_1x_registration_state_;
+  MMModemCdmaRegistrationState cdma_evdo_registration_state_;
+
+  // Current network operator. Since CDMA operator information is acquired
+  // using the (SID, NID) pair, which itself is acquired OTA, |provider_|
+  // can both be the home provider or the serving operator for roaming.
+  Cellular::Operator provider_;
+  uint32_t nid_;
+  uint32_t sid_;
+
+  // TODO(armansito): Use the common name |friendly_service_name_id_| once
+  // CellularCapabilityUniversal gets broken up for 3GPP.
+  static unsigned int friendly_service_name_id_cdma_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityUniversalCDMA);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CAPABILITY_UNIVERSAL_CDMA_H_
diff --git a/cellular_capability_universal_cdma_unittest.cc b/cellular_capability_universal_cdma_unittest.cc
new file mode 100644
index 0000000..eae9390
--- /dev/null
+++ b/cellular_capability_universal_cdma_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2013 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_cdma.h"
+
+#include <string>
+#include <vector>
+
+#include <base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular.h"
+#include "shill/cellular_service.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_cellular_operator_info.h"
+#include "shill/mock_cellular_service.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+#include "shill/mock_mm1_bearer_proxy.h"
+#include "shill/mock_mm1_modem_modem3gpp_proxy.h"
+#include "shill/mock_mm1_modem_modemcdma_proxy.h"
+#include "shill/mock_mm1_modem_proxy.h"
+#include "shill/mock_mm1_modem_simple_proxy.h"
+#include "shill/mock_mm1_sim_proxy.h"
+#include "shill/mock_modem_info.h"
+#include "shill/nice_mock_control.h"
+#include "shill/proxy_factory.h"
+
+using base::StringPrintf;
+using std::string;
+using std::vector;
+using testing::Invoke;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace shill {
+
+class CellularCapabilityUniversalCDMATest : public testing::Test {
+ public:
+  CellularCapabilityUniversalCDMATest()
+      : capability_(NULL),
+        device_adaptor_(NULL),
+        modem_info_(NULL, &dispatcher_, NULL, NULL, NULL),
+        bearer_proxy_(new mm1::MockBearerProxy()),
+        modem_3gpp_proxy_(new mm1::MockModemModem3gppProxy()),
+        modem_cdma_proxy_(new mm1::MockModemModemCdmaProxy()),
+        modem_proxy_(new mm1::MockModemProxy()),
+        modem_simple_proxy_(new mm1::MockModemSimpleProxy()),
+        sim_proxy_(new mm1::MockSimProxy()),
+        properties_proxy_(new MockDBusPropertiesProxy()),
+        proxy_factory_(this),
+        cellular_(new Cellular(&modem_info_,
+                               "",
+                               kMachineAddress,
+                               0,
+                               Cellular::kTypeUniversalCDMA,
+                               "",
+                               "",
+                               "",
+                               &proxy_factory_)),
+        service_(new MockCellularService(&modem_info_,
+                                         cellular_)) {}
+
+  virtual ~CellularCapabilityUniversalCDMATest() {
+    cellular_->service_ = NULL;
+    capability_ = NULL;
+    device_adaptor_ = NULL;
+  }
+
+  virtual void SetUp() {
+    capability_ = dynamic_cast<CellularCapabilityUniversalCDMA *>(
+        cellular_->capability_.get());
+    device_adaptor_ =
+        dynamic_cast<NiceMock<DeviceMockAdaptor> *>(cellular_->adaptor());
+    cellular_->service_ = service_;
+  }
+
+  virtual void TearDown() {
+    capability_->proxy_factory_ = NULL;
+  }
+
+  void SetService() {
+    cellular_->service_ = new CellularService(&modem_info_, cellular_);
+  }
+
+  void ReleaseCapabilityProxies() {
+    capability_->ReleaseProxies();
+  }
+
+  void SetCdmaProxy() {
+    capability_->modem_cdma_proxy_.reset(modem_cdma_proxy_.release());
+  }
+
+  void SetSimpleProxy() {
+    capability_->modem_simple_proxy_.reset(modem_simple_proxy_.release());
+  }
+
+ protected:
+  static const char kEsn[];
+  static const char kMachineAddress[];
+  static const char kMeid[];
+
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(CellularCapabilityUniversalCDMATest *test) :
+        test_(test) {}
+
+    // TODO(armansito): Some of these methods won't be necessary after 3GPP
+    // gets refactored out of CellularCapabilityUniversal.
+    virtual mm1::BearerProxyInterface *CreateBearerProxy(
+        const std::string &path,
+        const std::string &/*service*/) {
+      return test_->bearer_proxy_.release();
+    }
+
+    virtual mm1::ModemModem3gppProxyInterface *CreateMM1ModemModem3gppProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->modem_3gpp_proxy_.release();
+    }
+
+    virtual mm1::ModemModemCdmaProxyInterface *CreateMM1ModemModemCdmaProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->modem_cdma_proxy_.release();
+    }
+
+    virtual mm1::ModemProxyInterface *CreateMM1ModemProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->modem_proxy_.release();
+    }
+
+    virtual mm1::ModemSimpleProxyInterface *CreateMM1ModemSimpleProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->modem_simple_proxy_.release();
+    }
+
+    virtual mm1::SimProxyInterface *CreateSimProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->sim_proxy_.release();
+    }
+
+    virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
+        const std::string &/*path*/,
+        const std::string &/*service*/) {
+      return test_->properties_proxy_.release();
+    }
+
+   private:
+    CellularCapabilityUniversalCDMATest *test_;
+  };
+
+
+  CellularCapabilityUniversalCDMA *capability_;
+  NiceMock<DeviceMockAdaptor> *device_adaptor_;
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  MockGLib glib_;
+  scoped_ptr<mm1::MockBearerProxy> bearer_proxy_;
+  // TODO(armansito): Remove |modem_3gpp_proxy_| after refactor.
+  scoped_ptr<mm1::MockModemModem3gppProxy> modem_3gpp_proxy_;
+  scoped_ptr<mm1::MockModemModemCdmaProxy> modem_cdma_proxy_;
+  scoped_ptr<mm1::MockModemProxy> modem_proxy_;
+  scoped_ptr<mm1::MockModemSimpleProxy> modem_simple_proxy_;
+  scoped_ptr<mm1::MockSimProxy> sim_proxy_;
+  scoped_ptr<MockDBusPropertiesProxy> properties_proxy_;
+  TestProxyFactory proxy_factory_;
+  CellularRefPtr cellular_;
+  MockCellularService *service_;
+};
+
+// static
+const char CellularCapabilityUniversalCDMATest::kEsn[] = "0000";
+// static
+const char CellularCapabilityUniversalCDMATest::kMachineAddress[] =
+    "TestMachineAddress";
+// static
+const char CellularCapabilityUniversalCDMATest::kMeid[] = "11111111111111";
+
+TEST_F(CellularCapabilityUniversalCDMATest, PropertiesChanged) {
+  // Set up mock modem CDMA properties.
+  DBusPropertiesMap modem_cdma_properties;
+  modem_cdma_properties[MM_MODEM_MODEMCDMA_PROPERTY_MEID].
+      writer().append_string(kMeid);
+  modem_cdma_properties[MM_MODEM_MODEMCDMA_PROPERTY_ESN].
+      writer().append_string(kEsn);
+
+  SetUp();
+
+  EXPECT_TRUE(capability_->meid().empty());
+  EXPECT_TRUE(capability_->esn().empty());
+
+  // Changing properties on wrong interface will not have an effect
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_cdma_properties,
+                                       vector<string>());
+  EXPECT_TRUE(capability_->meid().empty());
+  EXPECT_TRUE(capability_->esn().empty());
+
+  // Changing properties on the right interface gets reflected in the
+  // capabilities object
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM_MODEMCDMA,
+                                       modem_cdma_properties,
+                                       vector<string>());
+  EXPECT_EQ(kMeid, capability_->meid());
+  EXPECT_EQ(kEsn, capability_->esn());
+}
+
+TEST_F(CellularCapabilityUniversalCDMATest, OnCDMARegistrationChanged) {
+  EXPECT_EQ(0, capability_->sid_);
+  EXPECT_EQ(0, capability_->nid_);
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+            capability_->cdma_1x_registration_state_);
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+            capability_->cdma_evdo_registration_state_);
+
+  EXPECT_EQ("", capability_->provider_.GetCode());
+  EXPECT_EQ("", capability_->provider_.GetName());
+  EXPECT_EQ("", capability_->provider_.GetCountry());
+
+  CellularOperatorInfo::CellularOperator *provider =
+      new CellularOperatorInfo::CellularOperator();
+
+  provider->country_ = "us";
+  provider->is_primary_ = true;
+  provider->name_list_.push_back(
+      CellularOperatorInfo::LocalizedName("Test", ""));
+
+  scoped_ptr<const CellularOperatorInfo::CellularOperator> ptr(provider);
+
+  EXPECT_CALL(*modem_info_.mock_cellular_operator_info(),
+              GetCellularOperatorBySID("2"))
+      .WillOnce(Return(ptr.get()));
+
+  capability_->OnCDMARegistrationChanged(
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+      2,
+      0);
+  EXPECT_EQ(2, capability_->sid_);
+  EXPECT_EQ(0, capability_->nid_);
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+            capability_->cdma_1x_registration_state_);
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+            capability_->cdma_evdo_registration_state_);
+
+  EXPECT_TRUE(capability_->IsRegistered());
+  EXPECT_EQ("2", capability_->provider_.GetCode());
+  EXPECT_EQ("Test", capability_->provider_.GetName());
+  EXPECT_EQ("us", capability_->provider_.GetCountry());
+}
+
+TEST_F(CellularCapabilityUniversalCDMATest, UpdateOperatorInfo) {
+  EXPECT_EQ("", capability_->provider_.GetCode());
+  EXPECT_EQ("", capability_->provider_.GetName());
+  EXPECT_EQ("", capability_->provider_.GetCountry());
+
+  capability_->UpdateOperatorInfo();
+  EXPECT_EQ("", capability_->provider_.GetCode());
+  EXPECT_EQ("", capability_->provider_.GetName());
+  EXPECT_EQ("", capability_->provider_.GetCountry());
+
+
+  capability_->UpdateOperatorInfo();
+  EXPECT_EQ("", capability_->provider_.GetCode());
+  EXPECT_EQ("", capability_->provider_.GetName());
+  EXPECT_EQ("", capability_->provider_.GetCountry());
+
+  capability_->sid_ = 1;
+  EXPECT_CALL(*modem_info_.mock_cellular_operator_info(),
+              GetCellularOperatorBySID(_))
+      .WillOnce(Return((const CellularOperatorInfo::CellularOperator *)NULL));
+
+  capability_->UpdateOperatorInfo();
+  EXPECT_EQ("", capability_->provider_.GetCode());
+  EXPECT_EQ("", capability_->provider_.GetName());
+  EXPECT_EQ("", capability_->provider_.GetCountry());
+
+  CellularOperatorInfo::CellularOperator *provider =
+      new CellularOperatorInfo::CellularOperator();
+
+  provider->country_ = "us";
+  provider->is_primary_ = true;
+  provider->name_list_.push_back(
+      CellularOperatorInfo::LocalizedName("Test", ""));
+
+  scoped_ptr<const CellularOperatorInfo::CellularOperator> ptr(provider);
+
+  EXPECT_CALL(*modem_info_.mock_cellular_operator_info(),
+              GetCellularOperatorBySID(_))
+      .WillOnce(Return(ptr.get()));
+
+  capability_->UpdateOperatorInfo();
+
+  EXPECT_EQ("1", capability_->provider_.GetCode());
+  EXPECT_EQ("Test", capability_->provider_.GetName());
+  EXPECT_EQ("us", capability_->provider_.GetCountry());
+}
+
+TEST_F(CellularCapabilityUniversalCDMATest, CreateFriendlyServiceName) {
+  CellularCapabilityUniversalCDMA::friendly_service_name_id_cdma_ = 0;
+  EXPECT_EQ(0, capability_->sid_);
+  EXPECT_EQ("CDMANetwork0", capability_->CreateFriendlyServiceName());
+  EXPECT_EQ("CDMANetwork1", capability_->CreateFriendlyServiceName());
+
+  capability_->provider_.SetCode("0123");
+  EXPECT_EQ("cellular_sid_0123", capability_->CreateFriendlyServiceName());
+
+  CellularOperatorInfo::CellularOperator *provider =
+      new CellularOperatorInfo::CellularOperator();
+
+  capability_->sid_ = 1;
+  provider->name_list_.push_back(
+      CellularOperatorInfo::LocalizedName("Test", ""));
+  capability_->provider_.SetCode("");
+
+  scoped_ptr<const CellularOperatorInfo::CellularOperator> ptr(provider);
+
+  EXPECT_CALL(*modem_info_.mock_cellular_operator_info(),
+              GetCellularOperatorBySID(_))
+      .WillOnce(Return(ptr.get()));
+  EXPECT_EQ("Test", capability_->CreateFriendlyServiceName());
+}
+
+TEST_F(CellularCapabilityUniversalCDMATest, UpdateOLP) {
+  CellularService::OLP test_olp;
+  test_olp.SetURL("http://testurl");
+  test_olp.SetMethod("POST");
+  test_olp.SetPostData("esn=${esn}&mdn=${mdn}&meid=${meid}");
+
+  capability_->esn_ = "0";
+  capability_->mdn_ = "3";
+  capability_->meid_= "4";
+  capability_->sid_ = 1;
+
+  EXPECT_CALL(*modem_info_.mock_cellular_operator_info(),
+      GetOLPBySID(base::StringPrintf("%u", capability_->sid_)))
+      .WillRepeatedly(Return(&test_olp));
+
+  SetService();
+  capability_->UpdateOLP();
+  const CellularService::OLP &olp = cellular_->service()->olp();
+  EXPECT_EQ("http://testurl", olp.GetURL());
+  EXPECT_EQ("POST", olp.GetMethod());
+  EXPECT_EQ("esn=0&mdn=3&meid=4", olp.GetPostData());
+}
+
+}  // namespace shill
diff --git a/cellular_capability_universal_unittest.cc b/cellular_capability_universal_unittest.cc
index 9c55e37..e2deffb 100644
--- a/cellular_capability_universal_unittest.cc
+++ b/cellular_capability_universal_unittest.cc
@@ -1420,14 +1420,12 @@
   CellularService::OLP test_olp;
   test_olp.SetURL("http://testurl");
   test_olp.SetMethod("POST");
-  test_olp.SetPostData("esn=${esn}&imei=${imei}&imsi=${imsi}&mdn=${mdn}&"
-                       "meid=${meid}&min=${min}&iccid=${iccid}");
+  test_olp.SetPostData("imei=${imei}&imsi=${imsi}&mdn=${mdn}&"
+                       "min=${min}&iccid=${iccid}");
 
-  capability_->esn_ = "0";
   capability_->imei_ = "1";
   capability_->imsi_ = "2";
   capability_->mdn_ = "3";
-  capability_->meid_= "4";
   capability_->min_ = "5";
   capability_->sim_identifier_ = "6";
   capability_->operator_id_ = "123456";
@@ -1441,7 +1439,7 @@
   const CellularService::OLP &olp = cellular_->service()->olp();
   EXPECT_EQ("http://testurl", olp.GetURL());
   EXPECT_EQ("POST", olp.GetMethod());
-  EXPECT_EQ("esn=0&imei=1&imsi=2&mdn=3&meid=4&min=5&iccid=6",
+  EXPECT_EQ("imei=1&imsi=2&mdn=3&min=5&iccid=6",
             olp.GetPostData());
 }
 
diff --git a/cellular_operator_info.cc b/cellular_operator_info.cc
index 37807d4..d701d04 100644
--- a/cellular_operator_info.cc
+++ b/cellular_operator_info.cc
@@ -524,6 +524,12 @@
   DISALLOW_COPY_AND_ASSIGN(CellularOperatorInfoImpl);
 };
 
+COI::LocalizedName::LocalizedName() {}
+COI::LocalizedName::LocalizedName(string name,
+                                  string language)
+    : name(name),
+      language(language) {}
+
 CellularOperatorInfo::CellularOperator::CellularOperator()
     : is_primary_(false),
       requires_roaming_(false) {}
diff --git a/cellular_operator_info.h b/cellular_operator_info.h
index b5185aa..54bc678 100644
--- a/cellular_operator_info.h
+++ b/cellular_operator_info.h
@@ -36,6 +36,9 @@
   // The name can be a carrier name, or the name that a cellular carrier
   // prefers to show for a certain access point.
   struct LocalizedName {
+    LocalizedName();
+    LocalizedName(std::string name, std::string language);
+
     // The name as it appears in the corresponding language.
     std::string name;
 
@@ -124,7 +127,11 @@
    private:
     friend class CellularOperatorInfo;
     friend class CellularOperatorInfoImpl;
+    friend class CellularCapabilityUniversalCDMATest;
     FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateStorageIdentifier);
+    FRIEND_TEST(CellularCapabilityUniversalCDMATest, CreateFriendlyServiceName);
+    FRIEND_TEST(CellularCapabilityUniversalCDMATest, OnCDMARegistrationChanged);
+    FRIEND_TEST(CellularCapabilityUniversalCDMATest, UpdateOperatorInfo);
 
     std::string country_;
     std::string identifier_;
diff --git a/mock_cellular_operator_info.h b/mock_cellular_operator_info.h
index eafbb5a..0035dfe 100644
--- a/mock_cellular_operator_info.h
+++ b/mock_cellular_operator_info.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -23,6 +23,10 @@
                      const CellularService::OLP *(const std::string &mccmnc));
   MOCK_CONST_METHOD1(GetCellularOperatorByMCCMNC,
                      const CellularOperator *(const std::string &mccmnc));
+  MOCK_CONST_METHOD1(GetOLPBySID,
+                     const CellularService::OLP *(const std::string &sid));
+  MOCK_CONST_METHOD1(GetCellularOperatorBySID,
+                     const CellularOperator *(const std::string &sid));
 };
 
 }  // namespace shill
diff --git a/modem_1.cc b/modem_1.cc
index 67934e6..783e09d 100644
--- a/modem_1.cc
+++ b/modem_1.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -79,11 +79,27 @@
 
 void Modem1::CreateDeviceMM1(const DBusInterfaceToProperties &properties) {
   Init();
-  if (!ContainsKey(properties, MM_DBUS_INTERFACE_MODEM)) {
+  uint32 capabilities = kuint32max;
+  DBusInterfaceToProperties::const_iterator it =
+      properties.find(MM_DBUS_INTERFACE_MODEM);
+  if (it == properties.end()) {
     LOG(ERROR) << "Cellular device with no modem properties";
     return;
   }
-  set_type(Cellular::kTypeUniversal);
+  const DBusPropertiesMap &modem_props = it->second;
+  DBusProperties::GetUint32(modem_props,
+                            MM_MODEM_PROPERTY_CURRENTCAPABILITIES,
+                            &capabilities);
+
+  if ((capabilities & MM_MODEM_CAPABILITY_LTE) ||
+      (capabilities & MM_MODEM_CAPABILITY_GSM_UMTS)) {
+    set_type(Cellular::kTypeUniversal);
+  } else if (capabilities & MM_MODEM_CAPABILITY_CDMA_EVDO) {
+    set_type(Cellular::kTypeUniversalCDMA);
+  } else {
+    LOG(ERROR) << "Unsupported capabilities: " << capabilities;
+    return;
+  }
 
   // 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.