shill: cellular: Move cellular code to its own sub-directory.

This CL is part of a series of CLs, which aim to reorganzie shill's flat
source code structure into a more modular form by moving technology
specific code into their own sub-directories.

BUG=chromium:433419
TEST=`USE='cellular' FEATURES=test emerge-$BOARD shill`
TEST=`USE='cellular clang asan' FEATURES=test emerge-$BOARD shill`

Change-Id: I783e85d8c606426ce2ded093588c1243fd0eef97
Reviewed-on: https://chromium-review.googlesource.com/229799
Reviewed-by: Thieu Le <thieule@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/cellular/active_passive_out_of_credits_detector.cc b/cellular/active_passive_out_of_credits_detector.cc
new file mode 100644
index 0000000..4a92efc
--- /dev/null
+++ b/cellular/active_passive_out_of_credits_detector.cc
@@ -0,0 +1,281 @@
+// 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/active_passive_out_of_credits_detector.h"
+
+#include <string>
+
+#include "shill/cellular/cellular_service.h"
+#include "shill/connection.h"
+#include "shill/connection_health_checker.h"
+#include "shill/logging.h"
+#include "shill/manager.h"
+#include "shill/traffic_monitor.h"
+
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(ActivePassiveOutOfCreditsDetector *a) {
+  return a->GetServiceRpcIdentifier();
+}
+}
+
+// static
+const int64_t
+    ActivePassiveOutOfCreditsDetector::kOutOfCreditsConnectionDropSeconds = 15;
+const int
+    ActivePassiveOutOfCreditsDetector::kOutOfCreditsMaxConnectAttempts = 3;
+const int64_t
+    ActivePassiveOutOfCreditsDetector::kOutOfCreditsResumeIgnoreSeconds = 5;
+
+ActivePassiveOutOfCreditsDetector::ActivePassiveOutOfCreditsDetector(
+    EventDispatcher *dispatcher,
+    Manager *manager,
+    Metrics *metrics,
+    CellularService *service)
+    : OutOfCreditsDetector(dispatcher, manager, metrics, service),
+      weak_ptr_factory_(this),
+      traffic_monitor_(
+          new TrafficMonitor(service->cellular(), dispatcher)),
+      service_rpc_identifier_(service->GetRpcIdentifier()) {
+  ResetDetector();
+  traffic_monitor_->set_network_problem_detected_callback(
+      Bind(&ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting,
+           weak_ptr_factory_.GetWeakPtr()));
+}
+
+ActivePassiveOutOfCreditsDetector::~ActivePassiveOutOfCreditsDetector() {
+  StopTrafficMonitor();
+}
+
+void ActivePassiveOutOfCreditsDetector::ResetDetector() {
+  SLOG(this, 2) << "Reset out-of-credits detection";
+  out_of_credits_detection_in_progress_ = false;
+  num_connect_attempts_ = 0;
+}
+
+bool ActivePassiveOutOfCreditsDetector::IsDetecting() const {
+  return out_of_credits_detection_in_progress_;
+}
+
+void ActivePassiveOutOfCreditsDetector::NotifyServiceStateChanged(
+    Service::ConnectState old_state, Service::ConnectState new_state) {
+  SLOG(this, 2) << __func__ << ": " << old_state << " -> " << new_state;
+  switch (new_state) {
+    case Service::kStateUnknown:
+    case Service::kStateIdle:
+    case Service::kStateFailure:
+      StopTrafficMonitor();
+      health_checker_.reset();
+      break;
+    case Service::kStateAssociating:
+      if (num_connect_attempts_ == 0)
+        ReportOutOfCredits(false);
+      if (old_state != Service::kStateAssociating) {
+        connect_start_time_ = base::Time::Now();
+        num_connect_attempts_++;
+        SLOG(this, 2) << __func__
+                      << ": num_connect_attempts="
+                      << num_connect_attempts_;
+      }
+      break;
+    case Service::kStateConnected:
+      StartTrafficMonitor();
+      SetupConnectionHealthChecker();
+      break;
+    case Service::kStatePortal:
+      SLOG(this, 2) << "Portal detection failed. Launching active probe "
+                    << "for out-of-credit detection.";
+      RequestConnectionHealthCheck();
+      break;
+    case Service::kStateConfiguring:
+    case Service::kStateOnline:
+      break;
+  }
+  DetectConnectDisconnectLoop(old_state, new_state);
+}
+
+bool ActivePassiveOutOfCreditsDetector::StartTrafficMonitor() {
+  SLOG(this, 2) << __func__;
+  SLOG(this, 2) << "Service " << service()->friendly_name()
+                << ": Traffic Monitor starting.";
+  traffic_monitor_->Start();
+  return true;
+}
+
+void ActivePassiveOutOfCreditsDetector::StopTrafficMonitor() {
+  SLOG(this, 2) << __func__;
+  SLOG(this, 2) << "Service " << service()->friendly_name()
+                << ": Traffic Monitor stopping.";
+  traffic_monitor_->Stop();
+}
+
+void ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting(int reason) {
+  SLOG(this, 2) << "Service " << service()->friendly_name()
+                << ": Traffic Monitor detected network congestion.";
+  SLOG(this, 2) << "Requesting active probe for out-of-credit detection.";
+  RequestConnectionHealthCheck();
+}
+
+void ActivePassiveOutOfCreditsDetector::SetupConnectionHealthChecker() {
+  DCHECK(service()->connection());
+  // TODO(thieule): Consider moving health_checker_remote_ips() out of manager
+  // (crbug.com/304974).
+  if (!health_checker_.get()) {
+    health_checker_.reset(
+        new ConnectionHealthChecker(
+            service()->connection(),
+            dispatcher(),
+            manager()->health_checker_remote_ips(),
+            Bind(&ActivePassiveOutOfCreditsDetector::
+                 OnConnectionHealthCheckerResult,
+                 weak_ptr_factory_.GetWeakPtr())));
+  } else {
+    health_checker_->SetConnection(service()->connection());
+  }
+  // Add URL in either case because a connection reset could have dropped past
+  // DNS queries.
+  health_checker_->AddRemoteURL(manager()->GetPortalCheckURL());
+}
+
+void ActivePassiveOutOfCreditsDetector::RequestConnectionHealthCheck() {
+  if (!health_checker_.get()) {
+    SLOG(this, 2) << "No health checker exists, cannot request "
+                  << "health check.";
+    return;
+  }
+  if (health_checker_->health_check_in_progress()) {
+    SLOG(this, 2) << "Health check already in progress.";
+    return;
+  }
+  health_checker_->Start();
+}
+
+void ActivePassiveOutOfCreditsDetector::OnConnectionHealthCheckerResult(
+    ConnectionHealthChecker::Result result) {
+  SLOG(this, 2) << __func__ << "(Result = "
+                << ConnectionHealthChecker::ResultToString(result) << ")";
+
+  if (result == ConnectionHealthChecker::kResultCongestedTxQueue) {
+    LOG(WARNING) << "Active probe determined possible out-of-credits "
+                 << "scenario.";
+    if (service()) {
+      Metrics::CellularOutOfCreditsReason reason =
+          (result == ConnectionHealthChecker::kResultCongestedTxQueue) ?
+              Metrics::kCellularOutOfCreditsReasonTxCongested :
+              Metrics::kCellularOutOfCreditsReasonElongatedTimeWait;
+      metrics()->NotifyCellularOutOfCredits(reason);
+
+      ReportOutOfCredits(true);
+      SLOG(this, 2) << "Disconnecting due to out-of-credit scenario.";
+      Error error;
+      service()->Disconnect(&error, "out-of-credits");
+    }
+  }
+}
+
+void ActivePassiveOutOfCreditsDetector::DetectConnectDisconnectLoop(
+    Service::ConnectState curr_state, Service::ConnectState new_state) {
+  // WORKAROUND:
+  // Some modems on Verizon network do not properly redirect when a SIM
+  // runs out of credits. This workaround is used to detect an out-of-credits
+  // condition by retrying a connect request if it was dropped within
+  // kOutOfCreditsConnectionDropSeconds. If the number of retries exceeds
+  // kOutOfCreditsMaxConnectAttempts, then the SIM is considered
+  // out-of-credits and the cellular service kOutOfCreditsProperty is set.
+  // This will signal Chrome to display the appropriate UX and also suppress
+  // auto-connect until the next time the user manually connects.
+  //
+  // TODO(thieule): Remove this workaround (crosbug.com/p/18169).
+  if (out_of_credits()) {
+    SLOG(this, 2) << __func__
+                  << ": Already out-of-credits, skipping check";
+    return;
+  }
+  base::TimeDelta
+      time_since_resume = base::Time::Now() - service()->resume_start_time();
+  if (time_since_resume.InSeconds() < kOutOfCreditsResumeIgnoreSeconds) {
+    // On platforms that power down the modem during suspend, make sure that
+    // we do not display a false out-of-credits warning to the user
+    // due to the sequence below by skipping out-of-credits detection
+    // immediately after a resume.
+    //   1. User suspends Chromebook.
+    //   2. Hardware turns off power to modem.
+    //   3. User resumes Chromebook.
+    //   4. Hardware restores power to modem.
+    //   5. ModemManager still has instance of old modem.
+    //      ModemManager does not delete this instance until udev fires a
+    //      device removed event.  ModemManager does not detect new modem
+    //      until udev fires a new device event.
+    //   6. Shill performs auto-connect against the old modem.
+    //      Make sure at this step that we do not display a false
+    //      out-of-credits warning.
+    //   7. Udev fires device removed event.
+    //   8. Udev fires new device event.
+    SLOG(this, 2) <<
+        "Skipping out-of-credits detection, too soon since resume.";
+    ResetDetector();
+    return;
+  }
+  base::TimeDelta
+      time_since_connect = base::Time::Now() - connect_start_time_;
+  if (time_since_connect.InSeconds() > kOutOfCreditsConnectionDropSeconds) {
+    ResetDetector();
+    return;
+  }
+  // Verizon can drop the connection in two ways:
+  //   - Denies the connect request
+  //   - Allows connect request but disconnects later
+  bool connection_dropped =
+      (Service::IsConnectedState(curr_state) ||
+       Service::IsConnectingState(curr_state)) &&
+      (new_state == Service::kStateFailure ||
+       new_state == Service::kStateIdle);
+  if (!connection_dropped)
+    return;
+  if (service()->explicitly_disconnected())
+    return;
+  if (service()->roaming_state() == kRoamingStateRoaming &&
+      !service()->cellular()->allow_roaming_property())
+    return;
+  if (time_since_connect.InSeconds() <= kOutOfCreditsConnectionDropSeconds) {
+    if (num_connect_attempts_ < kOutOfCreditsMaxConnectAttempts) {
+      SLOG(this, 2) << "Out-Of-Credits detection: Reconnecting "
+                    << "(retry #" << num_connect_attempts_ << ")";
+      // Prevent autoconnect logic from kicking in while we perform the
+      // out-of-credits detection.
+      out_of_credits_detection_in_progress_ = true;
+      dispatcher()->PostTask(
+          Bind(&ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect,
+               weak_ptr_factory_.GetWeakPtr()));
+    } else {
+      LOG(INFO) << "Active/Passive Out-Of-Credits detection: "
+                << "Marking service as out-of-credits";
+      metrics()->NotifyCellularOutOfCredits(
+          Metrics::kCellularOutOfCreditsReasonConnectDisconnectLoop);
+      ReportOutOfCredits(true);
+      ResetDetector();
+    }
+  }
+}
+
+void ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect() {
+  Error error;
+  service()->Connect(&error, __func__);
+}
+
+void ActivePassiveOutOfCreditsDetector::set_traffic_monitor(
+    TrafficMonitor *traffic_monitor) {
+  traffic_monitor_.reset(traffic_monitor);
+}
+
+void ActivePassiveOutOfCreditsDetector::set_connection_health_checker(
+    ConnectionHealthChecker *health_checker) {
+  health_checker_.reset(health_checker);
+}
+
+}  // namespace shill
diff --git a/cellular/active_passive_out_of_credits_detector.h b/cellular/active_passive_out_of_credits_detector.h
new file mode 100644
index 0000000..53b74c3
--- /dev/null
+++ b/cellular/active_passive_out_of_credits_detector.h
@@ -0,0 +1,117 @@
+// 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_ACTIVE_PASSIVE_OUT_OF_CREDITS_DETECTOR_H_
+#define SHILL_CELLULAR_ACTIVE_PASSIVE_OUT_OF_CREDITS_DETECTOR_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "shill/cellular/out_of_credits_detector.h"
+#include "shill/connection_health_checker.h"
+
+namespace shill {
+
+// Detects out-of-credits condition by monitoring for the following scenarios:
+//   - Passively watch for network congestion and launch active probes to
+//     determine if the network has stopped routing traffic.
+//   - Watch for connect/disconnect loop.
+class ActivePassiveOutOfCreditsDetector : public OutOfCreditsDetector {
+ public:
+  ActivePassiveOutOfCreditsDetector(EventDispatcher *dispatcher,
+                                    Manager *manager,
+                                    Metrics *metrics,
+                                    CellularService *service);
+  ~ActivePassiveOutOfCreditsDetector() override;
+
+  void ResetDetector() override;
+  bool IsDetecting() const override;
+  void NotifyServiceStateChanged(
+      Service::ConnectState old_state,
+      Service::ConnectState new_state) override;
+  void NotifySubscriptionStateChanged(uint32_t subscription_state) override {}
+
+  const TrafficMonitor *traffic_monitor() const {
+    return traffic_monitor_.get();
+  }
+
+  const std::string &GetServiceRpcIdentifier() const {
+    return service_rpc_identifier_;
+  }
+
+ private:
+  friend class ActivePassiveOutOfCreditsDetectorTest;
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest,
+      ConnectDisconnectLoopDetectionIntermittentNetwork);
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest,
+      ConnectDisconnectLoopDetectionNotSkippedAfterSlowResume);
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest,
+      OnConnectionHealthCheckerResult);
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest, OnNoNetworkRouting);
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest, StopTrafficMonitor);
+
+  static const int64_t kOutOfCreditsConnectionDropSeconds;
+  static const int kOutOfCreditsMaxConnectAttempts;
+  static const int64_t kOutOfCreditsResumeIgnoreSeconds;
+
+  // Initiates traffic monitoring.
+  bool StartTrafficMonitor();
+
+  // Stops traffic monitoring.
+  void StopTrafficMonitor();
+
+  // Responds to a TrafficMonitor no-network-routing failure.
+  void OnNoNetworkRouting(int reason);
+
+  // Initializes and configures the connection health checker.
+  void SetupConnectionHealthChecker();
+
+  // Checks the network connectivity status by creating a TCP connection, and
+  // optionally sending a small amout of data.
+  void RequestConnectionHealthCheck();
+
+  // Responds to the result from connection health checker in a device specific
+  // manner.
+  void OnConnectionHealthCheckerResult(ConnectionHealthChecker::Result result);
+
+  // Performs out-of-credits detection by checking to see if we're stuck in a
+  // connect/disconnect loop.
+  void DetectConnectDisconnectLoop(Service::ConnectState curr_state,
+                                   Service::ConnectState new_state);
+  // Reconnects to the cellular service in the context of out-of-credits
+  // detection.
+  void OutOfCreditsReconnect();
+
+  // Ownership of |traffic_monitor| is taken.
+  void set_traffic_monitor(TrafficMonitor *traffic_monitor);
+
+  // Ownership of |healther_checker| is taken.
+  void set_connection_health_checker(ConnectionHealthChecker *health_checker);
+
+  base::WeakPtrFactory<ActivePassiveOutOfCreditsDetector> weak_ptr_factory_;
+
+  // Passively monitors network traffic for network failures.
+  std::unique_ptr<TrafficMonitor> traffic_monitor_;
+  // Determine network health through active probes.
+  std::unique_ptr<ConnectionHealthChecker> health_checker_;
+
+  // The following members are used by the connect/disconnect loop detection.
+  // Time when the last connect request started.
+  base::Time connect_start_time_;
+  // Number of connect attempts.
+  int num_connect_attempts_;
+  // Flag indicating whether out-of-credits detection is in progress.
+  bool out_of_credits_detection_in_progress_;
+
+  // String to hold service identifier for scoped logging.
+  std::string service_rpc_identifier_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActivePassiveOutOfCreditsDetector);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_ACTIVE_PASSIVE_OUT_OF_CREDITS_DETECTOR_H_
diff --git a/cellular/active_passive_out_of_credits_detector_unittest.cc b/cellular/active_passive_out_of_credits_detector_unittest.cc
new file mode 100644
index 0000000..1f95b14
--- /dev/null
+++ b/cellular/active_passive_out_of_credits_detector_unittest.cc
@@ -0,0 +1,320 @@
+// 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/active_passive_out_of_credits_detector.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_cellular_service.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_connection.h"
+#include "shill/mock_connection_health_checker.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_proxy_factory.h"
+#include "shill/mock_traffic_monitor.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::ReturnRef;
+using testing::StrictMock;
+
+namespace shill {
+
+class ActivePassiveOutOfCreditsDetectorTest : public testing::Test {
+ public:
+  ActivePassiveOutOfCreditsDetectorTest()
+      : modem_info_(nullptr, &dispatcher_, &metrics_, &manager_, nullptr),
+        device_info_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                     modem_info_.metrics(), modem_info_.manager()),
+        manager_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                 modem_info_.metrics(), modem_info_.glib()),
+        metrics_(modem_info_.dispatcher()),
+        cellular_(new NiceMock<MockCellular>(&modem_info_,
+                                             "usb0",
+                                             kAddress,
+                                             3,
+                                             Cellular::kTypeCDMA,
+                                             "",
+                                             "",
+                                             "",
+                                             ProxyFactory::GetInstance())),
+        service_(new NiceMock<MockCellularService>(&modem_info_, cellular_)),
+        connection_(new NiceMock<MockConnection>(&device_info_)),
+        out_of_credits_detector_(
+            new ActivePassiveOutOfCreditsDetector(
+                modem_info_.dispatcher(), modem_info_.manager(),
+                modem_info_.metrics(), service_)) {}
+
+  virtual void SetUp() {
+    service_->connection_ = connection_;
+    cellular_->service_ = service_;
+    service_->SetRoamingState(kRoamingStateHome);
+    ON_CALL(*connection_, interface_name())
+        .WillByDefault(ReturnRef(interface_name_));
+    ON_CALL(*connection_, dns_servers())
+        .WillByDefault(ReturnRef(dns_servers_));
+    ON_CALL(manager_, GetPortalCheckURL())
+        .WillByDefault(ReturnRef(portal_check_url_));
+    ON_CALL(*service_, explicitly_disconnected()).WillByDefault(Return(false));
+    ON_CALL(*service_, resume_start_time())
+        .WillByDefault(ReturnRef(resume_start_time_));
+  }
+
+  virtual void TearDown() {
+    cellular_->service_ = nullptr;  // Break circular reference.
+  }
+
+  void OnConnectionHealthCheckerResult(
+      ConnectionHealthChecker::Result result) {}
+
+ protected:
+  static const char kAddress[];
+
+  void SetMockServiceState(Service::ConnectState old_state,
+                           Service::ConnectState new_state) {
+    out_of_credits_detector_->NotifyServiceStateChanged(old_state, new_state);
+  }
+
+  void SetTrafficMonitor(TrafficMonitor *traffic_monitor) {
+    out_of_credits_detector_->set_traffic_monitor(traffic_monitor);
+  }
+
+  void SetConnectionHealthChecker(ConnectionHealthChecker *health_checker) {
+    out_of_credits_detector_->set_connection_health_checker(health_checker);
+  }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  NiceMock<MockDeviceInfo> device_info_;
+  NiceMock<MockManager> manager_;
+  NiceMock<MockMetrics> metrics_;
+  scoped_refptr<NiceMock<MockCellular>> cellular_;
+  scoped_refptr<NiceMock<MockCellularService>> service_;
+  scoped_refptr<NiceMock<MockConnection>> connection_;
+  string interface_name_;
+  vector<string> dns_servers_;
+  string portal_check_url_;
+  base::Time resume_start_time_;
+  std::unique_ptr<ActivePassiveOutOfCreditsDetector> out_of_credits_detector_;
+};
+
+const char ActivePassiveOutOfCreditsDetectorTest::kAddress[] = "000102030405";
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopOutOfCreditsDetected) {
+  EXPECT_CALL(*service_, Connect(_, _)).Times(2);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateFailure);
+  EXPECT_TRUE(out_of_credits_detector_->IsDetecting());
+  dispatcher_.DispatchPendingEvents();
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConfiguring);
+  SetMockServiceState(Service::kStateConfiguring, Service::kStateIdle);
+  EXPECT_TRUE(out_of_credits_detector_->IsDetecting());
+  dispatcher_.DispatchPendingEvents();
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_TRUE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionNotSkippedAfterSlowResume) {
+  resume_start_time_ =
+      base::Time::Now() -
+      base::TimeDelta::FromSeconds(
+      ActivePassiveOutOfCreditsDetector::kOutOfCreditsResumeIgnoreSeconds + 1);
+  EXPECT_CALL(*service_, Connect(_, _)).Times(2);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateFailure);
+  EXPECT_TRUE(out_of_credits_detector_->IsDetecting());
+  dispatcher_.DispatchPendingEvents();
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConfiguring);
+  SetMockServiceState(Service::kStateConfiguring, Service::kStateIdle);
+  EXPECT_TRUE(out_of_credits_detector_->IsDetecting());
+  dispatcher_.DispatchPendingEvents();
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_TRUE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionSkippedAfterResume) {
+  resume_start_time_ = base::Time::Now();
+  ON_CALL(*service_, resume_start_time())
+      .WillByDefault(ReturnRef(resume_start_time_));
+  EXPECT_CALL(*service_, Connect(_, _)).Times(0);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+  // There should not be any pending connect requests but dispatch pending
+  // events anyway to be sure.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionSkippedAlreadyOutOfCredits) {
+  EXPECT_CALL(*service_, Connect(_, _)).Times(0);
+  out_of_credits_detector_->ReportOutOfCredits(true);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+  // There should not be any pending connect requests but dispatch pending
+  // events anyway to be sure.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionSkippedExplicitDisconnect) {
+  EXPECT_CALL(*service_, Connect(_, _)).Times(0);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  EXPECT_CALL(*service_, explicitly_disconnected()).WillOnce(Return(true));
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+  // There should not be any pending connect requests but dispatch pending
+  // events anyway to be sure.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionConnectionNotDropped) {
+  EXPECT_CALL(*service_, Connect(_, _)).Times(0);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConfiguring);
+  SetMockServiceState(Service::kStateConfiguring, Service::kStateConnected);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+  // There should not be any pending connect requests but dispatch pending
+  // events anyway to be sure.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    ConnectDisconnectLoopDetectionIntermittentNetwork) {
+  EXPECT_CALL(*service_, Connect(_, _)).Times(0);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  out_of_credits_detector_->connect_start_time_ =
+      base::Time::Now() -
+      base::TimeDelta::FromSeconds(
+          ActivePassiveOutOfCreditsDetector::
+          kOutOfCreditsConnectionDropSeconds + 1);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  EXPECT_FALSE(out_of_credits_detector_->IsDetecting());
+  // There should not be any pending connect requests but dispatch pending
+  // events anyway to be sure.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest, StartTrafficMonitor) {
+  MockTrafficMonitor *traffic_monitor = new StrictMock<MockTrafficMonitor>();
+  SetTrafficMonitor(traffic_monitor);  // Passes ownership.
+
+  // Traffic monitor should only start when the service is connected.
+  EXPECT_CALL(*traffic_monitor, Start()).Times(1);
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  Mock::VerifyAndClearExpectations(traffic_monitor);
+
+  // Traffic monitor should not start for other state transitions.
+  EXPECT_CALL(*traffic_monitor, Start()).Times(0);
+  EXPECT_CALL(*traffic_monitor, Stop()).Times(AnyNumber());
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  SetMockServiceState(Service::kStateIdle, Service::kStateConfiguring);
+  SetMockServiceState(Service::kStateConfiguring, Service::kStateFailure);
+  SetMockServiceState(Service::kStateIdle, Service::kStateAssociating);
+  SetMockServiceState(Service::kStateConfiguring, Service::kStatePortal);
+  SetMockServiceState(Service::kStatePortal, Service::kStateOnline);
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest, StopTrafficMonitor) {
+  // Traffic monitor should stop when the service is disconnected.
+  MockTrafficMonitor *traffic_monitor = new StrictMock<MockTrafficMonitor>();
+  SetTrafficMonitor(traffic_monitor);  // Passes ownership.
+  EXPECT_CALL(*traffic_monitor, Start());
+  EXPECT_CALL(*traffic_monitor, Stop());
+  SetMockServiceState(Service::kStateAssociating, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateIdle);
+  Mock::VerifyAndClearExpectations(traffic_monitor);
+
+  EXPECT_CALL(*traffic_monitor, Start());
+  EXPECT_CALL(*traffic_monitor, Stop());
+  SetMockServiceState(Service::kStateIdle, Service::kStateConnected);
+  SetMockServiceState(Service::kStateConnected, Service::kStateFailure);
+  Mock::VerifyAndClearExpectations(traffic_monitor);
+
+  // Need an additional call to Stop() because |traffic_monitor| destructor
+  // will call stop.
+  EXPECT_CALL(*traffic_monitor, Stop());
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest, OnNoNetworkRouting) {
+  // Make sure the connection health checker starts when there is no network
+  // routing.
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  MockConnectionHealthChecker *health_checker =
+      new MockConnectionHealthChecker(
+          service_->connection(),
+          modem_info_.dispatcher(),
+          manager_.health_checker_remote_ips(),
+          Bind(&ActivePassiveOutOfCreditsDetectorTest::
+               OnConnectionHealthCheckerResult,
+               Unretained(this)));
+  SetConnectionHealthChecker(health_checker);  // Passes ownership.
+  EXPECT_CALL(*health_checker, Start());
+  out_of_credits_detector_->OnNoNetworkRouting(0);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  Mock::VerifyAndClearExpectations(health_checker);
+
+  // Make sure connection health checker does not start again if there is a
+  // health check in progress.
+  EXPECT_CALL(*health_checker, health_check_in_progress())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*health_checker, Start()).Times(0);
+  out_of_credits_detector_->OnNoNetworkRouting(0);
+}
+
+TEST_F(ActivePassiveOutOfCreditsDetectorTest,
+    OnConnectionHealthCheckerResult) {
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  EXPECT_CALL(*service_, Disconnect(_, _)).Times(0);
+  out_of_credits_detector_->OnConnectionHealthCheckerResult(
+      ConnectionHealthChecker::kResultUnknown);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  out_of_credits_detector_->OnConnectionHealthCheckerResult(
+      ConnectionHealthChecker::kResultConnectionFailure);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+  Mock::VerifyAndClearExpectations(service_);
+
+  EXPECT_CALL(*service_, Disconnect(_,
+      ::testing::StrEq("out-of-credits"))).
+          Times(1);
+  out_of_credits_detector_->OnConnectionHealthCheckerResult(
+      ConnectionHealthChecker::kResultCongestedTxQueue);
+  EXPECT_TRUE(out_of_credits_detector_->out_of_credits());
+}
+
+}  // namespace shill
diff --git a/cellular/cellular.cc b/cellular/cellular.cc
new file mode 100644
index 0000000..bf78bc5
--- /dev/null
+++ b/cellular/cellular.cc
@@ -0,0 +1,1604 @@
+// 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/cellular.h"
+
+#include <netinet/in.h>
+#include <linux/if.h>  // NOLINT - Needs definitions from netinet/in.h
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_capability_cdma.h"
+#include "shill/cellular/cellular_capability_gsm.h"
+#include "shill/cellular/cellular_capability_universal.h"
+#include "shill/cellular/cellular_capability_universal_cdma.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mobile_operator_info.h"
+#include "shill/control_interface.h"
+#include "shill/device.h"
+#include "shill/device_info.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/external_task.h"
+#include "shill/logging.h"
+#include "shill/manager.h"
+#include "shill/net/rtnl_handler.h"
+#include "shill/ppp_device.h"
+#include "shill/ppp_device_factory.h"
+#include "shill/profile.h"
+#include "shill/property_accessor.h"
+#include "shill/proxy_factory.h"
+#include "shill/store_interface.h"
+#include "shill/technology.h"
+
+using base::Bind;
+using base::Closure;
+using base::FilePath;
+using base::StringPrintf;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(Cellular *c) { return c->GetRpcIdentifier(); }
+}
+
+// static
+const char Cellular::kAllowRoaming[] = "AllowRoaming";
+const int64_t Cellular::kDefaultScanningTimeoutMilliseconds = 60000;
+const char Cellular::kGenericServiceNamePrefix[] = "MobileNetwork";
+unsigned int Cellular::friendly_service_name_id_ = 1;
+
+Cellular::Cellular(ModemInfo *modem_info,
+                   const string &link_name,
+                   const string &address,
+                   int interface_index,
+                   Type type,
+                   const string &owner,
+                   const string &service,
+                   const string &path,
+                   ProxyFactory *proxy_factory)
+    : Device(modem_info->control_interface(),
+             modem_info->dispatcher(),
+             modem_info->metrics(),
+             modem_info->manager(),
+             link_name,
+             address,
+             interface_index,
+             Technology::kCellular),
+      weak_ptr_factory_(this),
+      state_(kStateDisabled),
+      modem_state_(kModemStateUnknown),
+      home_provider_info_(
+          new MobileOperatorInfo(modem_info->dispatcher(), "HomeProvider")),
+      serving_operator_info_(
+          new MobileOperatorInfo(modem_info->dispatcher(), "ServingOperator")),
+      mobile_operator_info_observer_(
+          new Cellular::MobileOperatorInfoObserver(this)),
+      dbus_owner_(owner),
+      dbus_service_(service),
+      dbus_path_(path),
+      scanning_supported_(false),
+      scanning_(false),
+      provider_requires_roaming_(false),
+      scan_interval_(0),
+      sim_present_(false),
+      prl_version_(0),
+      modem_info_(modem_info),
+      type_(type),
+      proxy_factory_(proxy_factory),
+      ppp_device_factory_(PPPDeviceFactory::GetInstance()),
+      allow_roaming_(false),
+      proposed_scan_in_progress_(false),
+      explicit_disconnect_(false),
+      is_ppp_authenticating_(false),
+      scanning_timeout_milliseconds_(kDefaultScanningTimeoutMilliseconds) {
+  RegisterProperties();
+  InitCapability(type);
+
+  // TODO(pprabhu) Split MobileOperatorInfo into a context that stores the
+  // costly database, and lighter objects that |Cellular| can own.
+  // crbug.com/363874
+  home_provider_info_->Init();
+  serving_operator_info_->Init();
+  home_provider_info()->AddObserver(mobile_operator_info_observer_.get());
+  serving_operator_info()->AddObserver(mobile_operator_info_observer_.get());
+
+  SLOG(this, 2) << "Cellular device " << this->link_name()
+                << " initialized.";
+}
+
+Cellular::~Cellular() {
+  // Under certain conditions, Cellular::StopModem may not be
+  // called before the Cellular device is destroyed. This happens if the dbus
+  // modem exported by the modem-manager daemon disappears soon after the modem
+  // is disabled, not giving shill enough time to complete the disable
+  // operation.
+  // In that case, the termination action associated with this cellular object
+  // may not have been removed.
+  manager()->RemoveTerminationAction(FriendlyName());
+
+  home_provider_info()->RemoveObserver(mobile_operator_info_observer_.get());
+  serving_operator_info()->RemoveObserver(
+      mobile_operator_info_observer_.get());
+  // Explicitly delete the observer to ensure that it is destroyed before the
+  // handle to |capability_| that it holds.
+  mobile_operator_info_observer_.reset();
+}
+
+bool Cellular::Load(StoreInterface *storage) {
+  const string id = GetStorageIdentifier();
+  if (!storage->ContainsGroup(id)) {
+    LOG(WARNING) << "Device is not available in the persistent store: " << id;
+    return false;
+  }
+  storage->GetBool(id, kAllowRoaming, &allow_roaming_);
+  return Device::Load(storage);
+}
+
+bool Cellular::Save(StoreInterface *storage) {
+  const string id = GetStorageIdentifier();
+  storage->SetBool(id, kAllowRoaming, allow_roaming_);
+  return Device::Save(storage);
+}
+
+// static
+string Cellular::GetStateString(State state) {
+  switch (state) {
+    case kStateDisabled:
+      return "CellularStateDisabled";
+    case kStateEnabled:
+      return "CellularStateEnabled";
+    case kStateRegistered:
+      return "CellularStateRegistered";
+    case kStateConnected:
+      return "CellularStateConnected";
+    case kStateLinked:
+      return "CellularStateLinked";
+    default:
+      NOTREACHED();
+  }
+  return StringPrintf("CellularStateUnknown-%d", state);
+}
+
+// static
+string Cellular::GetModemStateString(ModemState modem_state) {
+  switch (modem_state) {
+    case kModemStateFailed:
+      return "CellularModemStateFailed";
+    case kModemStateUnknown:
+      return "CellularModemStateUnknown";
+    case kModemStateInitializing:
+      return "CellularModemStateInitializing";
+    case kModemStateLocked:
+      return "CellularModemStateLocked";
+    case kModemStateDisabled:
+      return "CellularModemStateDisabled";
+    case kModemStateDisabling:
+      return "CellularModemStateDisabling";
+    case kModemStateEnabling:
+      return "CellularModemStateEnabling";
+    case kModemStateEnabled:
+      return "CellularModemStateEnabled";
+    case kModemStateSearching:
+      return "CellularModemStateSearching";
+    case kModemStateRegistered:
+      return "CellularModemStateRegistered";
+    case kModemStateDisconnecting:
+      return "CellularModemStateDisconnecting";
+    case kModemStateConnecting:
+      return "CellularModemStateConnecting";
+    case kModemStateConnected:
+      return "CellularModemStateConnected";
+    default:
+      NOTREACHED();
+  }
+  return StringPrintf("CellularModemStateUnknown-%d", modem_state);
+}
+
+string Cellular::GetTechnologyFamily(Error *error) {
+  return capability_->GetTypeString();
+}
+
+void Cellular::SetState(State state) {
+  SLOG(this, 2) << GetStateString(state_) << " -> "
+                << GetStateString(state);
+  state_ = state;
+}
+
+void Cellular::HelpRegisterDerivedBool(
+    const string &name,
+    bool(Cellular::*get)(Error *error),
+    bool(Cellular::*set)(const bool &value, Error *error)) {
+  mutable_store()->RegisterDerivedBool(
+      name,
+      BoolAccessor(
+          new CustomAccessor<Cellular, bool>(this, get, set)));
+}
+
+void Cellular::HelpRegisterConstDerivedString(
+    const string &name,
+    string(Cellular::*get)(Error *)) {
+  mutable_store()->RegisterDerivedString(
+      name,
+      StringAccessor(new CustomAccessor<Cellular, string>(this, get, nullptr)));
+}
+
+void Cellular::Start(Error *error,
+                     const EnabledStateChangedCallback &callback) {
+  DCHECK(error);
+  SLOG(this, 2) << __func__ << ": " << GetStateString(state_);
+  // We can only short circuit the start operation if both the cellular state
+  // is not disabled AND the proxies have been initialized.  We have seen
+  // crashes due to NULL proxies and the state being not disabled.
+  if (state_ != kStateDisabled && capability_->AreProxiesInitialized()) {
+    return;
+  }
+
+  ResultCallback cb = Bind(&Cellular::StartModemCallback,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           callback);
+  capability_->StartModem(error, cb);
+}
+
+void Cellular::Stop(Error *error,
+                    const EnabledStateChangedCallback &callback) {
+  SLOG(this, 2) << __func__ << ": " << GetStateString(state_);
+  explicit_disconnect_ = true;
+  ResultCallback cb = Bind(&Cellular::StopModemCallback,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           callback);
+  capability_->StopModem(error, cb);
+}
+
+bool Cellular::IsUnderlyingDeviceEnabled() const {
+  return IsEnabledModemState(modem_state_);
+}
+
+bool Cellular::IsModemRegistered() const {
+  return (modem_state_ == Cellular::kModemStateRegistered ||
+          modem_state_ == Cellular::kModemStateConnecting ||
+          modem_state_ == Cellular::kModemStateConnected);
+}
+
+// static
+bool Cellular::IsEnabledModemState(ModemState state) {
+  switch (state) {
+    case kModemStateFailed:
+    case kModemStateUnknown:
+    case kModemStateDisabled:
+    case kModemStateInitializing:
+    case kModemStateLocked:
+    case kModemStateDisabling:
+    case kModemStateEnabling:
+      return false;
+    case kModemStateEnabled:
+    case kModemStateSearching:
+    case kModemStateRegistered:
+    case kModemStateDisconnecting:
+    case kModemStateConnecting:
+    case kModemStateConnected:
+      return true;
+  }
+  return false;
+}
+
+void Cellular::StartModemCallback(const EnabledStateChangedCallback &callback,
+                                  const Error &error) {
+  SLOG(this, 2) << __func__ << ": " << GetStateString(state_);
+  if (error.IsSuccess() && (state_ == kStateDisabled)) {
+    SetState(kStateEnabled);
+    // Registration state updates may have been ignored while the
+    // modem was not yet marked enabled.
+    HandleNewRegistrationState();
+  }
+  callback.Run(error);
+}
+
+void Cellular::StopModemCallback(const EnabledStateChangedCallback &callback,
+                                 const Error &error) {
+  SLOG(this, 2) << __func__ << ": " << GetStateString(state_);
+  explicit_disconnect_ = false;
+  // Destroy the cellular service regardless of any errors that occur during
+  // the stop process since we do not know the state of the modem at this
+  // point.
+  DestroyService();
+  if (state_ != kStateDisabled)
+    SetState(kStateDisabled);
+  callback.Run(error);
+  // In case no termination action was executed (and TerminationActionComplete
+  // was not invoked) in response to a suspend request, any registered
+  // termination action needs to be removed explicitly.
+  manager()->RemoveTerminationAction(FriendlyName());
+}
+
+void Cellular::InitCapability(Type type) {
+  // TODO(petkov): Consider moving capability construction into a factory that's
+  // external to the Cellular class.
+  SLOG(this, 2) << __func__ << "(" << type << ")";
+  switch (type) {
+    case kTypeGSM:
+      capability_.reset(new CellularCapabilityGSM(this,
+                                                  proxy_factory_,
+                                                  modem_info_));
+      break;
+    case kTypeCDMA:
+      capability_.reset(new CellularCapabilityCDMA(this,
+                                                   proxy_factory_,
+                                                   modem_info_));
+      break;
+    case kTypeUniversal:
+      capability_.reset(new CellularCapabilityUniversal(
+          this,
+          proxy_factory_,
+          modem_info_));
+      break;
+    case kTypeUniversalCDMA:
+      capability_.reset(new CellularCapabilityUniversalCDMA(
+          this,
+          proxy_factory_,
+          modem_info_));
+      break;
+    default: NOTREACHED();
+  }
+  mobile_operator_info_observer_->set_capability(capability_.get());
+}
+
+void Cellular::Activate(const string &carrier,
+                        Error *error, const ResultCallback &callback) {
+  capability_->Activate(carrier, error, callback);
+}
+
+void Cellular::CompleteActivation(Error *error) {
+  capability_->CompleteActivation(error);
+}
+
+void Cellular::RegisterOnNetwork(const string &network_id,
+                                 Error *error,
+                                 const ResultCallback &callback) {
+  capability_->RegisterOnNetwork(network_id, error, callback);
+}
+
+void Cellular::RequirePIN(const string &pin, bool require,
+                          Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__ << "(" << require << ")";
+  capability_->RequirePIN(pin, require, error, callback);
+}
+
+void Cellular::EnterPIN(const string &pin,
+                        Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  capability_->EnterPIN(pin, error, callback);
+}
+
+void Cellular::UnblockPIN(const string &unblock_code,
+                          const string &pin,
+                          Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  capability_->UnblockPIN(unblock_code, pin, error, callback);
+}
+
+void Cellular::ChangePIN(const string &old_pin, const string &new_pin,
+                         Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  capability_->ChangePIN(old_pin, new_pin, error, callback);
+}
+
+void Cellular::Reset(Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  capability_->Reset(error, callback);
+}
+
+void Cellular::SetCarrier(const string &carrier,
+                          Error *error, const ResultCallback &callback) {
+  SLOG(this, 2) << __func__ << "(" << carrier << ")";
+  capability_->SetCarrier(carrier, error, callback);
+}
+
+bool Cellular::IsIPv6Allowed() const {
+  // A cellular device is disabled before the system goes into suspend mode.
+  // However, outstanding TCP sockets may not be nuked when the associated
+  // network interface goes down. When the system resumes from suspend, the
+  // cellular device is re-enabled and may reconnect to the network, which
+  // acquire a new IPv6 address on the network interface. However, those
+  // outstanding TCP sockets may initiate traffic with the old IPv6 address.
+  // Some network may not like the fact that two IPv6 addresses originated from
+  // the same modem within a connection session and may drop the connection.
+  // Here we disable IPv6 support on cellular devices to work around the issue.
+  //
+  // TODO(benchan): Resolve the IPv6 issue in a different way and then
+  // re-enable IPv6 support on cellular devices.
+  return false;
+}
+
+void Cellular::DropConnection() {
+  if (ppp_device_) {
+    // For PPP dongles, IP configuration is handled on the |ppp_device_|,
+    // rather than the netdev plumbed into |this|.
+    ppp_device_->DropConnection();
+  } else {
+    Device::DropConnection();
+  }
+}
+
+void Cellular::SetServiceState(Service::ConnectState state) {
+  if (ppp_device_) {
+    ppp_device_->SetServiceState(state);
+  } else if (selected_service()) {
+    Device::SetServiceState(state);
+  } else if (service_) {
+    service_->SetState(state);
+  } else {
+    LOG(WARNING) << "State change with no Service.";
+  }
+}
+
+void Cellular::SetServiceFailure(Service::ConnectFailure failure_state) {
+  if (ppp_device_) {
+    ppp_device_->SetServiceFailure(failure_state);
+  } else if (selected_service()) {
+    Device::SetServiceFailure(failure_state);
+  } else if (service_) {
+    service_->SetFailure(failure_state);
+  } else {
+    LOG(WARNING) << "State change with no Service.";
+  }
+}
+
+void Cellular::SetServiceFailureSilent(Service::ConnectFailure failure_state) {
+  if (ppp_device_) {
+    ppp_device_->SetServiceFailureSilent(failure_state);
+  } else if (selected_service()) {
+    Device::SetServiceFailureSilent(failure_state);
+  } else if (service_) {
+    service_->SetFailureSilent(failure_state);
+  } else {
+    LOG(WARNING) << "State change with no Service.";
+  }
+}
+
+void Cellular::OnBeforeSuspend(const ResultCallback &callback) {
+  LOG(INFO) << __func__;
+  Error error;
+  StopPPP();
+  SetEnabledNonPersistent(false, &error, callback);
+  if (error.IsFailure() && error.type() != Error::kInProgress) {
+    // If we fail to disable the modem right away, proceed instead of wasting
+    // the time to wait for the suspend/termination delay to expire.
+    LOG(WARNING) << "Proceed with suspend/termination even though the modem "
+                 << "is not yet disabled: " << error;
+    callback.Run(error);
+  }
+}
+
+void Cellular::OnAfterResume() {
+  SLOG(this, 2) << __func__;
+  if (enabled_persistent()) {
+    LOG(INFO) << "Restarting modem after resume.";
+
+    // If we started disabling the modem before suspend, but that
+    // suspend is still in progress, then we are not yet in
+    // kStateDisabled. That's a problem, because Cellular::Start
+    // returns immediately in that case. Hack around that by forcing
+    // |state_| here.
+    //
+    // TODO(quiche): Remove this hack. Maybe
+    // CellularCapabilityUniversal should generate separate
+    // notifications for Stop_Disable, and Stop_PowerDown. Then we'd
+    // update our state to kStateDisabled when Stop_Disable completes.
+    state_ = kStateDisabled;
+
+    Error error;
+    SetEnabledUnchecked(true, &error, Bind(LogRestartModemResult));
+    if (error.IsSuccess()) {
+      LOG(INFO) << "Modem restart completed immediately.";
+    } else if (error.IsOngoing()) {
+      LOG(INFO) << "Modem restart in progress.";
+    } else {
+      LOG(WARNING) << "Modem restart failed: " << error;
+    }
+  }
+  // TODO(quiche): Consider if this should be conditional. If, e.g.,
+  // the device was still disabling when we suspended, will trying to
+  // renew DHCP here cause problems?
+  Device::OnAfterResume();
+}
+
+void Cellular::Scan(ScanType /*scan_type*/, Error *error,
+                    const string &/*reason*/) {
+  SLOG(this, 2) << __func__;
+  CHECK(error);
+  if (proposed_scan_in_progress_) {
+    Error::PopulateAndLog(error, Error::kInProgress, "Already scanning");
+    return;
+  }
+
+  // |scan_type| is ignored because Cellular only does a full scan.
+  ResultStringmapsCallback cb = Bind(&Cellular::OnScanReply,
+                                     weak_ptr_factory_.GetWeakPtr());
+  capability_->Scan(error, cb);
+  // An immediate failure in |cabapility_->Scan(...)| is indicated through the
+  // |error| argument.
+  if (error->IsFailure())
+    return;
+
+  proposed_scan_in_progress_ = true;
+  UpdateScanning();
+}
+
+void Cellular::OnScanReply(const Stringmaps &found_networks,
+                           const Error &error) {
+  proposed_scan_in_progress_ = false;
+  UpdateScanning();
+
+  // TODO(jglasgow): fix error handling.
+  // At present, there is no way of notifying user of this asynchronous error.
+  if (error.IsFailure()) {
+    clear_found_networks();
+    return;
+  }
+
+  set_found_networks(found_networks);
+}
+
+void Cellular::HandleNewRegistrationState() {
+  SLOG(this, 2) << __func__
+                << ": (new state " << GetStateString(state_) << ")";
+  if (!capability_->IsRegistered()) {
+    if (!explicit_disconnect_ &&
+        (state_ == kStateLinked || state_ == kStateConnected) &&
+        service_.get())
+      metrics()->NotifyCellularDeviceDrop(
+          capability_->GetNetworkTechnologyString(), service_->strength());
+    DestroyService();
+    if (state_ == kStateLinked ||
+        state_ == kStateConnected ||
+        state_ == kStateRegistered) {
+      SetState(kStateEnabled);
+    }
+    return;
+  }
+  // In Disabled state, defer creating a service until fully
+  // enabled. UI will ignore the appearance of a new service
+  // on a disabled device.
+  if (state_ == kStateDisabled) {
+    return;
+  }
+  if (state_ == kStateEnabled) {
+    SetState(kStateRegistered);
+  }
+  if (!service_.get()) {
+    metrics()->NotifyDeviceScanFinished(interface_index());
+    CreateService();
+  }
+  capability_->GetSignalQuality();
+  if (state_ == kStateRegistered && modem_state_ == kModemStateConnected)
+    OnConnected();
+  service_->SetNetworkTechnology(capability_->GetNetworkTechnologyString());
+  service_->SetRoamingState(capability_->GetRoamingStateString());
+  manager()->UpdateService(service_);
+}
+
+void Cellular::HandleNewSignalQuality(uint32_t strength) {
+  SLOG(this, 2) << "Signal strength: " << strength;
+  if (service_) {
+    service_->SetStrength(strength);
+  }
+}
+
+void Cellular::CreateService() {
+  SLOG(this, 2) << __func__;
+  CHECK(!service_.get());
+  service_ = new CellularService(modem_info_, this);
+  capability_->OnServiceCreated();
+
+  // Storage identifier must be set only once, and before registering the
+  // service with the manager, since we key off of this identifier to
+  // determine the profile to load.
+  // TODO(pprabhu) Make profile matching more robust (crbug.com/369755)
+  string service_id;
+  if (home_provider_info_->IsMobileNetworkOperatorKnown() &&
+      !home_provider_info_->uuid().empty()) {
+    service_id = home_provider_info_->uuid();
+  } else if (serving_operator_info_->IsMobileNetworkOperatorKnown() &&
+             !serving_operator_info_->uuid().empty()) {
+    service_id = serving_operator_info_->uuid();
+  } else {
+    switch (type_) {
+      case kTypeGSM:
+      case kTypeUniversal:
+        if (!sim_identifier().empty()) {
+          service_id = sim_identifier();
+        }
+        break;
+
+      case kTypeCDMA:
+      case kTypeUniversalCDMA:
+        if (!meid().empty()) {
+          service_id = meid();
+        }
+        break;
+
+      default:
+        NOTREACHED();
+    }
+  }
+
+  if (!service_id.empty()) {
+    string storage_id = base::StringPrintf(
+        "%s_%s_%s",
+        kTypeCellular, address().c_str(), service_id.c_str());
+    service()->SetStorageIdentifier(storage_id);
+  }
+
+  manager()->RegisterService(service_);
+
+  // We might have missed a property update because the service wasn't created
+  // ealier.
+  UpdateScanning();
+  mobile_operator_info_observer_->OnOperatorChanged();
+}
+
+void Cellular::DestroyService() {
+  SLOG(this, 2) << __func__;
+  DropConnection();
+  if (service_) {
+    LOG(INFO) << "Deregistering cellular service " << service_->unique_name()
+              << " for device " << link_name();
+    manager()->DeregisterService(service_);
+    service_ = nullptr;
+  }
+}
+
+void Cellular::Connect(Error *error) {
+  SLOG(this, 2) << __func__;
+  if (state_ == kStateConnected || state_ == kStateLinked) {
+    Error::PopulateAndLog(error, Error::kAlreadyConnected,
+                          "Already connected; connection request ignored.");
+    return;
+  } else if (state_ != kStateRegistered) {
+    Error::PopulateAndLog(error, Error::kNotRegistered,
+                          "Modem not registered; connection request ignored.");
+    return;
+  }
+
+  if (!capability_->AllowRoaming() &&
+      service_->roaming_state() == kRoamingStateRoaming) {
+    Error::PopulateAndLog(error, Error::kNotOnHomeNetwork,
+                          "Roaming disallowed; connection request ignored.");
+    return;
+  }
+
+  DBusPropertiesMap properties;
+  capability_->SetupConnectProperties(&properties);
+  ResultCallback cb = Bind(&Cellular::OnConnectReply,
+                           weak_ptr_factory_.GetWeakPtr());
+  OnConnecting();
+  capability_->Connect(properties, error, cb);
+  if (!error->IsSuccess())
+    return;
+
+  bool is_auto_connecting = service_.get() && service_->is_auto_connecting();
+  metrics()->NotifyDeviceConnectStarted(interface_index(), is_auto_connecting);
+}
+
+// Note that there's no ResultCallback argument to this,
+// since Connect() isn't yet passed one.
+void Cellular::OnConnectReply(const Error &error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+  if (error.IsSuccess()) {
+    metrics()->NotifyDeviceConnectFinished(interface_index());
+    OnConnected();
+  } else {
+    metrics()->NotifyCellularDeviceFailure(error);
+    OnConnectFailed(error);
+  }
+}
+
+void Cellular::OnDisabled() {
+  SetEnabled(false);
+}
+
+void Cellular::OnEnabled() {
+  manager()->AddTerminationAction(FriendlyName(),
+                                  Bind(&Cellular::StartTermination,
+                                       weak_ptr_factory_.GetWeakPtr()));
+  SetEnabled(true);
+}
+
+void Cellular::OnConnecting() {
+  if (service_)
+    service_->SetState(Service::kStateAssociating);
+}
+
+void Cellular::OnConnected() {
+  SLOG(this, 2) << __func__;
+  if (state_ == kStateConnected || state_ == kStateLinked) {
+    SLOG(this, 2) << "Already connected";
+    return;
+  }
+  SetState(kStateConnected);
+  if (!service_) {
+    LOG(INFO) << "Disconnecting due to no cellular service.";
+    Disconnect(nullptr, "no celluar service");
+  } else if (!capability_->AllowRoaming() &&
+      service_->roaming_state() == kRoamingStateRoaming) {
+    LOG(INFO) << "Disconnecting due to roaming.";
+    Disconnect(nullptr, "roaming");
+  } else {
+    EstablishLink();
+  }
+}
+
+void Cellular::OnConnectFailed(const Error &error) {
+  if (service_)
+    service_->SetFailure(Service::kFailureUnknown);
+}
+
+void Cellular::Disconnect(Error *error, const char *reason) {
+  SLOG(this, 2) << __func__ << ": " << reason;
+  if (state_ != kStateConnected && state_ != kStateLinked) {
+    Error::PopulateAndLog(
+        error, Error::kNotConnected, "Not connected; request ignored.");
+    return;
+  }
+  StopPPP();
+  explicit_disconnect_ = true;
+  ResultCallback cb = Bind(&Cellular::OnDisconnectReply,
+                           weak_ptr_factory_.GetWeakPtr());
+  capability_->Disconnect(error, cb);
+}
+
+void Cellular::OnDisconnectReply(const Error &error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+  explicit_disconnect_ = false;
+  if (error.IsSuccess()) {
+    OnDisconnected();
+  } else {
+    metrics()->NotifyCellularDeviceFailure(error);
+    OnDisconnectFailed();
+  }
+}
+
+void Cellular::OnDisconnected() {
+  SLOG(this, 2) << __func__;
+  if (!DisconnectCleanup()) {
+    LOG(WARNING) << "Disconnect occurred while in state "
+                 << GetStateString(state_);
+  }
+}
+
+void Cellular::OnDisconnectFailed() {
+  SLOG(this, 2) << __func__;
+  // If the modem is in the disconnecting state, then
+  // the disconnect should eventually succeed, so do
+  // nothing.
+  if (modem_state_ == kModemStateDisconnecting) {
+    LOG(WARNING) << "Ignoring failed disconnect while modem is disconnecting.";
+    return;
+  }
+
+  // OnDisconnectFailed got called because no bearers
+  // to disconnect were found. Which means that we shouldn't
+  // really remain in the connected/linked state if we
+  // are in one of those.
+  if (!DisconnectCleanup()) {
+    // otherwise, no-op
+    LOG(WARNING) << "Ignoring failed disconnect while in state "
+                 << GetStateString(state_);
+  }
+
+  // TODO(armansito): In either case, shill ends up thinking
+  // that it's disconnected, while for some reason the underlying
+  // modem might still actually be connected. In that case the UI
+  // would be reflecting an incorrect state and a further connection
+  // request would fail. We should perhaps tear down the modem and
+  // restart it here.
+}
+
+void Cellular::EstablishLink() {
+  SLOG(this, 2) << __func__;
+  CHECK_EQ(kStateConnected, state_);
+
+  CellularBearer *bearer = capability_->GetActiveBearer();
+  if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodPPP) {
+    LOG(INFO) << "Start PPP connection on " << bearer->data_interface();
+    StartPPP(bearer->data_interface());
+    return;
+  }
+
+  unsigned int flags = 0;
+  if (manager()->device_info()->GetFlags(interface_index(), &flags) &&
+      (flags & IFF_UP) != 0) {
+    LinkEvent(flags, IFF_UP);
+    return;
+  }
+  // TODO(petkov): Provide a timeout for a failed link-up request.
+  rtnl_handler()->SetInterfaceFlags(interface_index(), IFF_UP, IFF_UP);
+
+  // Set state to associating.
+  OnConnecting();
+}
+
+void Cellular::LinkEvent(unsigned int flags, unsigned int change) {
+  Device::LinkEvent(flags, change);
+  if (ppp_task_) {
+    LOG(INFO) << "Ignoring LinkEvent on device with PPP interface.";
+    return;
+  }
+
+  if ((flags & IFF_UP) != 0 && state_ == kStateConnected) {
+    LOG(INFO) << link_name() << " is up.";
+    SetState(kStateLinked);
+
+    // TODO(benchan): IPv6 support is currently disabled for cellular devices.
+    // Check and obtain IPv6 configuration from the bearer when we later enable
+    // IPv6 support on cellular devices.
+    CellularBearer *bearer = capability_->GetActiveBearer();
+    if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodStatic) {
+      SLOG(this, 2) << "Assign static IP configuration from bearer.";
+      SelectService(service_);
+      SetServiceState(Service::kStateConfiguring);
+      AssignIPConfig(*bearer->ipv4_config_properties());
+      return;
+    }
+
+    if (AcquireIPConfig()) {
+      SLOG(this, 2) << "Start DHCP to acquire IP configuration.";
+      SelectService(service_);
+      SetServiceState(Service::kStateConfiguring);
+      return;
+    }
+
+    LOG(ERROR) << "Unable to acquire IP configuration over DHCP.";
+    return;
+  }
+
+  if ((flags & IFF_UP) == 0 && state_ == kStateLinked) {
+    LOG(INFO) << link_name() << " is down.";
+    SetState(kStateConnected);
+    DropConnection();
+  }
+}
+
+void Cellular::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  capability_->OnDBusPropertiesChanged(interface,
+                                       changed_properties,
+                                       invalidated_properties);
+}
+
+string Cellular::CreateDefaultFriendlyServiceName() {
+  SLOG(this, 2) << __func__;
+  return base::StringPrintf("%s_%u",
+                            kGenericServiceNamePrefix,
+                            friendly_service_name_id_++);
+}
+
+bool Cellular::IsDefaultFriendlyServiceName(const string &service_name) const {
+  return StartsWithASCII(service_name, kGenericServiceNamePrefix, true);
+}
+
+void Cellular::OnModemStateChanged(ModemState new_state) {
+  ModemState old_state = modem_state_;
+  SLOG(this, 2) << __func__ << ": " << GetModemStateString(old_state)
+                << " -> " << GetModemStateString(new_state);
+  if (old_state == new_state) {
+    SLOG(this, 2) << "The new state matches the old state. Nothing to do.";
+    return;
+  }
+  set_modem_state(new_state);
+  if (old_state >= kModemStateRegistered &&
+      new_state < kModemStateRegistered) {
+    capability_->SetUnregistered(new_state == kModemStateSearching);
+    HandleNewRegistrationState();
+  }
+  if (new_state == kModemStateDisabled) {
+    OnDisabled();
+  } else if (new_state >= kModemStateEnabled) {
+    if (old_state < kModemStateEnabled) {
+      // Just became enabled, update enabled state.
+      OnEnabled();
+    }
+    if ((new_state == kModemStateEnabled ||
+         new_state == kModemStateSearching ||
+         new_state == kModemStateRegistered) &&
+        (old_state == kModemStateConnected ||
+         old_state == kModemStateConnecting ||
+         old_state == kModemStateDisconnecting))
+      OnDisconnected();
+    else if (new_state == kModemStateConnecting)
+      OnConnecting();
+    else if (new_state == kModemStateConnected &&
+             old_state == kModemStateConnecting)
+      OnConnected();
+  }
+
+  // Update the kScanningProperty property after we've handled the current state
+  // update completely.
+  UpdateScanning();
+}
+
+bool Cellular::IsActivating() const {
+  return capability_->IsActivating();
+}
+
+bool Cellular::SetAllowRoaming(const bool &value, Error */*error*/) {
+  SLOG(this, 2) << __func__
+                << "(" << allow_roaming_ << "->" << value << ")";
+  if (allow_roaming_ == value) {
+    return false;
+  }
+  allow_roaming_ = value;
+  manager()->UpdateDevice(this);
+
+  // Use AllowRoaming() instead of allow_roaming_ in order to
+  // incorporate provider preferences when evaluating if a disconnect
+  // is required.
+  if (!capability_->AllowRoaming() &&
+      capability_->GetRoamingStateString() == kRoamingStateRoaming) {
+    Error error;
+    Disconnect(&error, __func__);
+  }
+  adaptor()->EmitBoolChanged(kCellularAllowRoamingProperty, value);
+  return true;
+}
+
+void Cellular::StartTermination() {
+  SLOG(this, 2) << __func__;
+  OnBeforeSuspend(Bind(&Cellular::OnTerminationCompleted,
+                       weak_ptr_factory_.GetWeakPtr()));
+}
+
+void Cellular::OnTerminationCompleted(const Error &error) {
+  LOG(INFO) << __func__ << ": " << error;
+  manager()->TerminationActionComplete(FriendlyName());
+  manager()->RemoveTerminationAction(FriendlyName());
+}
+
+bool Cellular::DisconnectCleanup() {
+  bool succeeded = false;
+  if (state_ == kStateConnected || state_ == kStateLinked) {
+    SetState(kStateRegistered);
+    SetServiceFailureSilent(Service::kFailureUnknown);
+    DestroyIPConfig();
+    succeeded = true;
+  }
+  capability_->DisconnectCleanup();
+  return succeeded;
+}
+
+// static
+void Cellular::LogRestartModemResult(const Error &error) {
+  if (error.IsSuccess()) {
+    LOG(INFO) << "Modem restart completed.";
+  } else {
+    LOG(WARNING) << "Attempt to restart modem failed: " << error;
+  }
+}
+
+void Cellular::StartPPP(const string &serial_device) {
+  SLOG(PPP, this, 2) << __func__ << " on " << serial_device;
+  // Detach any SelectedService from this device. It will be grafted onto
+  // the PPPDevice after PPP is up (in Cellular::Notify).
+  //
+  // This has two important effects: 1) kills dhcpcd if it is running.
+  // 2) stops Cellular::LinkEvent from driving changes to the
+  // SelectedService.
+  if (selected_service()) {
+    CHECK_EQ(service_.get(), selected_service().get());
+    // Save and restore |service_| state, as DropConnection calls
+    // SelectService, and SelectService will move selected_service()
+    // to kStateIdle.
+    Service::ConnectState original_state(service_->state());
+    Device::DropConnection();  // Don't redirect to PPPDevice.
+    service_->SetState(original_state);
+  } else {
+    CHECK(!ipconfig());  // Shouldn't have ipconfig without selected_service().
+  }
+
+  base::Callback<void(pid_t, int)> death_callback(
+      Bind(&Cellular::OnPPPDied, weak_ptr_factory_.GetWeakPtr()));
+  vector<string> args;
+  map<string, string> environment;
+  Error error;
+  if (SLOG_IS_ON(PPP, 5)) {
+    args.push_back("debug");
+  }
+  args.push_back("nodetach");
+  args.push_back("nodefaultroute");  // Don't let pppd muck with routing table.
+  args.push_back("usepeerdns");  // Request DNS servers.
+  args.push_back("plugin");  // Goes with next arg.
+  args.push_back(PPPDevice::kPluginPath);
+  args.push_back(serial_device);
+  is_ppp_authenticating_ = false;
+  std::unique_ptr<ExternalTask> new_ppp_task(
+      new ExternalTask(modem_info_->control_interface(),
+                       modem_info_->glib(),
+                       weak_ptr_factory_.GetWeakPtr(),
+                       death_callback));
+  if (new_ppp_task->Start(
+          FilePath(PPPDevice::kDaemonPath), args, environment, true, &error)) {
+    LOG(INFO) << "Forked pppd process.";
+    ppp_task_ = std::move(new_ppp_task);
+  }
+}
+
+void Cellular::StopPPP() {
+  SLOG(PPP, this, 2) << __func__;
+  if (!ppp_task_) {
+    return;
+  }
+  DropConnection();
+  ppp_task_.reset();
+  ppp_device_ = nullptr;
+}
+
+// called by |ppp_task_|
+void Cellular::GetLogin(string *user, string *password) {
+  SLOG(PPP, this, 2) << __func__;
+  if (!service()) {
+    LOG(ERROR) << __func__ << " with no service ";
+    return;
+  }
+  CHECK(user);
+  CHECK(password);
+  *user = service()->ppp_username();
+  *password = service()->ppp_password();
+}
+
+// Called by |ppp_task_|.
+void Cellular::Notify(const string &reason,
+                      const map<string, string> &dict) {
+  SLOG(PPP, this, 2) << __func__ << " " << reason << " on " << link_name();
+
+  if (reason == kPPPReasonAuthenticating) {
+    OnPPPAuthenticating();
+  } else if (reason == kPPPReasonAuthenticated) {
+    OnPPPAuthenticated();
+  } else if (reason == kPPPReasonConnect) {
+    OnPPPConnected(dict);
+  } else if (reason == kPPPReasonDisconnect) {
+    OnPPPDisconnected();
+  } else {
+    NOTREACHED();
+  }
+}
+
+void Cellular::OnPPPAuthenticated() {
+  SLOG(PPP, this, 2) << __func__;
+  is_ppp_authenticating_ = false;
+}
+
+void Cellular::OnPPPAuthenticating() {
+  SLOG(PPP, this, 2) << __func__;
+  is_ppp_authenticating_ = true;
+}
+
+void Cellular::OnPPPConnected(const map<string, string> &params) {
+  SLOG(PPP, this, 2) << __func__;
+  string interface_name = PPPDevice::GetInterfaceName(params);
+  DeviceInfo *device_info = modem_info_->manager()->device_info();
+  int interface_index = device_info->GetIndex(interface_name);
+  if (interface_index < 0) {
+    // TODO(quiche): Consider handling the race when the RTNL notification about
+    // the new PPP device has not been received yet. crbug.com/246832.
+    NOTIMPLEMENTED() << ": No device info for " << interface_name << ".";
+    return;
+  }
+
+  if (!ppp_device_ || ppp_device_->interface_index() != interface_index) {
+    if (ppp_device_) {
+      ppp_device_->SelectService(nullptr);  // No longer drives |service_|.
+    }
+    ppp_device_ = ppp_device_factory_->CreatePPPDevice(
+        modem_info_->control_interface(),
+        modem_info_->dispatcher(),
+        modem_info_->metrics(),
+        modem_info_->manager(),
+        interface_name,
+        interface_index);
+    device_info->RegisterDevice(ppp_device_);
+  }
+
+  CHECK(service_);
+  // For PPP, we only SelectService on the |ppp_device_|.
+  CHECK(!selected_service());
+  const bool kBlackholeIPv6 = false;
+  ppp_device_->SetEnabled(true);
+  ppp_device_->SelectService(service_);
+  ppp_device_->UpdateIPConfigFromPPP(params, kBlackholeIPv6);
+}
+
+void Cellular::OnPPPDisconnected() {
+  SLOG(PPP, this, 2) << __func__;
+  // DestroyLater, rather than while on stack.
+  ppp_task_.release()->DestroyLater(modem_info_->dispatcher());
+  if (is_ppp_authenticating_) {
+    SetServiceFailure(Service::kFailurePPPAuth);
+  } else {
+    // TODO(quiche): Don't set failure if we disconnected intentionally.
+    SetServiceFailure(Service::kFailureUnknown);
+  }
+  Error error;
+  Disconnect(&error, __func__);
+}
+
+void Cellular::OnPPPDied(pid_t pid, int exit) {
+  LOG(INFO) << __func__ << " on " << link_name();
+  OnPPPDisconnected();
+}
+
+void Cellular::UpdateScanning() {
+  if (proposed_scan_in_progress_) {
+    set_scanning(true);
+    return;
+  }
+
+  if (modem_state_ == kModemStateEnabling) {
+    set_scanning(true);
+    return;
+  }
+
+  if (service_ && service_->activation_state() != kActivationStateActivated) {
+    set_scanning(false);
+    return;
+  }
+
+  if (modem_state_ == kModemStateEnabled ||
+      modem_state_ == kModemStateSearching) {
+    set_scanning(true);
+    return;
+  }
+
+  set_scanning(false);
+}
+
+void Cellular::RegisterProperties() {
+  PropertyStore *store = this->mutable_store();
+
+  // These properties do not have setters, and events are not generated when
+  // they are changed.
+  store->RegisterConstString(kDBusServiceProperty, &dbus_service_);
+  store->RegisterConstString(kDBusObjectProperty, &dbus_path_);
+
+  store->RegisterUint16(kScanIntervalProperty, &scan_interval_);
+
+  // These properties have setters that should be used to change their values.
+  // Events are generated whenever the values change.
+  store->RegisterConstStringmap(kHomeProviderProperty, &home_provider_);
+  store->RegisterConstString(kCarrierProperty, &carrier_);
+  store->RegisterConstBool(kSupportNetworkScanProperty, &scanning_supported_);
+  store->RegisterConstString(kEsnProperty, &esn_);
+  store->RegisterConstString(kFirmwareRevisionProperty, &firmware_revision_);
+  store->RegisterConstString(kHardwareRevisionProperty, &hardware_revision_);
+  store->RegisterConstString(kImeiProperty, &imei_);
+  store->RegisterConstString(kImsiProperty, &imsi_);
+  store->RegisterConstString(kMdnProperty, &mdn_);
+  store->RegisterConstString(kMeidProperty, &meid_);
+  store->RegisterConstString(kMinProperty, &min_);
+  store->RegisterConstString(kManufacturerProperty, &manufacturer_);
+  store->RegisterConstString(kModelIDProperty, &model_id_);
+  store->RegisterConstBool(kScanningProperty, &scanning_);
+
+  store->RegisterConstString(kSelectedNetworkProperty, &selected_network_);
+  store->RegisterConstStringmaps(kFoundNetworksProperty, &found_networks_);
+  store->RegisterConstBool(kProviderRequiresRoamingProperty,
+                           &provider_requires_roaming_);
+  store->RegisterConstBool(kSIMPresentProperty, &sim_present_);
+  store->RegisterConstStringmaps(kCellularApnListProperty, &apn_list_);
+  store->RegisterConstString(kIccidProperty, &sim_identifier_);
+
+  store->RegisterConstStrings(kSupportedCarriersProperty, &supported_carriers_);
+  store->RegisterConstUint16(kPRLVersionProperty, &prl_version_);
+
+  // TODO(pprabhu): Decide whether these need their own custom setters.
+  HelpRegisterConstDerivedString(kTechnologyFamilyProperty,
+                                 &Cellular::GetTechnologyFamily);
+  HelpRegisterDerivedBool(kCellularAllowRoamingProperty,
+                          &Cellular::GetAllowRoaming,
+                          &Cellular::SetAllowRoaming);
+}
+
+void Cellular::set_home_provider(const Stringmap &home_provider) {
+  if (home_provider_ == home_provider)
+    return;
+
+  home_provider_ = home_provider;
+  adaptor()->EmitStringmapChanged(kHomeProviderProperty, home_provider_);
+}
+
+void Cellular::set_carrier(const string &carrier) {
+  if (carrier_ == carrier)
+    return;
+
+  carrier_ = carrier;
+  adaptor()->EmitStringChanged(kCarrierProperty, carrier_);
+}
+
+void Cellular::set_scanning_supported(bool scanning_supported) {
+  if (scanning_supported_ == scanning_supported)
+    return;
+
+  scanning_supported_ = scanning_supported;
+  if (adaptor())
+    adaptor()->EmitBoolChanged(kSupportNetworkScanProperty,
+                               scanning_supported_);
+  else
+    SLOG(this, 2) << "Could not emit signal for property |"
+                  << kSupportNetworkScanProperty
+                  << "| change. DBus adaptor is NULL!";
+}
+
+void Cellular::set_esn(const string &esn) {
+  if (esn_ == esn)
+    return;
+
+  esn_ = esn;
+  adaptor()->EmitStringChanged(kEsnProperty, esn_);
+}
+
+void Cellular::set_firmware_revision(const string &firmware_revision) {
+  if (firmware_revision_ == firmware_revision)
+    return;
+
+  firmware_revision_ = firmware_revision;
+  adaptor()->EmitStringChanged(kFirmwareRevisionProperty, firmware_revision_);
+}
+
+void Cellular::set_hardware_revision(const string &hardware_revision) {
+  if (hardware_revision_ == hardware_revision)
+    return;
+
+  hardware_revision_ = hardware_revision;
+  adaptor()->EmitStringChanged(kHardwareRevisionProperty, hardware_revision_);
+}
+
+// TODO(armansito): The following methods should probably log their argument
+// values. Need to learn if any of them need to be scrubbed.
+void Cellular::set_imei(const string &imei) {
+  if (imei_ == imei)
+    return;
+
+  imei_ = imei;
+  adaptor()->EmitStringChanged(kImeiProperty, imei_);
+}
+
+void Cellular::set_imsi(const string &imsi) {
+  if (imsi_ == imsi)
+    return;
+
+  imsi_ = imsi;
+  adaptor()->EmitStringChanged(kImsiProperty, imsi_);
+}
+
+void Cellular::set_mdn(const string &mdn) {
+  if (mdn_ == mdn)
+    return;
+
+  mdn_ = mdn;
+  adaptor()->EmitStringChanged(kMdnProperty, mdn_);
+}
+
+void Cellular::set_meid(const string &meid) {
+  if (meid_ == meid)
+    return;
+
+  meid_ = meid;
+  adaptor()->EmitStringChanged(kMeidProperty, meid_);
+}
+
+void Cellular::set_min(const string &min) {
+  if (min_ == min)
+    return;
+
+  min_ = min;
+  adaptor()->EmitStringChanged(kMinProperty, min_);
+}
+
+void Cellular::set_manufacturer(const string &manufacturer) {
+  if (manufacturer_ == manufacturer)
+    return;
+
+  manufacturer_ = manufacturer;
+  adaptor()->EmitStringChanged(kManufacturerProperty, manufacturer_);
+}
+
+void Cellular::set_model_id(const string &model_id) {
+  if (model_id_ == model_id)
+    return;
+
+  model_id_ = model_id;
+  adaptor()->EmitStringChanged(kModelIDProperty, model_id_);
+}
+
+void Cellular::set_mm_plugin(const string &mm_plugin) {
+  mm_plugin_ = mm_plugin;
+}
+
+void Cellular::set_scanning(bool scanning) {
+  if (scanning_ == scanning)
+    return;
+
+  scanning_ = scanning;
+  adaptor()->EmitBoolChanged(kScanningProperty, scanning_);
+
+  // kScanningProperty is a sticky-false property.
+  // Every time it is set to |true|, it will remain |true| up to a maximum of
+  // |kScanningTimeout| time, after which it will be reset to |false|.
+  if (!scanning_ && !scanning_timeout_callback_.IsCancelled()) {
+     SLOG(this, 2) << "Scanning set to false. "
+                   << "Cancelling outstanding timeout.";
+     scanning_timeout_callback_.Cancel();
+  } else {
+    CHECK(scanning_timeout_callback_.IsCancelled());
+    SLOG(this, 2) << "Scanning set to true. "
+                  << "Starting timeout to reset to false.";
+    scanning_timeout_callback_.Reset(Bind(&Cellular::set_scanning,
+                                          weak_ptr_factory_.GetWeakPtr(),
+                                          false));
+    dispatcher()->PostDelayedTask(
+        scanning_timeout_callback_.callback(),
+        scanning_timeout_milliseconds_);
+  }
+}
+
+void Cellular::set_selected_network(const string &selected_network) {
+  if (selected_network_ == selected_network)
+    return;
+
+  selected_network_ = selected_network;
+  adaptor()->EmitStringChanged(kSelectedNetworkProperty, selected_network_);
+}
+
+void Cellular::set_found_networks(const Stringmaps &found_networks) {
+  // There is no canonical form of a Stringmaps value.
+  // So don't check for redundant updates.
+  found_networks_ = found_networks;
+  adaptor()->EmitStringmapsChanged(kFoundNetworksProperty, found_networks_);
+}
+
+void Cellular::clear_found_networks() {
+  if (found_networks_.empty())
+    return;
+
+  found_networks_.clear();
+  adaptor()->EmitStringmapsChanged(kFoundNetworksProperty, found_networks_);
+}
+
+void Cellular::set_provider_requires_roaming(bool provider_requires_roaming) {
+  if (provider_requires_roaming_ == provider_requires_roaming)
+    return;
+
+  provider_requires_roaming_ = provider_requires_roaming;
+  adaptor()->EmitBoolChanged(kProviderRequiresRoamingProperty,
+                             provider_requires_roaming_);
+}
+
+void Cellular::set_sim_present(bool sim_present) {
+  if (sim_present_ == sim_present)
+    return;
+
+  sim_present_ = sim_present;
+  adaptor()->EmitBoolChanged(kSIMPresentProperty, sim_present_);
+}
+
+void Cellular::set_apn_list(const Stringmaps &apn_list) {
+  // There is no canonical form of a Stringmaps value.
+  // So don't check for redundant updates.
+  apn_list_ = apn_list;
+  // See crbug.com/215581: Sometimes adaptor may be nullptr when |set_apn_list|
+  // is called.
+  if (adaptor())
+    adaptor()->EmitStringmapsChanged(kCellularApnListProperty, apn_list_);
+  else
+    SLOG(this, 2) << "Could not emit signal for property |"
+                  << kCellularApnListProperty
+                  << "| change. DBus adaptor is NULL!";
+}
+
+void Cellular::set_sim_identifier(const string &sim_identifier) {
+  if (sim_identifier_ == sim_identifier)
+    return;
+
+  sim_identifier_ = sim_identifier;
+  adaptor()->EmitStringChanged(kIccidProperty, sim_identifier_);
+}
+
+void Cellular::set_supported_carriers(const Strings &supported_carriers) {
+  // There is no canonical form of a Strings value.
+  // So don't check for redundant updates.
+  supported_carriers_ = supported_carriers;
+  adaptor()->EmitStringsChanged(kSupportedCarriersProperty,
+                                supported_carriers_);
+}
+
+void Cellular::set_prl_version(uint16_t prl_version) {
+  if (prl_version_ == prl_version)
+    return;
+
+  prl_version_ = prl_version;
+  adaptor()->EmitUint16Changed(kPRLVersionProperty, prl_version_);
+}
+
+void Cellular::set_home_provider_info(MobileOperatorInfo *home_provider_info) {
+  home_provider_info_.reset(home_provider_info);
+}
+
+void Cellular::set_serving_operator_info(
+    MobileOperatorInfo *serving_operator_info) {
+  serving_operator_info_.reset(serving_operator_info);
+}
+
+void Cellular::UpdateHomeProvider(const MobileOperatorInfo *operator_info) {
+  SLOG(this, 3) << __func__;
+
+  Stringmap home_provider;
+  if (!operator_info->sid().empty()) {
+    home_provider[kOperatorCodeKey] = operator_info->sid();
+  }
+  if (!operator_info->nid().empty()) {
+    home_provider[kOperatorCodeKey] = operator_info->nid();
+  }
+  if (!operator_info->mccmnc().empty()) {
+    home_provider[kOperatorCodeKey] = operator_info->mccmnc();
+  }
+  if (!operator_info->operator_name().empty()) {
+    home_provider[kOperatorNameKey] = operator_info->operator_name();
+  }
+  if (!operator_info->country().empty()) {
+    home_provider[kOperatorCountryKey] = operator_info->country();
+  }
+  set_home_provider(home_provider);
+
+  const ScopedVector<MobileOperatorInfo::MobileAPN> &apn_list =
+      operator_info->apn_list();
+  Stringmaps apn_list_dict;
+
+  for (const auto &mobile_apn : apn_list) {
+    Stringmap props;
+    if (!mobile_apn->apn.empty()) {
+      props[kApnProperty] = mobile_apn->apn;
+    }
+    if (!mobile_apn->username.empty()) {
+      props[kApnUsernameProperty] = mobile_apn->username;
+    }
+    if (!mobile_apn->password.empty()) {
+      props[kApnPasswordProperty] = mobile_apn->password;
+    }
+
+    // Find the first localized and non-localized name, if any.
+    if (!mobile_apn->operator_name_list.empty()) {
+      props[kApnNameProperty] = mobile_apn->operator_name_list[0].name;
+    }
+    for (const auto &lname : mobile_apn->operator_name_list) {
+      if (!lname.language.empty()) {
+        props[kApnLocalizedNameProperty] = lname.name;
+      }
+    }
+
+    apn_list_dict.push_back(props);
+  }
+  set_apn_list(apn_list_dict);
+
+  set_provider_requires_roaming(operator_info->requires_roaming());
+}
+
+void Cellular::UpdateServingOperator(
+    const MobileOperatorInfo *operator_info,
+    const MobileOperatorInfo *home_provider_info) {
+  SLOG(this, 3) << __func__;
+  if (!service()) {
+    return;
+  }
+
+  Stringmap serving_operator;
+  if (!operator_info->sid().empty()) {
+    serving_operator[kOperatorCodeKey] = operator_info->sid();
+  }
+  if (!operator_info->nid().empty()) {
+    serving_operator[kOperatorCodeKey] = operator_info->nid();
+  }
+  if (!operator_info->mccmnc().empty()) {
+    serving_operator[kOperatorCodeKey] = operator_info->mccmnc();
+  }
+  if (!operator_info->operator_name().empty()) {
+    serving_operator[kOperatorNameKey] = operator_info->operator_name();
+  }
+  if (!operator_info->country().empty()) {
+    serving_operator[kOperatorCountryKey] = operator_info->country();
+  }
+  service()->set_serving_operator(serving_operator);
+
+  // Set friendly name of service.
+  string service_name;
+  if (!operator_info->operator_name().empty()) {
+    // If roaming, try to show "<home-provider> | <serving-operator>", per 3GPP
+    // rules (TS 31.102 and annex A of 122.101).
+    if (service()->roaming_state() == kRoamingStateRoaming &&
+        home_provider_info &&
+        !home_provider_info->operator_name().empty()) {
+      service_name += home_provider_info->operator_name() + " | ";
+    }
+    service_name += operator_info->operator_name();
+  } else if (!operator_info->mccmnc().empty()) {
+    // We could not get a name for the operator, just use the code.
+    service_name = "cellular_" + operator_info->mccmnc();
+  } else {
+    // We do not have any information, so must fallback to default service name.
+    // Only assign a new default name if the service doesn't already have one,
+    // because we we generate a new name each time.
+    service_name = service()->friendly_name();
+    if (!IsDefaultFriendlyServiceName(service_name)) {
+      service_name = CreateDefaultFriendlyServiceName();
+    }
+  }
+  service()->SetFriendlyName(service_name);
+}
+
+// /////////////////////////////////////////////////////////////////////////////
+// MobileOperatorInfoObserver implementation.
+Cellular::MobileOperatorInfoObserver::MobileOperatorInfoObserver(
+    Cellular *cellular)
+  : cellular_(cellular),
+    capability_(nullptr) {}
+
+Cellular::MobileOperatorInfoObserver::~MobileOperatorInfoObserver() {}
+
+void Cellular::MobileOperatorInfoObserver::OnOperatorChanged() {
+  SLOG(cellular_, 3) << __func__;
+
+  // Give the capabilities a chance to hook in and update their state.
+  // Some tests set |capability_| to nullptr avoid having to expect the full
+  // behaviour caused by this call.
+  if (capability_) {
+    capability_->OnOperatorChanged();
+  }
+
+  const MobileOperatorInfo *home_provider_info =
+      cellular_->home_provider_info();
+  const MobileOperatorInfo *serving_operator_info =
+      cellular_->serving_operator_info();
+
+  const bool home_provider_known =
+      home_provider_info->IsMobileNetworkOperatorKnown();
+  const bool serving_operator_known =
+      serving_operator_info->IsMobileNetworkOperatorKnown();
+
+  if (home_provider_known) {
+    cellular_->UpdateHomeProvider(home_provider_info);
+  } else if (serving_operator_known) {
+    SLOG(cellular_, 2) << "Serving provider proxying in for home provider.";
+    cellular_->UpdateHomeProvider(serving_operator_info);
+  }
+
+  if (serving_operator_known) {
+    if (home_provider_known) {
+      cellular_->UpdateServingOperator(serving_operator_info,
+                                       home_provider_info);
+    } else {
+      cellular_->UpdateServingOperator(serving_operator_info, nullptr);
+    }
+  } else if (home_provider_known) {
+    cellular_->UpdateServingOperator(home_provider_info, home_provider_info);
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/cellular.h b/cellular/cellular.h
new file mode 100644
index 0000000..4b27add
--- /dev/null
+++ b/cellular/cellular.h
@@ -0,0 +1,570 @@
+// 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_CELLULAR_H_
+#define SHILL_CELLULAR_CELLULAR_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/mobile_operator_info.h"
+#include "shill/cellular/modem_info.h"
+#include "shill/cellular/modem_proxy_interface.h"
+#include "shill/dbus_properties.h"
+#include "shill/device.h"
+#include "shill/event_dispatcher.h"
+#include "shill/metrics.h"
+#include "shill/refptr_types.h"
+#include "shill/rpc_task.h"
+
+namespace shill {
+
+class CellularCapability;
+class Error;
+class ExternalTask;
+class MobileOperatorInfo;
+class PPPDeviceFactory;
+class ProxyFactory;
+
+class Cellular : public Device, public RPCTaskDelegate {
+ public:
+  enum Type {
+    kTypeGSM,
+    kTypeCDMA,
+    kTypeUniversal,  // ModemManager1
+    kTypeUniversalCDMA,
+    kTypeInvalid,
+  };
+
+  // The device states progress linearly from Disabled to Linked.
+  enum State {
+    // This is the initial state of the modem and indicates that the modem radio
+    // is not turned on.
+    kStateDisabled,
+    // This state indicates that the modem radio is turned on, and it should be
+    // possible to measure signal strength.
+    kStateEnabled,
+    // The modem has registered with a network and has signal quality
+    // measurements. A cellular service object is created.
+    kStateRegistered,
+    // The modem has connected to a network.
+    kStateConnected,
+    // The network interface is UP.
+    kStateLinked,
+  };
+
+  // This enum must be kept in sync with ModemManager's MMModemState enum.
+  enum ModemState {
+    kModemStateFailed = -1,
+    kModemStateUnknown = 0,
+    kModemStateInitializing = 1,
+    kModemStateLocked = 2,
+    kModemStateDisabled = 3,
+    kModemStateDisabling = 4,
+    kModemStateEnabling = 5,
+    kModemStateEnabled = 6,
+    kModemStateSearching = 7,
+    kModemStateRegistered = 8,
+    kModemStateDisconnecting = 9,
+    kModemStateConnecting = 10,
+    kModemStateConnected = 11,
+  };
+
+  // |owner| is the ModemManager DBus service owner (e.g., ":1.17").
+  // |path| is the ModemManager.Modem DBus object path (e.g.,
+  // "/org/chromium/ModemManager/Gobi/0").
+  // |service| is the modem mananager service name (e.g.,
+  // /org/chromium/ModemManager or /org/freedesktop/ModemManager1).
+  Cellular(ModemInfo *modem_info,
+           const std::string &link_name,
+           const std::string &address,
+           int interface_index,
+           Type type,
+           const std::string &owner,
+           const std::string &service,
+           const std::string &path,
+           ProxyFactory *proxy_factory);
+  ~Cellular() override;
+
+  // Load configuration for the device from |storage|.
+  virtual bool Load(StoreInterface *storage);
+
+  // Save configuration for the device to |storage|.
+  virtual bool Save(StoreInterface *storage);
+
+  // Asynchronously connects the modem to the network. Populates |error| on
+  // failure, leaves it unchanged otherwise.
+  virtual void Connect(Error *error);
+
+  // Asynchronously disconnects the modem from the network and populates
+  // |error| on failure, leaves it unchanged otherwise.
+  virtual void Disconnect(Error *error, const char *reason);
+
+  // Asynchronously activates the modem. Returns an error on failure.
+  void Activate(const std::string &carrier, Error *error,
+                const ResultCallback &callback);
+
+  // Performs the necessary steps to bring the service to the activated state,
+  // once an online payment has been done.
+  void CompleteActivation(Error *error);
+
+  const CellularServiceRefPtr &service() const { return service_; }
+  MobileOperatorInfo *home_provider_info() const {
+    return home_provider_info_.get();
+  }
+  MobileOperatorInfo *serving_operator_info() const {
+    return serving_operator_info_.get();
+  }
+
+  // Deregisters and destructs the current service and destroys the connection,
+  // if any. This also eliminates the circular references between this device
+  // and the associated service, allowing eventual device destruction.
+  virtual void DestroyService();
+
+  static std::string GetStateString(State state);
+  static std::string GetModemStateString(ModemState modem_state);
+
+  std::string CreateDefaultFriendlyServiceName();
+  bool IsDefaultFriendlyServiceName(const std::string &service_name) const;
+
+  // Update the home provider from the information in |operator_info|. This
+  // information may be from the SIM / received OTA.
+  void UpdateHomeProvider(const MobileOperatorInfo *operator_info);
+  // Update the serving operator using information in |operator_info|.
+  // Additionally, if |home_provider_info| is not nullptr, use it to come up
+  // with a better name.
+  void UpdateServingOperator(const MobileOperatorInfo *operator_info,
+                             const MobileOperatorInfo *home_provider_info);
+
+  State state() const { return state_; }
+
+  void set_modem_state(ModemState state) { modem_state_ = state; }
+  ModemState modem_state() const { return modem_state_; }
+  bool IsUnderlyingDeviceEnabled() const;
+  bool IsModemRegistered() const;
+  static bool IsEnabledModemState(ModemState state);
+
+  void HandleNewSignalQuality(uint32_t strength);
+
+  // Processes a change in the modem registration state, possibly creating,
+  // 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);
+
+  // Inherited from Device.
+  void Start(Error *error,
+             const EnabledStateChangedCallback &callback) override;
+  void Stop(Error *error, const EnabledStateChangedCallback &callback) override;
+  void LinkEvent(unsigned int flags, unsigned int change) override;
+  void Scan(ScanType /*scan_type*/, Error *error,
+            const std::string &/*reason*/) override;
+  void RegisterOnNetwork(const std::string &network_id,
+                         Error *error,
+                         const ResultCallback &callback) override;
+  void RequirePIN(const std::string &pin, bool require,
+                  Error *error, const ResultCallback &callback) override;
+  void EnterPIN(const std::string &pin,
+                Error *error, const ResultCallback &callback) override;
+  void UnblockPIN(const std::string &unblock_code,
+                  const std::string &pin,
+                  Error *error, const ResultCallback &callback) override;
+  void ChangePIN(const std::string &old_pin,
+                 const std::string &new_pin,
+                 Error *error, const ResultCallback &callback) override;
+  void Reset(Error *error, const ResultCallback &callback) override;
+  void SetCarrier(const std::string &carrier,
+                  Error *error, const ResultCallback &callback) override;
+  bool IsIPv6Allowed() const override;
+  void DropConnection() override;
+  void SetServiceState(Service::ConnectState state) override;
+  void SetServiceFailure(Service::ConnectFailure failure_state) override;
+  void SetServiceFailureSilent(Service::ConnectFailure failure_state) override;
+  void OnBeforeSuspend(const ResultCallback &callback) override;
+  void OnAfterResume() override;
+
+  void StartModemCallback(const EnabledStateChangedCallback &callback,
+                          const Error &error);
+  void StopModemCallback(const EnabledStateChangedCallback &callback,
+                         const Error &error);
+  void OnDisabled();
+  void OnEnabled();
+  void OnConnecting();
+  void OnConnected();
+  void OnConnectFailed(const Error &error);
+  void OnDisconnected();
+  void OnDisconnectFailed();
+  std::string GetTechnologyFamily(Error *error);
+  void OnModemStateChanged(ModemState new_state);
+  void OnScanReply(const Stringmaps &found_networks, const Error &error);
+
+  // accessor to read the allow roaming property
+  bool allow_roaming_property() const { return allow_roaming_; }
+  // Is the underlying device in the process of activating?
+  bool IsActivating() const;
+
+  // Initiate PPP link. Called from capabilities.
+  virtual void StartPPP(const std::string &serial_device);
+  // Callback for |ppp_task_|.
+  virtual void OnPPPDied(pid_t pid, int exit);
+  // Implements RPCTaskDelegate, for |ppp_task_|.
+  void GetLogin(std::string *user, std::string *password) override;
+  void Notify(const std::string &reason,
+              const std::map<std::string, std::string> &dict) override;
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // DBus Properties exposed by the Device interface of shill.
+  void RegisterProperties();
+
+  // getters
+  const std::string &dbus_owner() const { return dbus_owner_; }
+  const std::string &dbus_service() const { return dbus_service_; }
+  const std::string &dbus_path() const { return dbus_path_; }
+  const Stringmap &home_provider() const { return home_provider_; }
+  const std::string &carrier() const { return carrier_; }
+  bool scanning_supported() const { return scanning_supported_; }
+  const std::string &esn() const { return esn_; }
+  const std::string &firmware_revision() const { return firmware_revision_; }
+  const std::string &hardware_revision() const { return hardware_revision_; }
+  const std::string &imei() const { return imei_; }
+  const std::string &imsi() const { return imsi_; }
+  const std::string &mdn() const { return mdn_; }
+  const std::string &meid() const { return meid_; }
+  const std::string &min() const { return min_; }
+  const std::string &manufacturer() const { return manufacturer_; }
+  const std::string &model_id() const { return model_id_; }
+  const std::string &mm_plugin() const { return mm_plugin_; }
+  bool scanning() const { return scanning_; }
+
+  const std::string &selected_network() const { return selected_network_; }
+  const Stringmaps &found_networks() const { return found_networks_; }
+  bool provider_requires_roaming() const { return provider_requires_roaming_; }
+  bool sim_present() const { return sim_present_; }
+  const Stringmaps &apn_list() const { return apn_list_; }
+  const std::string &sim_identifier() const { return sim_identifier_; }
+
+  const Strings &supported_carriers() const { return supported_carriers_; }
+  uint16_t prl_version() const { return prl_version_; }
+
+  // setters
+  void set_home_provider(const Stringmap &home_provider);
+  void set_carrier(const std::string &carrier);
+  void set_scanning_supported(bool scanning_supported);
+  void set_esn(const std::string &esn);
+  void set_firmware_revision(const std::string &firmware_revision);
+  void set_hardware_revision(const std::string &hardware_revision);
+  void set_imei(const std::string &imei);
+  void set_imsi(const std::string &imsi);
+  void set_mdn(const std::string &mdn);
+  void set_meid(const std::string &meid);
+  void set_min(const std::string &min);
+  void set_manufacturer(const std::string &manufacturer);
+  void set_model_id(const std::string &model_id);
+  void set_mm_plugin(const std::string &mm_plugin);
+  void set_scanning(bool scanning);
+
+  void set_selected_network(const std::string &selected_network);
+  void clear_found_networks();
+  void set_found_networks(const Stringmaps &found_networks);
+  void set_provider_requires_roaming(bool provider_requires_roaming);
+  void set_sim_present(bool sim_present);
+  void set_apn_list(const Stringmaps &apn_list);
+  void set_sim_identifier(const std::string &sim_identifier);
+
+  void set_supported_carriers(const Strings &supported_carriers);
+  void set_prl_version(uint16_t prl_version);
+
+  // Takes ownership.
+  void set_home_provider_info(MobileOperatorInfo *home_provider_info);
+  // Takes ownership.
+  void set_serving_operator_info(MobileOperatorInfo *serving_operator_info);
+
+ private:
+  friend class ActivePassiveOutOfCreditsDetectorTest;
+  friend class CellularTest;
+  friend class CellularCapabilityTest;
+  friend class CellularCapabilityCDMATest;
+  friend class CellularCapabilityGSMTest;
+  friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
+  friend class CellularServiceTest;
+  friend class ModemTest;
+  friend class SubscriptionStateOutOfCreditsDetectorTest;
+  FRIEND_TEST(CellularCapabilityCDMATest, GetRegistrationState);
+  FRIEND_TEST(CellularCapabilityGSMTest, AllowRoaming);
+  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(CellularCapabilityUniversalCDMATest, OnCDMARegistrationChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, Connect);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, IsServiceActivationRequired);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StartModemAlreadyEnabled);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StopModemConnected);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdatePendingActivationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateRegistrationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateRegistrationStateModemNotConnected);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateScanningProperty);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateServiceActivationState);
+  FRIEND_TEST(CellularTest, ChangeServiceState);
+  FRIEND_TEST(CellularTest, ChangeServiceStatePPP);
+  FRIEND_TEST(CellularTest, CreateService);
+  FRIEND_TEST(CellularTest, Connect);
+  FRIEND_TEST(CellularTest, ConnectFailure);
+  FRIEND_TEST(CellularTest, ConnectFailureNoService);
+  FRIEND_TEST(CellularTest, ConnectSuccessNoService);
+  FRIEND_TEST(CellularTest, CustomSetterNoopChange);
+  FRIEND_TEST(CellularTest, DisableModem);
+  FRIEND_TEST(CellularTest, Disconnect);
+  FRIEND_TEST(CellularTest, DisconnectFailure);
+  FRIEND_TEST(CellularTest, DisconnectWithCallback);
+  FRIEND_TEST(CellularTest, DropConnection);
+  FRIEND_TEST(CellularTest, DropConnectionPPP);
+  FRIEND_TEST(CellularTest, EnableTrafficMonitor);
+  FRIEND_TEST(CellularTest, EstablishLinkDHCP);
+  FRIEND_TEST(CellularTest, EstablishLinkPPP);
+  FRIEND_TEST(CellularTest, EstablishLinkStatic);
+  FRIEND_TEST(CellularTest, FriendlyServiceName);
+  FRIEND_TEST(CellularTest,
+              HandleNewRegistrationStateForServiceRequiringActivation);
+  FRIEND_TEST(CellularTest, HomeProviderServingOperator);
+  FRIEND_TEST(CellularTest, LinkEventUpWithPPP);
+  FRIEND_TEST(CellularTest, LinkEventUpWithoutPPP);
+  FRIEND_TEST(CellularTest, LinkEventWontDestroyService);
+  FRIEND_TEST(CellularTest, ModemStateChangeDisable);
+  FRIEND_TEST(CellularTest, ModemStateChangeEnable);
+  FRIEND_TEST(CellularTest, ModemStateChangeStaleConnected);
+  FRIEND_TEST(CellularTest, ModemStateChangeValidConnected);
+  FRIEND_TEST(CellularTest, Notify);
+  FRIEND_TEST(CellularTest, OnAfterResumeDisableInProgressWantDisabled);
+  FRIEND_TEST(CellularTest, OnAfterResumeDisableQueuedWantEnabled);
+  FRIEND_TEST(CellularTest, OnAfterResumeDisabledWantDisabled);
+  FRIEND_TEST(CellularTest, OnAfterResumeDisabledWantEnabled);
+  FRIEND_TEST(CellularTest, OnAfterResumePowerDownInProgressWantEnabled);
+  FRIEND_TEST(CellularTest, OnConnectionHealthCheckerResult);
+  FRIEND_TEST(CellularTest, OnPPPDied);
+  FRIEND_TEST(CellularTest, PPPConnectionFailedAfterAuth);
+  FRIEND_TEST(CellularTest, PPPConnectionFailedBeforeAuth);
+  FRIEND_TEST(CellularTest, PPPConnectionFailedDuringAuth);
+  FRIEND_TEST(CellularTest, ScanAsynchronousFailure);
+  FRIEND_TEST(CellularTest, ScanImmediateFailure);
+  FRIEND_TEST(CellularTest, ScanSuccess);
+  FRIEND_TEST(CellularTest, SetAllowRoaming);
+  FRIEND_TEST(CellularTest, StartModemCallback);
+  FRIEND_TEST(CellularTest, StartModemCallbackFail);
+  FRIEND_TEST(CellularTest, StopModemCallback);
+  FRIEND_TEST(CellularTest, StopModemCallbackFail);
+  FRIEND_TEST(CellularTest, StopPPPOnDisconnect);
+  FRIEND_TEST(CellularTest, StopPPPOnTermination);
+  FRIEND_TEST(CellularTest, StorageIdentifier);
+  FRIEND_TEST(CellularTest, StartConnected);
+  FRIEND_TEST(CellularTest, StartCDMARegister);
+  FRIEND_TEST(CellularTest, StartGSMRegister);
+  FRIEND_TEST(CellularTest, StartLinked);
+  FRIEND_TEST(CellularTest, StartPPP);
+  FRIEND_TEST(CellularTest, StartPPPAfterEthernetUp);
+  FRIEND_TEST(CellularTest, StartPPPAlreadyStarted);
+  FRIEND_TEST(CellularTest, UpdateScanning);
+  FRIEND_TEST(Modem1Test, CreateDeviceMM1);
+
+  class MobileOperatorInfoObserver : public MobileOperatorInfo::Observer {
+   public:
+    // |cellular| must have lifespan longer than this object. In practice this
+    // is enforced because |cellular| owns this object.
+    explicit MobileOperatorInfoObserver(Cellular *cellular);
+    ~MobileOperatorInfoObserver() override;
+
+    void set_capability(CellularCapability *capability) {
+      capability_ = capability;
+    }
+
+    // Inherited from MobileOperatorInfo::Observer
+    void OnOperatorChanged() override;
+
+   private:
+    Cellular *const cellular_;
+    // Owned by |Cellular|.
+    CellularCapability *capability_;
+
+    DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoObserver);
+  };
+
+  // Names of properties in storage
+  static const char kAllowRoaming[];
+
+  // the |kScanningProperty| exposed by Cellular device is sticky false. Every
+  // time it is set to true, it must be reset to false after a time equal to
+  // this constant.
+  static const int64_t kDefaultScanningTimeoutMilliseconds;
+
+  // Generic service name prefix, shown when the correct carrier name is
+  // unknown.
+  static const char kGenericServiceNamePrefix[];
+  static unsigned int friendly_service_name_id_;
+
+  void SetState(State state);
+
+  // Invoked when the modem is connected to the cellular network to transition
+  // to the network-connected state and bring the network interface up.
+  void EstablishLink();
+
+  void InitCapability(Type type);
+
+  void CreateService();
+
+  // HelpRegisterDerived*: Expose a property over RPC, with the name |name|.
+  //
+  // Reads of the property will be handled by invoking |get|.
+  // Writes to the property will be handled by invoking |set|.
+  // Clearing the property will be handled by PropertyStore.
+  void HelpRegisterDerivedBool(
+      const std::string &name,
+      bool(Cellular::*get)(Error *error),
+      bool(Cellular::*set)(const bool &value, Error *error));
+  void HelpRegisterConstDerivedString(
+      const std::string &name,
+      std::string(Cellular::*get)(Error *error));
+
+  void OnConnectReply(const Error &error);
+  void OnDisconnectReply(const Error &error);
+
+  // DBUS accessors to read/modify the allow roaming property
+  bool GetAllowRoaming(Error */*error*/) { return allow_roaming_; }
+  bool SetAllowRoaming(const bool &value, Error *error);
+
+  // When shill terminates or ChromeOS suspends, this function is called to
+  // disconnect from the cellular network.
+  void StartTermination();
+
+  // This method is invoked upon the completion of StartTermination().
+  void OnTerminationCompleted(const Error &error);
+
+  // This function does the final cleanup once a disconnect request terminates.
+  // Returns true, if the device state is successfully changed.
+  bool DisconnectCleanup();
+
+  // Executed after the asynchronous CellularCapability::StartModem
+  // call from OnAfterResume completes.
+  static void LogRestartModemResult(const Error &error);
+
+  // Terminate the pppd process associated with this Device, and remove the
+  // association between the PPPDevice and our CellularService. If this
+  // Device is not using PPP, the method has no effect.
+  void StopPPP();
+
+  // Handlers for PPP events. Dispatched from Notify().
+  void OnPPPAuthenticated();
+  void OnPPPAuthenticating();
+  void OnPPPConnected(const std::map<std::string, std::string> &params);
+  void OnPPPDisconnected();
+
+  void UpdateScanning();
+
+  base::WeakPtrFactory<Cellular> weak_ptr_factory_;
+
+  State state_;
+  ModemState modem_state_;
+
+  std::unique_ptr<CellularCapability> capability_;
+
+  // Operator info objects. These objects receive updates as we receive
+  // information about the network operators from the SIM or OTA. In turn, they
+  // send out updates through their observer interfaces whenever the identity of
+  // the network operator changes, or any other property of the operator
+  // changes.
+  std::unique_ptr<MobileOperatorInfo> home_provider_info_;
+  std::unique_ptr<MobileOperatorInfo> serving_operator_info_;
+  // Observer object to listen to updates from the operator info objects.
+  std::unique_ptr<MobileOperatorInfoObserver> mobile_operator_info_observer_;
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // All DBus Properties exposed by the Cellular device.
+  // Properties common to GSM and CDMA modems.
+  const std::string dbus_owner_;  // :x.y
+  const std::string dbus_service_;  // org.*.ModemManager*
+  const std::string dbus_path_;  // ModemManager.Modem
+  Stringmap home_provider_;
+
+  bool scanning_supported_;
+  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 mm_plugin_;
+  bool scanning_;
+
+  // GSM only properties.
+  // They are always exposed but are non empty only for GSM technology modems.
+  std::string selected_network_;
+  Stringmaps found_networks_;
+  bool provider_requires_roaming_;
+  uint16_t scan_interval_;
+  bool sim_present_;
+  Stringmaps apn_list_;
+  std::string sim_identifier_;
+
+  // CDMA only properties.
+  uint16_t prl_version_;
+
+  // This property is specific to Gobi modems.
+  Strings supported_carriers_;
+  // End of DBus properties.
+  // ///////////////////////////////////////////////////////////////////////////
+
+  ModemInfo *modem_info_;
+  const Type type_;
+  ProxyFactory *proxy_factory_;
+  PPPDeviceFactory *ppp_device_factory_;
+
+  CellularServiceRefPtr service_;
+
+  // User preference to allow or disallow roaming
+  bool allow_roaming_;
+
+  // Track whether a user initiated scan is in prgoress (initiated via ::Scan)
+  bool proposed_scan_in_progress_;
+
+  // Flag indicating that a disconnect has been explicitly requested.
+  bool explicit_disconnect_;
+
+  std::unique_ptr<ExternalTask> ppp_task_;
+  PPPDeviceRefPtr ppp_device_;
+  bool is_ppp_authenticating_;
+
+  // Sometimes modems may be stuck in the SEARCHING state during the lack of
+  // presence of a network. During this indefinite duration of time, keeping
+  // the Device.Scanning property as |true| causes a bad user experience.
+  // This callback sets it to |false| after a timeout period has passed.
+  base::CancelableClosure scanning_timeout_callback_;
+  int64_t scanning_timeout_milliseconds_;
+
+  DISALLOW_COPY_AND_ASSIGN(Cellular);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_H_
diff --git a/cellular/cellular_bearer.cc b/cellular/cellular_bearer.cc
new file mode 100644
index 0000000..0e9ba44
--- /dev/null
+++ b/cellular/cellular_bearer.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/cellular_bearer.h"
+
+#include <ModemManager/ModemManager.h>
+
+#include <base/bind.h>
+
+#include "shill/dbus_properties.h"
+#include "shill/dbus_properties_proxy.h"
+#include "shill/logging.h"
+#include "shill/proxy_factory.h"
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(const CellularBearer *c) { return "(cellular_bearer)"; }
+}
+
+namespace {
+
+const char kPropertyAddress[] = "address";
+const char kPropertyDNS1[] = "dns1";
+const char kPropertyDNS2[] = "dns2";
+const char kPropertyDNS3[] = "dns3";
+const char kPropertyGateway[] = "gateway";
+const char kPropertyMethod[] = "method";
+const char kPropertyPrefix[] = "prefix";
+
+IPConfig::Method ConvertMMBearerIPConfigMethod(uint32_t method) {
+  switch (method) {
+    case MM_BEARER_IP_METHOD_PPP:
+      return IPConfig::kMethodPPP;
+    case MM_BEARER_IP_METHOD_STATIC:
+      return IPConfig::kMethodStatic;
+    case MM_BEARER_IP_METHOD_DHCP:
+      return IPConfig::kMethodDHCP;
+    default:
+      return IPConfig::kMethodUnknown;
+  }
+}
+
+}  // namespace
+
+CellularBearer::CellularBearer(ProxyFactory *proxy_factory,
+                               const string &dbus_path,
+                               const string &dbus_service)
+    : proxy_factory_(proxy_factory),
+      dbus_path_(dbus_path),
+      dbus_service_(dbus_service),
+      connected_(false),
+      ipv4_config_method_(IPConfig::kMethodUnknown),
+      ipv6_config_method_(IPConfig::kMethodUnknown) {
+  CHECK(proxy_factory_);
+}
+
+CellularBearer::~CellularBearer() {}
+
+bool CellularBearer::Init() {
+  SLOG(this, 3) << __func__ << ": path='" << dbus_path_
+                << "', service='" << dbus_service_ << "'";
+
+  dbus_properties_proxy_.reset(
+      proxy_factory_->CreateDBusPropertiesProxy(dbus_path_, dbus_service_));
+  // It is possible that ProxyFactory::CreateDBusPropertiesProxy() returns
+  // nullptr as the bearer DBus object may no longer exist.
+  if (!dbus_properties_proxy_) {
+    LOG(WARNING) << "Failed to create DBus properties proxy for bearer '"
+                 << dbus_path_ << "'. Bearer is likely gone.";
+    return false;
+  }
+
+  dbus_properties_proxy_->set_properties_changed_callback(base::Bind(
+      &CellularBearer::OnDBusPropertiesChanged, base::Unretained(this)));
+  UpdateProperties();
+  return true;
+}
+
+void CellularBearer::GetIPConfigMethodAndProperties(
+    const DBusPropertiesMap &properties,
+    IPAddress::Family address_family,
+    IPConfig::Method *ipconfig_method,
+    std::unique_ptr<IPConfig::Properties> *ipconfig_properties) const {
+  DCHECK(ipconfig_method);
+  DCHECK(ipconfig_properties);
+
+  uint32_t method;
+  if (!DBusProperties::GetUint32(properties, kPropertyMethod, &method)) {
+    SLOG(this, 2) << "Bearer '" << dbus_path_
+                  << "' does not specify an IP configuration method.";
+    method = MM_BEARER_IP_METHOD_UNKNOWN;
+  }
+  *ipconfig_method = ConvertMMBearerIPConfigMethod(method);
+  ipconfig_properties->reset();
+
+  if (*ipconfig_method != IPConfig::kMethodStatic)
+    return;
+
+  string address, gateway;
+  if (!DBusProperties::GetString(properties, kPropertyAddress, &address) ||
+      !DBusProperties::GetString(properties, kPropertyGateway, &gateway)) {
+    SLOG(this, 2) << "Bearer '" << dbus_path_
+                  << "' static IP configuration does not specify valid "
+                         "address/gateway information.";
+    *ipconfig_method = IPConfig::kMethodUnknown;
+    return;
+  }
+
+  ipconfig_properties->reset(new IPConfig::Properties);
+  (*ipconfig_properties)->address_family = address_family;
+  (*ipconfig_properties)->address = address;
+  (*ipconfig_properties)->gateway = gateway;
+
+  uint32_t prefix;
+  if (!DBusProperties::GetUint32(properties, kPropertyPrefix, &prefix)) {
+    prefix = IPAddress::GetMaxPrefixLength(address_family);
+  }
+  (*ipconfig_properties)->subnet_prefix = prefix;
+
+  string dns;
+  if (DBusProperties::GetString(properties, kPropertyDNS1, &dns)) {
+    (*ipconfig_properties)->dns_servers.push_back(dns);
+  }
+  if (DBusProperties::GetString(properties, kPropertyDNS2, &dns)) {
+    (*ipconfig_properties)->dns_servers.push_back(dns);
+  }
+  if (DBusProperties::GetString(properties, kPropertyDNS3, &dns)) {
+    (*ipconfig_properties)->dns_servers.push_back(dns);
+  }
+}
+
+void CellularBearer::ResetProperties() {
+  connected_ = false;
+  data_interface_.clear();
+  ipv4_config_method_ = IPConfig::kMethodUnknown;
+  ipv4_config_properties_.reset();
+  ipv6_config_method_ = IPConfig::kMethodUnknown;
+  ipv6_config_properties_.reset();
+}
+
+void CellularBearer::UpdateProperties() {
+  ResetProperties();
+
+  if (!dbus_properties_proxy_)
+    return;
+
+  DBusPropertiesMap properties =
+      dbus_properties_proxy_->GetAll(MM_DBUS_INTERFACE_BEARER);
+  if (properties.empty()) {
+    LOG(WARNING) << "Could not get properties of bearer '" << dbus_path_
+                 << "'. Bearer is likely gone and thus ignored.";
+    return;
+  }
+
+  OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                          vector<string>());
+}
+
+void CellularBearer::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &/*invalidated_properties*/) {
+  SLOG(this, 3) << __func__ << ": path=" << dbus_path_
+                << ", interface=" << interface;
+
+  if (interface != MM_DBUS_INTERFACE_BEARER)
+    return;
+
+  bool connected;
+  if (DBusProperties::GetBool(
+        changed_properties, MM_BEARER_PROPERTY_CONNECTED, &connected)) {
+    connected_ = connected;
+  }
+
+  string data_interface;
+  if (DBusProperties::GetString(
+        changed_properties, MM_BEARER_PROPERTY_INTERFACE, &data_interface)) {
+    data_interface_ = data_interface;
+  }
+
+  DBusPropertiesMap ipconfig;
+  if (DBusProperties::GetDBusPropertiesMap(
+        changed_properties, MM_BEARER_PROPERTY_IP4CONFIG, &ipconfig)) {
+    GetIPConfigMethodAndProperties(ipconfig,
+                                   IPAddress::kFamilyIPv4,
+                                   &ipv4_config_method_,
+                                   &ipv4_config_properties_);
+  }
+  if (DBusProperties::GetDBusPropertiesMap(
+        changed_properties, MM_BEARER_PROPERTY_IP6CONFIG, &ipconfig)) {
+    GetIPConfigMethodAndProperties(ipconfig,
+                                   IPAddress::kFamilyIPv6,
+                                   &ipv6_config_method_,
+                                   &ipv6_config_properties_);
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_bearer.h b/cellular/cellular_bearer.h
new file mode 100644
index 0000000..b95a8c8
--- /dev/null
+++ b/cellular/cellular_bearer.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_CELLULAR_CELLULAR_BEARER_H_
+#define SHILL_CELLULAR_CELLULAR_BEARER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>
+
+#include "shill/dbus_properties.h"
+#include "shill/ipconfig.h"
+
+namespace shill {
+
+class DBusPropertiesProxyInterface;
+class ProxyFactory;
+
+// A class for observing property changes of a bearer object exposed by
+// ModemManager.
+class CellularBearer {
+ public:
+  // Constructs a cellular bearer for observing property changes of a
+  // corresponding bearer object, at the DBus path |dbus_path| of DBus service
+  // |dbus_service|,  exposed by ModemManager. The ownership of |proxy_factory|
+  // is not transferred, and should outlive this object.
+  //
+  // TODO(benchan): Use a context object approach to pass objects like
+  // ProxyFactory through constructor.
+  CellularBearer(ProxyFactory *proxy_factory,
+                 const std::string &dbus_path,
+                 const std::string &dbus_service);
+  ~CellularBearer();
+
+  // Initializes this object by creating a DBus properties proxy to observe
+  // property changes of the corresponding bearer object exposed by ModemManager
+  // and also fetching the current properties of the bearer.  Returns true on
+  // success or false if it fails to the DBus properties proxy.
+  bool Init();
+
+  // Callback upon DBus property changes of the bearer.
+  void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
+
+  const std::string &dbus_path() const { return dbus_path_; }
+  const std::string &dbus_service() const { return dbus_service_; }
+
+  bool connected() const { return connected_; }
+  const std::string &data_interface() const { return data_interface_; }
+  IPConfig::Method ipv4_config_method() const { return ipv4_config_method_; }
+  const IPConfig::Properties *ipv4_config_properties() const {
+    return ipv4_config_properties_.get();
+  }
+  IPConfig::Method ipv6_config_method() const { return ipv6_config_method_; }
+  const IPConfig::Properties *ipv6_config_properties() const {
+    return ipv6_config_properties_.get();
+  }
+
+ private:
+  friend class CellularBearerTest;
+  FRIEND_TEST(CellularTest, EstablishLinkDHCP);
+  FRIEND_TEST(CellularTest, EstablishLinkPPP);
+  FRIEND_TEST(CellularTest, EstablishLinkStatic);
+
+  // Gets the IP configuration method and properties from |properties|.
+  // |address_family| specifies the IP address family of the configuration.
+  // |ipconfig_method| and |ipconfig_properties| are used to return the IP
+  // configuration method and properties and should be non-NULL.
+  void GetIPConfigMethodAndProperties(
+      const DBusPropertiesMap &properties,
+      IPAddress::Family address_family,
+      IPConfig::Method *ipconfig_method,
+      std::unique_ptr<IPConfig::Properties> *ipconfig_properties) const;
+
+  // Resets bearer properties.
+  void ResetProperties();
+
+  // Updates bearer properties by fetching the current properties of the
+  // corresponding bearer object exposed by ModemManager over DBus.
+  void UpdateProperties();
+
+  // Setters for unit tests.
+  void set_connected(bool connected) { connected_ = connected; }
+  void set_data_interface(const std::string &data_interface) {
+    data_interface_ = data_interface;
+  }
+  void set_ipv4_config_method(IPConfig::Method ipv4_config_method) {
+    ipv4_config_method_ = ipv4_config_method;
+  }
+  void set_ipv4_config_properties(
+      std::unique_ptr<IPConfig::Properties> ipv4_config_properties) {
+    ipv4_config_properties_ = std::move(ipv4_config_properties);
+  }
+  void set_ipv6_config_method(IPConfig::Method ipv6_config_method) {
+    ipv6_config_method_ = ipv6_config_method;
+  }
+  void set_ipv6_config_properties(
+      std::unique_ptr<IPConfig::Properties> ipv6_config_properties) {
+    ipv6_config_properties_ = std::move(ipv6_config_properties);
+  }
+
+  ProxyFactory *proxy_factory_;
+  std::string dbus_path_;
+  std::string dbus_service_;
+  std::unique_ptr<DBusPropertiesProxyInterface> dbus_properties_proxy_;
+  bool connected_;
+  std::string data_interface_;
+
+  // If |ipv4_config_method_| is set to |IPConfig::kMethodStatic|,
+  // |ipv4_config_properties_| is guaranteed to contain valid IP configuration
+  // properties. Otherwise, |ipv4_config_properties_| is set to nullptr.
+  // |ipv6_config_properties_| is handled similarly.
+  IPConfig::Method ipv4_config_method_;
+  std::unique_ptr<IPConfig::Properties> ipv4_config_properties_;
+  IPConfig::Method ipv6_config_method_;
+  std::unique_ptr<IPConfig::Properties> ipv6_config_properties_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularBearer);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_BEARER_H_
diff --git a/cellular/cellular_bearer_unittest.cc b/cellular/cellular_bearer_unittest.cc
new file mode 100644
index 0000000..f21bfab
--- /dev/null
+++ b/cellular/cellular_bearer_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/cellular_bearer.h"
+
+#include <ModemManager/ModemManager.h>
+
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_proxy_factory.h"
+#include "shill/testing.h"
+
+using std::string;
+using std::vector;
+using testing::Return;
+using testing::ReturnNull;
+using testing::_;
+
+namespace shill {
+
+namespace {
+
+const char kBearerDBusPath[] = "/org/freedesktop/ModemManager/Bearer/0";
+const char kBearerDBusService[] = "org.freedesktop.ModemManager";
+const char kDataInterface[] = "/dev/ppp0";
+const char kIPv4Address[] = "10.0.0.1";
+const char kIPv4Gateway[] = "10.0.0.254";
+const int kIPv4SubnetPrefix = 8;
+const char *const kIPv4DNS[] = { "10.0.0.2", "8.8.4.4", "8.8.8.8" };
+const char kIPv6Address[] = "0:0:0:0:0:ffff:a00:1";
+const char kIPv6Gateway[] = "0:0:0:0:0:ffff:a00:fe";
+const int kIPv6SubnetPrefix = 16;
+const char *const kIPv6DNS[] = {
+  "0:0:0:0:0:ffff:a00:fe", "0:0:0:0:0:ffff:808:404", "0:0:0:0:0:ffff:808:808"
+};
+
+}  // namespace
+
+class CellularBearerTest : public testing::Test {
+ public:
+  CellularBearerTest()
+      : proxy_factory_(new MockProxyFactory()),
+        bearer_(proxy_factory_.get(), kBearerDBusPath, kBearerDBusService) {}
+
+ protected:
+  void VerifyDefaultProperties() {
+    EXPECT_EQ(kBearerDBusPath, bearer_.dbus_path());
+    EXPECT_EQ(kBearerDBusService, bearer_.dbus_service());
+    EXPECT_FALSE(bearer_.connected());
+    EXPECT_EQ("", bearer_.data_interface());
+    EXPECT_EQ(IPConfig::kMethodUnknown, bearer_.ipv4_config_method());
+    EXPECT_EQ(nullptr, bearer_.ipv4_config_properties());;
+    EXPECT_EQ(IPConfig::kMethodUnknown, bearer_.ipv6_config_method());
+    EXPECT_EQ(nullptr, bearer_.ipv6_config_properties());;
+  }
+
+  static DBusPropertiesMap ConstructIPv4ConfigProperties(
+      MMBearerIpMethod ipconfig_method) {
+    DBusPropertiesMap ipconfig_properties;
+    ipconfig_properties["method"].writer().append_uint32(ipconfig_method);
+    if (ipconfig_method == MM_BEARER_IP_METHOD_STATIC) {
+      ipconfig_properties["address"].writer().append_string(kIPv4Address);
+      ipconfig_properties["gateway"].writer().append_string(kIPv4Gateway);
+      ipconfig_properties["prefix"].writer().append_uint32(kIPv4SubnetPrefix);
+      ipconfig_properties["dns1"].writer().append_string(kIPv4DNS[0]);
+      ipconfig_properties["dns2"].writer().append_string(kIPv4DNS[1]);
+      ipconfig_properties["dns3"].writer().append_string(kIPv4DNS[2]);
+    }
+    return ipconfig_properties;
+  }
+
+  static DBusPropertiesMap ConstructIPv6ConfigProperties(
+      MMBearerIpMethod ipconfig_method) {
+    DBusPropertiesMap ipconfig_properties;
+    ipconfig_properties["method"].writer().append_uint32(ipconfig_method);
+    if (ipconfig_method == MM_BEARER_IP_METHOD_STATIC) {
+      ipconfig_properties["address"].writer().append_string(kIPv6Address);
+      ipconfig_properties["gateway"].writer().append_string(kIPv6Gateway);
+      ipconfig_properties["prefix"].writer().append_uint32(kIPv6SubnetPrefix);
+      ipconfig_properties["dns1"].writer().append_string(kIPv6DNS[0]);
+      ipconfig_properties["dns2"].writer().append_string(kIPv6DNS[1]);
+      ipconfig_properties["dns3"].writer().append_string(kIPv6DNS[2]);
+    }
+    return ipconfig_properties;
+  }
+
+  static DBusPropertiesMap ConstructBearerProperties(
+      bool connected, const string &data_interface,
+      MMBearerIpMethod ipv4_config_method,
+      MMBearerIpMethod ipv6_config_method) {
+    DBusPropertiesMap properties;
+    properties[MM_BEARER_PROPERTY_CONNECTED].writer().append_bool(connected);
+    properties[MM_BEARER_PROPERTY_INTERFACE].writer().append_string(
+        data_interface.c_str());
+
+    DBus::MessageIter writer;
+    writer = properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+    writer << ConstructIPv4ConfigProperties(ipv4_config_method);
+    writer = properties[MM_BEARER_PROPERTY_IP6CONFIG].writer();
+    writer << ConstructIPv6ConfigProperties(ipv6_config_method);
+    return properties;
+  }
+
+  void VerifyStaticIPv4ConfigMethodAndProperties() {
+    EXPECT_EQ(IPConfig::kMethodStatic, bearer_.ipv4_config_method());
+    const IPConfig::Properties *ipv4_config_properties =
+        bearer_.ipv4_config_properties();
+    ASSERT_NE(nullptr, ipv4_config_properties);;
+    EXPECT_EQ(IPAddress::kFamilyIPv4, ipv4_config_properties->address_family);
+    EXPECT_EQ(kIPv4Address, ipv4_config_properties->address);
+    EXPECT_EQ(kIPv4Gateway, ipv4_config_properties->gateway);
+    EXPECT_EQ(kIPv4SubnetPrefix, ipv4_config_properties->subnet_prefix);
+    ASSERT_EQ(3, ipv4_config_properties->dns_servers.size());
+    EXPECT_EQ(kIPv4DNS[0], ipv4_config_properties->dns_servers[0]);
+    EXPECT_EQ(kIPv4DNS[1], ipv4_config_properties->dns_servers[1]);
+    EXPECT_EQ(kIPv4DNS[2], ipv4_config_properties->dns_servers[2]);
+  }
+
+  void VerifyStaticIPv6ConfigMethodAndProperties() {
+    EXPECT_EQ(IPConfig::kMethodStatic, bearer_.ipv6_config_method());
+    const IPConfig::Properties *ipv6_config_properties =
+        bearer_.ipv6_config_properties();
+    ASSERT_NE(nullptr, ipv6_config_properties);;
+    EXPECT_EQ(IPAddress::kFamilyIPv6, ipv6_config_properties->address_family);
+    EXPECT_EQ(kIPv6Address, ipv6_config_properties->address);
+    EXPECT_EQ(kIPv6Gateway, ipv6_config_properties->gateway);
+    EXPECT_EQ(kIPv6SubnetPrefix, ipv6_config_properties->subnet_prefix);
+    ASSERT_EQ(3, ipv6_config_properties->dns_servers.size());
+    EXPECT_EQ(kIPv6DNS[0], ipv6_config_properties->dns_servers[0]);
+    EXPECT_EQ(kIPv6DNS[1], ipv6_config_properties->dns_servers[1]);
+    EXPECT_EQ(kIPv6DNS[2], ipv6_config_properties->dns_servers[2]);
+  }
+
+  std::unique_ptr<MockProxyFactory> proxy_factory_;
+  CellularBearer bearer_;
+};
+
+TEST_F(CellularBearerTest, Constructor) {
+  VerifyDefaultProperties();
+}
+
+TEST_F(CellularBearerTest, Init) {
+  // Ownership of |properties_proxy| is transferred to |bearer_| via
+  // |proxy_factory_|.
+  std::unique_ptr<MockDBusPropertiesProxy> properties_proxy(
+      new MockDBusPropertiesProxy);
+  EXPECT_CALL(*proxy_factory_.get(),
+              CreateDBusPropertiesProxy(kBearerDBusPath, kBearerDBusService))
+      .WillOnce(ReturnAndReleasePointee(&properties_proxy));
+  EXPECT_CALL(*properties_proxy.get(), set_properties_changed_callback(_))
+      .Times(1);
+  EXPECT_CALL(*properties_proxy.get(), GetAll(MM_DBUS_INTERFACE_BEARER))
+      .WillOnce(Return(ConstructBearerProperties(true, kDataInterface,
+                                                 MM_BEARER_IP_METHOD_STATIC,
+                                                 MM_BEARER_IP_METHOD_STATIC)));
+  bearer_.Init();
+  EXPECT_TRUE(bearer_.connected());
+  EXPECT_EQ(kDataInterface, bearer_.data_interface());
+  VerifyStaticIPv4ConfigMethodAndProperties();
+  VerifyStaticIPv6ConfigMethodAndProperties();
+}
+
+TEST_F(CellularBearerTest, InitAndCreateDBusPropertiesProxyFails) {
+  EXPECT_CALL(*proxy_factory_.get(),
+              CreateDBusPropertiesProxy(kBearerDBusPath, kBearerDBusService))
+      .WillOnce(ReturnNull());
+  bearer_.Init();
+  VerifyDefaultProperties();
+}
+
+TEST_F(CellularBearerTest, OnDBusPropertiesChanged) {
+  DBusPropertiesMap properties;
+
+  // If interface is not MM_DBUS_INTERFACE_BEARER, no updates should be done.
+  bearer_.OnDBusPropertiesChanged("", properties, vector<string>());
+  VerifyDefaultProperties();
+
+  properties[MM_BEARER_PROPERTY_CONNECTED].writer().append_bool(true);
+  bearer_.OnDBusPropertiesChanged("", properties, vector<string>());
+  VerifyDefaultProperties();
+
+  // Update 'interface' property.
+  properties.clear();
+  properties[MM_BEARER_PROPERTY_INTERFACE].writer().append_string(
+      kDataInterface);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(kDataInterface, bearer_.data_interface());
+
+  // Update 'connected' property.
+  properties.clear();
+  properties[MM_BEARER_PROPERTY_CONNECTED].writer().append_bool(true);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_TRUE(bearer_.connected());
+  // 'interface' property remains unchanged.
+  EXPECT_EQ(kDataInterface, bearer_.data_interface());
+
+  DBus::MessageIter writer;
+
+  // Update 'ip4config' property.
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+  writer << ConstructIPv4ConfigProperties(MM_BEARER_IP_METHOD_UNKNOWN);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodUnknown, bearer_.ipv4_config_method());
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+  writer << ConstructIPv4ConfigProperties(MM_BEARER_IP_METHOD_PPP);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodPPP, bearer_.ipv4_config_method());
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+  writer << ConstructIPv4ConfigProperties(MM_BEARER_IP_METHOD_STATIC);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodStatic, bearer_.ipv4_config_method());
+  VerifyStaticIPv4ConfigMethodAndProperties();
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+  writer << ConstructIPv4ConfigProperties(MM_BEARER_IP_METHOD_DHCP);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodDHCP, bearer_.ipv4_config_method());
+
+  // Update 'ip6config' property.
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP6CONFIG].writer();
+  writer << ConstructIPv6ConfigProperties(MM_BEARER_IP_METHOD_UNKNOWN);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodUnknown, bearer_.ipv6_config_method());
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP6CONFIG].writer();
+  writer << ConstructIPv6ConfigProperties(MM_BEARER_IP_METHOD_PPP);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodPPP, bearer_.ipv6_config_method());
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP6CONFIG].writer();
+  writer << ConstructIPv6ConfigProperties(MM_BEARER_IP_METHOD_STATIC);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodStatic, bearer_.ipv6_config_method());
+  VerifyStaticIPv6ConfigMethodAndProperties();
+
+  properties.clear();
+  writer = properties[MM_BEARER_PROPERTY_IP6CONFIG].writer();
+  writer << ConstructIPv6ConfigProperties(MM_BEARER_IP_METHOD_DHCP);
+  bearer_.OnDBusPropertiesChanged(MM_DBUS_INTERFACE_BEARER, properties,
+                                  vector<string>());
+  EXPECT_EQ(IPConfig::kMethodDHCP, bearer_.ipv6_config_method());
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability.cc b/cellular/cellular_capability.cc
new file mode 100644
index 0000000..1d6df69
--- /dev/null
+++ b/cellular/cellular_capability.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/cellular/cellular_capability.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/property_accessor.h"
+
+using base::Closure;
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapability *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+const char CellularCapability::kModemPropertyIMSI[] = "imsi";
+const char CellularCapability::kModemPropertyState[] = "State";
+// All timeout values are in milliseconds
+const int CellularCapability::kTimeoutActivate = 300000;
+const int CellularCapability::kTimeoutConnect = 45000;
+const int CellularCapability::kTimeoutDefault = 5000;
+const int CellularCapability::kTimeoutDisconnect = 45000;
+const int CellularCapability::kTimeoutEnable = 45000;
+const int CellularCapability::kTimeoutRegister = 90000;
+const int CellularCapability::kTimeoutReset = 90000;
+const int CellularCapability::kTimeoutScan = 120000;
+
+CellularCapability::CellularCapability(Cellular *cellular,
+                                       ProxyFactory *proxy_factory,
+                                       ModemInfo *modem_info)
+    : cellular_(cellular),
+      proxy_factory_(proxy_factory),
+      modem_info_(modem_info) {}
+
+CellularCapability::~CellularCapability() {}
+
+void CellularCapability::OnUnsupportedOperation(const char *operation,
+                                                Error *error) {
+  string message("The ");
+  message.append(operation).append(" operation is not supported.");
+  Error::PopulateAndLog(error, Error::kNotSupported, message);
+}
+
+void CellularCapability::DisconnectCleanup() {}
+
+void CellularCapability::Activate(const string &carrier,
+                                  Error *error,
+                                  const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::CompleteActivation(Error *error) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+bool CellularCapability::IsServiceActivationRequired() const {
+  return false;
+}
+
+void CellularCapability::RegisterOnNetwork(
+    const string &/*network_id*/,
+    Error *error,
+    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::RequirePIN(const std::string &/*pin*/,
+                                    bool /*require*/,
+                                    Error *error,
+                                    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::EnterPIN(const string &/*pin*/,
+                                  Error *error,
+                                  const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::UnblockPIN(const string &/*unblock_code*/,
+                                    const string &/*pin*/,
+                                    Error *error,
+                                    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::ChangePIN(const string &/*old_pin*/,
+                                   const string &/*new_pin*/,
+                                   Error *error,
+                                   const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::Scan(Error *error,
+                              const ResultStringmapsCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::Reset(Error *error,
+                               const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapability::SetCarrier(const std::string &/*carrier*/,
+                                    Error *error,
+                                    const ResultCallback &/*callback*/) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+CellularBearer *CellularCapability::GetActiveBearer() const {
+  return nullptr;
+}
+
+bool CellularCapability::IsActivating() const {
+  return false;
+}
+
+bool CellularCapability::ShouldDetectOutOfCredit() const {
+  return false;
+}
+
+void CellularCapability::OnOperatorChanged() {
+  SLOG(this, 3) << __func__;
+  if (cellular()->service()) {
+    UpdateServiceOLP();
+  }
+}
+
+void CellularCapability::UpdateServiceOLP() {
+  SLOG(this, 3) << __func__;
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability.h b/cellular/cellular_capability.h
new file mode 100644
index 0000000..357aaa2
--- /dev/null
+++ b/cellular/cellular_capability.h
@@ -0,0 +1,301 @@
+// 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_CELLULAR_CAPABILITY_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_H_
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/callbacks.h"
+#include "shill/cellular/cellular.h"
+#include "shill/dbus_properties.h"
+#include "shill/metrics.h"
+
+namespace shill {
+
+class Cellular;
+class CellularBearer;
+class Error;
+class ModemInfo;
+class ProxyFactory;
+
+// 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
+//       |            |
+//       |            |-- CellularCapabilityUniversalCDMA
+//       |
+//       |-- CellularCapabilityClassic
+//                    |
+//                    |-- CellularCapabilityGSM
+//                    |
+//                    |-- 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:
+  static const int kTimeoutActivate;
+  static const int kTimeoutConnect;
+  static const int kTimeoutDefault;
+  static const int kTimeoutDisconnect;
+  static const int kTimeoutEnable;
+  static const int kTimeoutRegister;
+  static const int kTimeoutReset;
+  static const int kTimeoutScan;
+
+  static const char kModemPropertyIMSI[];
+  static const char kModemPropertyState[];
+
+  // |cellular| is the parent Cellular device.
+  CellularCapability(Cellular *cellular,
+                     ProxyFactory *proxy_factory,
+                     ModemInfo *modem_info);
+  virtual ~CellularCapability();
+
+  virtual std::string GetTypeString() const = 0;
+
+  // Called when the modem manager has sent a property change notification
+  // signal over DBus.
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) = 0;
+
+  // -------------------------------------------------------------------------
+  // Modem management
+  // -------------------------------------------------------------------------
+
+  // 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 non-blocking calls to the
+  // modem-manager server. After each call, control is passed back up to the
+  // main loop. Each time a reply to a non-blocking call is received, the
+  // operation advances to the next step, until either an error occurs in one of
+  // them, or all the steps have been completed, at which point StartModem() is
+  // finished.
+  virtual void StartModem(Error *error, const ResultCallback &callback) = 0;
+
+  // 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) = 0;
+
+  // Resets the modem.
+  //
+  // The default implementation fails by returning kNotSupported via |error|.
+  virtual void Reset(Error *error, const ResultCallback &callback);
+
+  // Checks to see if all proxies have been initialized.
+  virtual bool AreProxiesInitialized() const = 0;
+
+  // -------------------------------------------------------------------------
+  // Activation
+  // -------------------------------------------------------------------------
+
+  // Returns true if service activation is required.
+  //
+  // The default implementation returns false.
+  virtual bool IsServiceActivationRequired() const;
+
+  // Returns true if the modem is being activated.
+  //
+  // The default implementation returns false.
+  virtual bool IsActivating() const;
+
+  // Activates the modem.
+  //
+  // The default implementation fails by returning kNotSupported via |error|.
+  virtual void Activate(const std::string &carrier,
+                        Error *error, const ResultCallback &callback);
+
+  // Initiates the necessary to steps to verify that the cellular service has
+  // been activated. Once these steps have been completed, the service should
+  // be marked as activated.
+  //
+  // The default implementation fails by returning kNotSupported via |error|.
+  virtual void CompleteActivation(Error *error);
+
+  // -------------------------------------------------------------------------
+  // Network service and registration
+  // -------------------------------------------------------------------------
+
+  // Configures the modem to support the |carrier|.
+  //
+  // The default implementation fails by returning kNotSupported via |error|.
+  virtual void SetCarrier(const std::string &carrier,
+                          Error *error,
+                          const ResultCallback &callback);
+
+  // Asks the modem to scan for networks.
+  //
+  // The default implementation fails by returning kNotSupported via |error|.
+  //
+  // Subclasses should implement this by fetching scan results asynchronously.
+  // When the results are ready, update the 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 ResultStringmapsCallback &callback);
+
+  // Registers on a network with |network_id|.
+  virtual void RegisterOnNetwork(const std::string &network_id,
+                                 Error *error,
+                                 const ResultCallback &callback);
+
+  // Returns true if the modem is registered on a network, which can be a home
+  // or roaming network. It is possible that we cannot determine whether it is
+  // a home or roaming network, but we still consider the modem is registered.
+  virtual bool IsRegistered() const = 0;
+
+  // If we are informed by means of something other than a signal indicating
+  // a registration state change that the modem has unregistered from the
+  // network, we need to update the network-type-specific capability object.
+  virtual void SetUnregistered(bool searching) = 0;
+
+  // Invoked by the parent Cellular device when a new service is created.
+  virtual void OnServiceCreated() = 0;
+
+  // Hook called by the Cellular device when either the Home Provider or the
+  // Serving Operator changes. Default implementation calls other hooks declared
+  // below. Overrides should chain up to this function.
+  // Note: This may be called before |CellularService| is created.
+  virtual void OnOperatorChanged();
+  virtual void UpdateServiceOLP();
+
+  // Returns an empty string if the network technology is unknown.
+  virtual std::string GetNetworkTechnologyString() const = 0;
+
+  virtual std::string GetRoamingStateString() const = 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;
+
+  // Returns true if the cellular device should initiate passive traffic
+  // monitoring to trigger active out-of-credit detection checks. The default
+  // implementation returns false by default.
+  virtual bool ShouldDetectOutOfCredit() const;
+
+  // TODO(armansito): Remove this method once cromo is deprecated.
+  virtual void GetSignalQuality() = 0;
+
+  // -------------------------------------------------------------------------
+  // Connection management
+  // -------------------------------------------------------------------------
+
+  // Fills |properties| with properties for establishing a connection, which
+  // will be passed to Connect().
+  virtual void SetupConnectProperties(DBusPropertiesMap *properties) = 0;
+
+  // Connects the modem to a network based on the connection properties
+  // specified by |properties|.
+  virtual void Connect(const DBusPropertiesMap &properties,
+                       Error *error,
+                       const ResultCallback &callback) = 0;
+
+  // Disconnects the modem from a network.
+  virtual void Disconnect(Error *error, const ResultCallback &callback) = 0;
+
+  // Called when a disconnect operation completes, successful or not.
+  //
+  // The default implementation does nothing.
+  virtual void DisconnectCleanup();
+
+  // Returns a pointer to the current active bearer object or nullptr if no
+  // active bearer exists. The returned bearer object is managed by this
+  // capability object. This implementation returns nullptr by default.
+  virtual CellularBearer *GetActiveBearer() const;
+
+  // -------------------------------------------------------------------------
+  // SIM PIN management
+  // -------------------------------------------------------------------------
+
+  // The default implementation fails by returning kNotSupported via |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);
+
+  // -------------------------------------------------------------------------
+
+  Cellular *cellular() const { return cellular_; }
+  ProxyFactory *proxy_factory() const { return proxy_factory_; }
+  ModemInfo *modem_info() const { return modem_info_; }
+
+ protected:
+  // 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);
+
+  // Accessor for subclasses to read the 'allow roaming' property.
+  bool allow_roaming_property() const {
+    return cellular_->allow_roaming_property();
+  }
+
+ private:
+  friend class CellularCapabilityGSMTest;
+  friend class CellularCapabilityTest;
+  friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
+  friend class CellularTest;
+  FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearer);
+  FRIEND_TEST(CellularTest, Connect);
+  FRIEND_TEST(CellularTest, TearDown);
+
+  Cellular *cellular_;
+  ProxyFactory *proxy_factory_;
+  ModemInfo *modem_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapability);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_H_
diff --git a/cellular/cellular_capability_cdma.cc b/cellular/cellular_capability_cdma.cc
new file mode 100644
index 0000000..255d933
--- /dev/null
+++ b/cellular/cellular_capability_cdma.cc
@@ -0,0 +1,404 @@
+// 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/cellular_capability_cdma.h"
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/logging.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapabilityCDMA *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+// static
+const char CellularCapabilityCDMA::kPhoneNumber[] = "#777";
+
+CellularCapabilityCDMA::CellularCapabilityCDMA(Cellular *cellular,
+                                               ProxyFactory *proxy_factory,
+                                               ModemInfo *modem_info)
+    : CellularCapabilityClassic(cellular, proxy_factory, modem_info),
+      weak_ptr_factory_(this),
+      activation_starting_(false),
+      activation_state_(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED),
+      registration_state_evdo_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
+      registration_state_1x_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+  SLOG(this, 2) << "Cellular capability constructed: CDMA";
+}
+
+CellularCapabilityCDMA::~CellularCapabilityCDMA() {}
+
+void CellularCapabilityCDMA::InitProxies() {
+  CellularCapabilityClassic::InitProxies();
+  proxy_.reset(proxy_factory()->CreateModemCDMAProxy(
+      cellular()->dbus_path(), cellular()->dbus_owner()));
+  proxy_->set_signal_quality_callback(
+      Bind(&CellularCapabilityCDMA::OnSignalQualitySignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  proxy_->set_activation_state_callback(
+      Bind(&CellularCapabilityCDMA::OnActivationStateChangedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  proxy_->set_registration_state_callback(
+      Bind(&CellularCapabilityCDMA::OnRegistrationStateChangedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+}
+
+string CellularCapabilityCDMA::GetTypeString() const {
+  return kTechnologyFamilyCdma;
+}
+
+void CellularCapabilityCDMA::StartModem(Error *error,
+                                        const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  InitProxies();
+
+  CellularTaskList *tasks = new CellularTaskList();
+  ResultCallback cb =
+      Bind(&CellularCapabilityCDMA::StepCompletedCallback,
+           weak_ptr_factory_.GetWeakPtr(), callback, false, tasks);
+  if (!cellular()->IsUnderlyingDeviceEnabled())
+    tasks->push_back(Bind(&CellularCapabilityCDMA::EnableModem,
+                          weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityCDMA::GetModemStatus,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityCDMA::GetMEID,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityCDMA::GetModemInfo,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityCDMA::FinishEnable,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+
+  RunNextStep(tasks);
+}
+
+void CellularCapabilityCDMA::ReleaseProxies() {
+  CellularCapabilityClassic::ReleaseProxies();
+  proxy_.reset();
+}
+
+bool CellularCapabilityCDMA::AreProxiesInitialized() const {
+  return (CellularCapabilityClassic::AreProxiesInitialized() && proxy_.get());
+}
+
+bool CellularCapabilityCDMA::AllowRoaming() {
+  return allow_roaming_property();
+}
+
+
+void CellularCapabilityCDMA::OnServiceCreated() {
+  SLOG(this, 2) << __func__;
+  cellular()->service()->SetUsageURL(usage_url_);
+  cellular()->service()->SetActivationType(
+      CellularService::kActivationTypeOTASP);
+  HandleNewActivationState(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR);
+}
+
+void CellularCapabilityCDMA::UpdateStatus(const DBusPropertiesMap &properties) {
+  string carrier;
+  DBusProperties::GetUint32(
+      properties, "activation_state", &activation_state_);
+  // TODO(petkov): For now, get the payment and usage URLs from ModemManager to
+  // match flimflam. In the future, get these from an alternative source (e.g.,
+  // database, carrier-specific properties, etc.).
+  UpdateOnlinePortal(properties);
+  uint16_t prl_version;
+  if (DBusProperties::GetUint16(properties, "prl_version", &prl_version))
+    cellular()->set_prl_version(prl_version);
+}
+
+void CellularCapabilityCDMA::UpdateServiceOLP() {
+  SLOG(this, 3) << __func__;
+  // All OLP changes are routed up to the Home Provider.
+  if (!cellular()->home_provider_info()->IsMobileNetworkOperatorKnown()) {
+    return;
+  }
+
+  const vector<MobileOperatorInfo::OnlinePortal> &olp_list =
+      cellular()->home_provider_info()->olp_list();
+  if (olp_list.empty()) {
+    return;
+  }
+
+  if (olp_list.size() > 1) {
+    SLOG(this, 1) << "Found multiple online portals. Choosing the first.";
+  }
+  cellular()->service()->SetOLP(olp_list[0].url,
+                                olp_list[0].method,
+                                olp_list[0].post_data);
+}
+
+void CellularCapabilityCDMA::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  (*properties)[kConnectPropertyPhoneNumber].writer().append_string(
+      kPhoneNumber);
+}
+
+void CellularCapabilityCDMA::Activate(const string &carrier,
+                                      Error *error,
+                                      const ResultCallback &callback) {
+  SLOG(this, 2) << __func__ << "(" << carrier << ")";
+  // We're going to trigger something which leads to an activation.
+  activation_starting_ = true;
+  if (cellular()->state() == Cellular::kStateEnabled ||
+      cellular()->state() == Cellular::kStateRegistered) {
+    ActivationResultCallback activation_callback =
+        Bind(&CellularCapabilityCDMA::OnActivateReply,
+             weak_ptr_factory_.GetWeakPtr(),
+             callback);
+    proxy_->Activate(carrier, error, activation_callback, kTimeoutActivate);
+  } else if (cellular()->state() == Cellular::kStateConnected ||
+             cellular()->state() == Cellular::kStateLinked) {
+    pending_activation_callback_ = callback;
+    pending_activation_carrier_ = carrier;
+    cellular()->Disconnect(error, __func__);
+  } else {
+    Error::PopulateAndLog(error, Error::kInvalidArguments,
+                          "Unable to activate in " +
+                          Cellular::GetStateString(cellular()->state()));
+    activation_starting_ = false;
+  }
+}
+
+void CellularCapabilityCDMA::HandleNewActivationState(uint32_t error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+  if (!cellular()->service().get()) {
+    LOG(ERROR) << "In " << __func__ << "(): service is null.";
+    return;
+  }
+  cellular()->service()->SetActivationState(
+      GetActivationStateString(activation_state_));
+  cellular()->service()->set_error(GetActivationErrorString(error));
+}
+
+void CellularCapabilityCDMA::DisconnectCleanup() {
+  CellularCapabilityClassic::DisconnectCleanup();
+  if (pending_activation_callback_.is_null()) {
+    return;
+  }
+  if (cellular()->state() == Cellular::kStateEnabled ||
+      cellular()->state() == Cellular::kStateRegistered) {
+    Error ignored_error;
+    Activate(pending_activation_carrier_,
+             &ignored_error,
+             pending_activation_callback_);
+  } else {
+    Error error;
+    Error::PopulateAndLog(
+        &error,
+        Error::kOperationFailed,
+        "Tried to disconnect before activating cellular service and failed");
+    HandleNewActivationState(MM_MODEM_CDMA_ACTIVATION_ERROR_UNKNOWN);
+    activation_starting_ = false;
+    pending_activation_callback_.Run(error);
+  }
+  pending_activation_callback_.Reset();
+  pending_activation_carrier_.clear();
+}
+
+// static
+string CellularCapabilityCDMA::GetActivationStateString(uint32_t state) {
+  switch (state) {
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED:
+      return kActivationStateActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING:
+      return kActivationStateActivating;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED:
+      return kActivationStateNotActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
+      return kActivationStatePartiallyActivated;
+    default:
+      return kActivationStateUnknown;
+  }
+}
+
+// static
+string CellularCapabilityCDMA::GetActivationErrorString(uint32_t error) {
+  switch (error) {
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_WRONG_RADIO_INTERFACE:
+      return kErrorNeedEvdo;
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_ROAMING:
+      return kErrorNeedHomeNetwork;
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_COULD_NOT_CONNECT:
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_SECURITY_AUTHENTICATION_FAILED:
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_PROVISIONING_FAILED:
+      return kErrorOtaspFailed;
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR:
+      return "";
+    case MM_MODEM_CDMA_ACTIVATION_ERROR_NO_SIGNAL:
+    default:
+      return kErrorActivationFailed;
+  }
+}
+
+void CellularCapabilityCDMA::GetMEID(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  if (cellular()->meid().empty()) {
+    // TODO(petkov): Switch to asynchronous calls (crbug.com/200687).
+    cellular()->set_meid(proxy_->MEID());
+    SLOG(this, 2) << "MEID: " << cellular()->meid();
+  }
+  callback.Run(Error());
+}
+
+void CellularCapabilityCDMA::GetProperties(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  // No properties.
+  callback.Run(Error());
+}
+
+bool CellularCapabilityCDMA::IsActivating() const {
+  return activation_starting_ ||
+      activation_state_ == MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+}
+
+bool CellularCapabilityCDMA::IsRegistered() const {
+  return registration_state_evdo_ != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
+      registration_state_1x_ != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+}
+
+void CellularCapabilityCDMA::SetUnregistered(bool searching) {
+  registration_state_evdo_ = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  registration_state_1x_ = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+}
+
+string CellularCapabilityCDMA::GetNetworkTechnologyString() const {
+  if (registration_state_evdo_ != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+    return kNetworkTechnologyEvdo;
+  }
+  if (registration_state_1x_ != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+    return kNetworkTechnology1Xrtt;
+  }
+  return "";
+}
+
+string CellularCapabilityCDMA::GetRoamingStateString() const {
+  uint32_t state = registration_state_evdo_;
+  if (state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+    state = registration_state_1x_;
+  }
+  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 kRoamingStateHome;
+    case MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING:
+      return kRoamingStateRoaming;
+    default:
+      NOTREACHED();
+  }
+  return kRoamingStateUnknown;
+}
+
+void CellularCapabilityCDMA::GetSignalQuality() {
+  SLOG(this, 2) << __func__;
+  SignalQualityCallback callback =
+      Bind(&CellularCapabilityCDMA::OnGetSignalQualityReply,
+           weak_ptr_factory_.GetWeakPtr());
+  proxy_->GetSignalQuality(nullptr, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityCDMA::GetRegistrationState() {
+  SLOG(this, 2) << __func__;
+  RegistrationStateCallback callback =
+      Bind(&CellularCapabilityCDMA::OnGetRegistrationStateReply,
+           weak_ptr_factory_.GetWeakPtr());
+  proxy_->GetRegistrationState(nullptr, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityCDMA::OnActivateReply(
+    const ResultCallback &callback, uint32_t status, const Error &error) {
+  activation_starting_ = false;
+  if (error.IsSuccess()) {
+    if (status == MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR) {
+      activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+    } else {
+      LOG(WARNING) << "Modem activation failed with status: "
+                   << GetActivationErrorString(status) << " (" << status << ")";
+    }
+    HandleNewActivationState(status);
+  } else {
+    LOG(ERROR) << "Activate() failed with error: " << error;
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityCDMA::OnGetRegistrationStateReply(
+    uint32_t state_1x, uint32_t state_evdo, const Error &error) {
+  SLOG(this, 2) << __func__;
+  if (error.IsSuccess())
+    OnRegistrationStateChangedSignal(state_1x, state_evdo);
+}
+
+void CellularCapabilityCDMA::OnGetSignalQualityReply(uint32_t quality,
+                                                     const Error &error) {
+  if (error.IsSuccess())
+    OnSignalQualitySignal(quality);
+}
+
+void CellularCapabilityCDMA::OnActivationStateChangedSignal(
+    uint32_t activation_state,
+    uint32_t activation_error,
+    const DBusPropertiesMap &status_changes) {
+  SLOG(this, 2) << __func__;
+  string prop_value;
+
+  if (DBusProperties::GetString(status_changes, "mdn", &prop_value))
+    cellular()->set_mdn(prop_value);
+  if (DBusProperties::GetString(status_changes, "min", &prop_value))
+    cellular()->set_min(prop_value);
+
+  UpdateOnlinePortal(status_changes);
+  activation_state_ = activation_state;
+  HandleNewActivationState(activation_error);
+}
+
+void CellularCapabilityCDMA::OnRegistrationStateChangedSignal(
+    uint32_t state_1x, uint32_t state_evdo) {
+  SLOG(this, 2) << __func__;
+  registration_state_1x_ = state_1x;
+  registration_state_evdo_ = state_evdo;
+  cellular()->HandleNewRegistrationState();
+}
+
+void CellularCapabilityCDMA::OnSignalQualitySignal(uint32_t strength) {
+  cellular()->HandleNewSignalQuality(strength);
+}
+
+void CellularCapabilityCDMA::UpdateOnlinePortal(
+    const DBusPropertiesMap &properties) {
+  // Treat the three updates atomically: Only update the serving operator when
+  // all three are known:
+  string olp_url, olp_method, olp_post_data;
+  if (DBusProperties::GetString(properties, "payment_url", &olp_url) &&
+      DBusProperties::GetString(properties,
+                                "payment_url_method", &olp_method) &&
+      DBusProperties::GetString(properties,
+                                "payment_url_postdata",
+                                &olp_post_data)) {
+    cellular()->home_provider_info()->UpdateOnlinePortal(olp_url,
+                                                            olp_method,
+                                                            olp_post_data);
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_cdma.h b/cellular/cellular_capability_cdma.h
new file mode 100644
index 0000000..eb5436f
--- /dev/null
+++ b/cellular/cellular_capability_cdma.h
@@ -0,0 +1,121 @@
+// 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_CELLULAR_CAPABILITY_CDMA_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_CDMA_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/cellular_capability_classic.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/modem_cdma_proxy_interface.h"
+
+namespace shill {
+
+class ModemInfo;
+
+class CellularCapabilityCDMA : public CellularCapabilityClassic {
+ public:
+  CellularCapabilityCDMA(Cellular *cellular,
+                         ProxyFactory *proxy_factory,
+                         ModemInfo *modem_info);
+  ~CellularCapabilityCDMA() override;
+
+  // Inherited from CellularCapability.
+  std::string GetTypeString() const override;
+  void StartModem(Error *error,
+                  const ResultCallback &callback) override;
+  bool AreProxiesInitialized() const override;
+  void Activate(const std::string &carrier,
+                Error *error,
+                const ResultCallback &callback) override;
+  bool IsActivating() const override;
+  bool IsRegistered() const override;
+  void SetUnregistered(bool searching) override;
+  void OnServiceCreated() override;
+  std::string GetNetworkTechnologyString() const override;
+  std::string GetRoamingStateString() const override;
+  bool AllowRoaming() override;
+  void GetSignalQuality() override;
+  void SetupConnectProperties(DBusPropertiesMap *properties) override;
+  void DisconnectCleanup() override;
+
+  // Inherited from CellularCapabilityClassic.
+  void GetRegistrationState() override;
+  void GetProperties(const ResultCallback &callback) override;
+
+  virtual void GetMEID(const ResultCallback &callback);
+
+  uint32_t activation_state() const { return activation_state_; }
+  uint32_t registration_state_evdo() const { return registration_state_evdo_; }
+  uint32_t registration_state_1x() const { return registration_state_1x_; }
+
+ protected:
+  // Inherited from CellularCapabilityClassic.
+  void InitProxies() override;
+  void ReleaseProxies() override;
+  void UpdateStatus(const DBusPropertiesMap &properties) override;
+
+ private:
+  friend class CellularCapabilityCDMATest;
+  friend class CellularTest;
+  FRIEND_TEST(CellularCapabilityCDMATest, Activate);
+  FRIEND_TEST(CellularCapabilityCDMATest, ActivateError);
+  FRIEND_TEST(CellularCapabilityCDMATest, GetActivationStateString);
+  FRIEND_TEST(CellularCapabilityCDMATest, GetActivationErrorString);
+  FRIEND_TEST(CellularServiceTest, IsAutoConnectable);
+  FRIEND_TEST(CellularTest, CreateService);
+
+  static const char kPhoneNumber[];
+
+  void HandleNewActivationState(uint32_t error);
+
+  static std::string GetActivationStateString(uint32_t state);
+  static std::string GetActivationErrorString(uint32_t error);
+
+  // Signal callbacks from the Modem.CDMA interface
+  void OnActivationStateChangedSignal(
+      uint32_t activation_state,
+      uint32_t activation_error,
+      const DBusPropertiesMap &status_changes);
+  void OnRegistrationStateChangedSignal(
+      uint32_t state_1x, uint32_t state_evdo);
+  void OnSignalQualitySignal(uint32_t strength);
+
+  // Method reply callbacks from the Modem.CDMA interface
+  void OnActivateReply(const ResultCallback &callback,
+                       uint32_t status, const Error &error);
+
+  void OnGetRegistrationStateReply(uint32_t state_1x, uint32_t state_evdo,
+                                   const Error &error);
+  void OnGetSignalQualityReply(uint32_t strength, const Error &error);
+
+  std::unique_ptr<ModemCDMAProxyInterface> proxy_;
+  base::WeakPtrFactory<CellularCapabilityCDMA> weak_ptr_factory_;
+
+  // Helper method to extract the online portal information from properties.
+  void UpdateOnlinePortal(const DBusPropertiesMap &properties);
+  void UpdateServiceOLP() override;
+
+  bool activation_starting_;
+  ResultCallback pending_activation_callback_;
+  std::string pending_activation_carrier_;
+  uint32_t activation_state_;
+  uint32_t registration_state_evdo_;
+  uint32_t registration_state_1x_;
+  std::string usage_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityCDMA);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_CDMA_H_
diff --git a/cellular/cellular_capability_cdma_unittest.cc b/cellular/cellular_capability_cdma_unittest.cc
new file mode 100644
index 0000000..3f8a8be
--- /dev/null
+++ b/cellular/cellular_capability_cdma_unittest.cc
@@ -0,0 +1,386 @@
+// 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/cellular_capability_cdma.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_modem_cdma_proxy.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_modem_proxy.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+using testing::_;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrEq;
+
+namespace shill {
+
+class CellularCapabilityCDMATest : public testing::Test {
+ public:
+  CellularCapabilityCDMATest()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        cellular_(new MockCellular(&modem_info_,
+                                   "",
+                                   "",
+                                   0,
+                                   Cellular::kTypeCDMA,
+                                   "",
+                                   "",
+                                   "",
+                                   ProxyFactory::GetInstance())),
+        classic_proxy_(new MockModemProxy()),
+        proxy_(new MockModemCDMAProxy()),
+        capability_(nullptr) {
+    modem_info_.metrics()->RegisterDevice(cellular_->interface_index(),
+                                          Technology::kCellular);
+  }
+
+  virtual ~CellularCapabilityCDMATest() {
+    cellular_->service_ = nullptr;
+    capability_ = nullptr;
+  }
+
+  virtual void SetUp() {
+    capability_ =
+        dynamic_cast<CellularCapabilityCDMA *>(cellular_->capability_.get());
+  }
+
+  void InvokeActivate(const string &carrier, Error *error,
+                      const ActivationResultCallback &callback,
+                      int timeout) {
+    callback.Run(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR, Error());
+  }
+  void InvokeActivateError(const string &carrier, Error *error,
+                           const ActivationResultCallback &callback,
+                           int timeout) {
+    callback.Run(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_SIGNAL, Error());
+  }
+  void InvokeDisconnect(Error *error,
+                        const ResultCallback &callback,
+                        int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeDisconnectError(Error *error,
+                             const ResultCallback &callback,
+                             int timeout) {
+    Error err(Error::kOperationFailed);
+    callback.Run(err);
+  }
+  void InvokeGetSignalQuality(Error *error,
+                              const SignalQualityCallback &callback,
+                              int timeout) {
+    callback.Run(kStrength, Error());
+  }
+  void InvokeGetRegistrationState(Error *error,
+                                  const RegistrationStateCallback &callback,
+                                  int timeout) {
+    callback.Run(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
+                 MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+                 Error());
+  }
+
+  MOCK_METHOD1(TestCallback, void(const Error &error));
+
+ protected:
+  static const char kMEID[];
+  static const char kTestCarrier[];
+  static const unsigned int kStrength;
+
+  bool IsActivationStarting() const {
+    return capability_->activation_starting_;
+  }
+
+  void SetRegistrationStateEVDO(uint32_t state) {
+    capability_->registration_state_evdo_ = state;
+  }
+
+  void SetRegistrationState1x(uint32_t state) {
+    capability_->registration_state_1x_ = state;
+  }
+
+  void SetProxy() {
+    capability_->proxy_.reset(proxy_.release());
+    capability_->CellularCapabilityClassic::proxy_.reset(
+        classic_proxy_.release());
+  }
+
+  void SetService() {
+    cellular_->service_ = new CellularService(&modem_info_, cellular_);
+  }
+
+  void SetDeviceState(Cellular::State state) {
+    cellular_->state_ = state;
+  }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  scoped_refptr<MockCellular> cellular_;
+  std::unique_ptr<MockModemProxy> classic_proxy_;
+  std::unique_ptr<MockModemCDMAProxy> proxy_;
+  CellularCapabilityCDMA *capability_;  // Owned by |cellular_|.
+};
+
+const char CellularCapabilityCDMATest::kMEID[] = "D1234567EF8901";
+const char CellularCapabilityCDMATest::kTestCarrier[] = "The Cellular Carrier";
+const unsigned int CellularCapabilityCDMATest::kStrength = 90;
+
+TEST_F(CellularCapabilityCDMATest, PropertyStore) {
+  EXPECT_TRUE(cellular_->store().Contains(kPRLVersionProperty));
+}
+
+TEST_F(CellularCapabilityCDMATest, Activate) {
+  SetDeviceState(Cellular::kStateEnabled);
+  EXPECT_CALL(*proxy_, Activate(kTestCarrier, _, _,
+                                CellularCapability::kTimeoutActivate))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityCDMATest::InvokeActivate));
+  EXPECT_CALL(*this, TestCallback(_));
+  SetProxy();
+  SetService();
+  capability_->Activate(kTestCarrier, nullptr,
+      Bind(&CellularCapabilityCDMATest::TestCallback, Unretained(this)));
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING,
+            capability_->activation_state());
+  EXPECT_EQ(kActivationStateActivating,
+            cellular_->service()->activation_state());
+  EXPECT_EQ("", cellular_->service()->error());
+}
+
+TEST_F(CellularCapabilityCDMATest, ActivateWhileConnected) {
+  SetDeviceState(Cellular::kStateConnected);
+  {
+    InSequence dummy;
+
+    EXPECT_CALL(*cellular_, Disconnect(_, StrEq("Activate")));
+    EXPECT_CALL(*proxy_, Activate(kTestCarrier, _, _,
+                                  CellularCapability::kTimeoutActivate))
+        .WillOnce(Invoke(this,
+                         &CellularCapabilityCDMATest::InvokeActivate));
+    EXPECT_CALL(*this, TestCallback(_));
+  }
+  SetProxy();
+  SetService();
+  Error error;
+  capability_->Activate(kTestCarrier, &error,
+      Bind(&CellularCapabilityCDMATest::TestCallback, Unretained(this)));
+  // So now we should be "activating" while we wait for a disconnect.
+  EXPECT_TRUE(IsActivationStarting());
+  EXPECT_TRUE(capability_->IsActivating());
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED,
+            capability_->activation_state());
+  // Simulate a disconnect.
+  SetDeviceState(Cellular::kStateRegistered);
+  capability_->DisconnectCleanup();
+  // Now the modem is actually activating.
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING,
+            capability_->activation_state());
+  EXPECT_EQ(kActivationStateActivating,
+            cellular_->service()->activation_state());
+  EXPECT_EQ("", cellular_->service()->error());
+  EXPECT_FALSE(IsActivationStarting());
+  EXPECT_TRUE(capability_->IsActivating());
+}
+
+TEST_F(CellularCapabilityCDMATest, ActivateWhileConnectedButFail) {
+  SetDeviceState(Cellular::kStateConnected);
+  {
+    InSequence dummy;
+
+    EXPECT_CALL(*cellular_, Disconnect(_, StrEq("Activate")));
+    EXPECT_CALL(*proxy_, Activate(kTestCarrier, _, _,
+                                  CellularCapability::kTimeoutActivate))
+        .Times(0);
+  }
+  SetProxy();
+  SetService();
+  Error error;
+  capability_->Activate(kTestCarrier, &error,
+      Bind(&CellularCapabilityCDMATest::TestCallback, Unretained(this)));
+  // So now we should be "activating" while we wait for a disconnect.
+  EXPECT_TRUE(IsActivationStarting());
+  EXPECT_TRUE(capability_->IsActivating());
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED,
+            capability_->activation_state());
+  // Similulate a failed disconnect (the modem is still connected!).
+  capability_->DisconnectCleanup();
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED,
+            capability_->activation_state());
+  EXPECT_EQ(kActivationStateNotActivated,
+            cellular_->service()->activation_state());
+  EXPECT_EQ(kErrorActivationFailed, cellular_->service()->error());
+  EXPECT_FALSE(IsActivationStarting());
+  EXPECT_FALSE(capability_->IsActivating());
+}
+
+TEST_F(CellularCapabilityCDMATest, ActivateError) {
+  SetDeviceState(Cellular::kStateEnabled);
+  EXPECT_CALL(*proxy_, Activate(kTestCarrier, _, _,
+                                CellularCapability::kTimeoutActivate))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityCDMATest::InvokeActivateError));
+  EXPECT_CALL(*this, TestCallback(_));
+  SetProxy();
+  SetService();
+  capability_->Activate(kTestCarrier, nullptr,
+      Bind(&CellularCapabilityCDMATest::TestCallback, Unretained(this)));
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED,
+            capability_->activation_state());
+  EXPECT_EQ(kActivationStateNotActivated,
+            cellular_->service()->activation_state());
+  EXPECT_EQ(kErrorActivationFailed,
+            cellular_->service()->error());
+}
+
+TEST_F(CellularCapabilityCDMATest, GetActivationStateString) {
+  EXPECT_EQ(kActivationStateActivated,
+            CellularCapabilityCDMA::GetActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED));
+  EXPECT_EQ(kActivationStateActivating,
+            CellularCapabilityCDMA::GetActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING));
+  EXPECT_EQ(kActivationStateNotActivated,
+            CellularCapabilityCDMA::GetActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED));
+  EXPECT_EQ(kActivationStatePartiallyActivated,
+            CellularCapabilityCDMA::GetActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED));
+  EXPECT_EQ(kActivationStateUnknown,
+            CellularCapabilityCDMA::GetActivationStateString(123));
+}
+
+TEST_F(CellularCapabilityCDMATest, GetActivationErrorString) {
+  EXPECT_EQ(kErrorNeedEvdo,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_WRONG_RADIO_INTERFACE));
+  EXPECT_EQ(kErrorNeedHomeNetwork,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_ROAMING));
+  EXPECT_EQ(kErrorOtaspFailed,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_COULD_NOT_CONNECT));
+  EXPECT_EQ(kErrorOtaspFailed,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_SECURITY_AUTHENTICATION_FAILED));
+  EXPECT_EQ(kErrorOtaspFailed,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_PROVISIONING_FAILED));
+  EXPECT_EQ("",
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR));
+  EXPECT_EQ(kErrorActivationFailed,
+            CellularCapabilityCDMA::GetActivationErrorString(
+                MM_MODEM_CDMA_ACTIVATION_ERROR_NO_SIGNAL));
+  EXPECT_EQ(kErrorActivationFailed,
+            CellularCapabilityCDMA::GetActivationErrorString(1234));
+}
+
+TEST_F(CellularCapabilityCDMATest, IsRegisteredEVDO) {
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
+  EXPECT_TRUE(capability_->IsRegistered());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_TRUE(capability_->IsRegistered());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+  EXPECT_TRUE(capability_->IsRegistered());
+}
+
+TEST_F(CellularCapabilityCDMATest, IsRegistered1x) {
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
+  EXPECT_TRUE(capability_->IsRegistered());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_TRUE(capability_->IsRegistered());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+  EXPECT_TRUE(capability_->IsRegistered());
+}
+
+TEST_F(CellularCapabilityCDMATest, GetNetworkTechnologyString) {
+  EXPECT_EQ("", capability_->GetNetworkTechnologyString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_EQ(kNetworkTechnologyEvdo,
+            capability_->GetNetworkTechnologyString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_EQ(kNetworkTechnology1Xrtt,
+            capability_->GetNetworkTechnologyString());
+}
+
+TEST_F(CellularCapabilityCDMATest, GetRoamingStateString) {
+  EXPECT_EQ(kRoamingStateUnknown,
+            capability_->GetRoamingStateString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
+  EXPECT_EQ(kRoamingStateUnknown,
+            capability_->GetRoamingStateString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_EQ(kRoamingStateHome, capability_->GetRoamingStateString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+  EXPECT_EQ(kRoamingStateRoaming,
+            capability_->GetRoamingStateString());
+  SetRegistrationStateEVDO(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
+  EXPECT_EQ(kRoamingStateUnknown,
+            capability_->GetRoamingStateString());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_HOME);
+  EXPECT_EQ(kRoamingStateHome, capability_->GetRoamingStateString());
+  SetRegistrationState1x(MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING);
+  EXPECT_EQ(kRoamingStateRoaming,
+            capability_->GetRoamingStateString());
+}
+
+TEST_F(CellularCapabilityCDMATest, GetSignalQuality) {
+  EXPECT_CALL(*proxy_,
+              GetSignalQuality(nullptr, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityCDMATest::InvokeGetSignalQuality));
+  SetProxy();
+  SetService();
+  EXPECT_EQ(0, cellular_->service()->strength());
+  capability_->GetSignalQuality();
+  EXPECT_EQ(kStrength, cellular_->service()->strength());
+}
+
+TEST_F(CellularCapabilityCDMATest, GetRegistrationState) {
+  EXPECT_FALSE(cellular_->service().get());
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+            capability_->registration_state_1x());
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+            capability_->registration_state_evdo());
+  EXPECT_CALL(*proxy_,
+              GetRegistrationState(nullptr, _,
+                                   CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(
+          this,
+          &CellularCapabilityCDMATest::InvokeGetRegistrationState));
+  SetProxy();
+  cellular_->state_ = Cellular::kStateEnabled;
+  EXPECT_CALL(*modem_info_.mock_manager(), RegisterService(_));
+  capability_->GetRegistrationState();
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
+            capability_->registration_state_1x());
+  EXPECT_EQ(MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+            capability_->registration_state_evdo());
+  EXPECT_TRUE(cellular_->service().get());
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_classic.cc b/cellular/cellular_capability_classic.cc
new file mode 100644
index 0000000..dc4250d
--- /dev/null
+++ b/cellular/cellular_capability_classic.cc
@@ -0,0 +1,354 @@
+// 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/cellular_capability_classic.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/modem_gobi_proxy_interface.h"
+#include "shill/error.h"
+#include "shill/logging.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 {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapabilityClassic *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+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";
+const char CellularCapabilityClassic::kModemPropertyEnabled[] = "Enabled";
+const int CellularCapabilityClassic::kTimeoutSetCarrierMilliseconds = 120000;
+
+static Cellular::ModemState ConvertClassicToModemState(uint32_t classic_state) {
+  ModemClassicState cstate = static_cast<ModemClassicState>(classic_state);
+  switch (cstate) {
+    case kModemClassicStateUnknown:
+      return Cellular::kModemStateUnknown;
+    case kModemClassicStateDisabled:
+      return Cellular::kModemStateDisabled;
+    case kModemClassicStateDisabling:
+      return Cellular::kModemStateDisabling;
+    case kModemClassicStateEnabling:
+      return Cellular::kModemStateEnabling;
+    case kModemClassicStateEnabled:
+      return Cellular::kModemStateEnabled;
+    case kModemClassicStateSearching:
+      return Cellular::kModemStateSearching;
+    case kModemClassicStateRegistered:
+      return Cellular::kModemStateRegistered;
+    case kModemClassicStateDisconnecting:
+      return Cellular::kModemStateDisconnecting;
+    case kModemClassicStateConnecting:
+      return Cellular::kModemStateConnecting;
+    case kModemClassicStateConnected:
+      return Cellular::kModemStateConnected;
+    default:
+      return Cellular::kModemStateUnknown;
+  }
+}
+
+CellularCapabilityClassic::CellularCapabilityClassic(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory,
+    ModemInfo *modem_info)
+    : CellularCapability(cellular, proxy_factory, modem_info),
+      weak_ptr_factory_(this) {
+  // This class is currently instantiated only for Gobi modems so setup the
+  // supported carriers list appropriately and expose it over RPC.
+  cellular->set_supported_carriers({kCarrierGenericUMTS,
+                                    kCarrierSprint,
+                                    kCarrierVerizon});
+}
+
+CellularCapabilityClassic::~CellularCapabilityClassic() {}
+
+void CellularCapabilityClassic::InitProxies() {
+  SLOG(this, 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() {
+  SLOG(this, 2) << __func__;
+  proxy_.reset();
+  simple_proxy_.reset();
+  gobi_proxy_.reset();
+}
+
+bool CellularCapabilityClassic::AreProxiesInitialized() const {
+  return (proxy_.get() && simple_proxy_.get() && gobi_proxy_.get());
+}
+
+void CellularCapabilityClassic::FinishEnable(const ResultCallback &callback) {
+  // Normally, running the callback is the last thing done in a method.
+  // In this case, we do it first, because we want to make sure that
+  // the device is marked as Enabled before the registration state is
+  // handled. See comment in Cellular::HandleNewRegistrationState.
+  callback.Run(Error());
+  GetRegistrationState();
+  GetSignalQuality();
+  // We expect the modem to start scanning after it has been enabled.
+  // Change this if this behavior is no longer the case in the future.
+  modem_info()->metrics()->NotifyDeviceEnableFinished(
+      cellular()->interface_index());
+  modem_info()->metrics()->NotifyDeviceScanStarted(
+      cellular()->interface_index());
+}
+
+void CellularCapabilityClassic::FinishDisable(const ResultCallback &callback) {
+  modem_info()->metrics()->NotifyDeviceDisableFinished(
+      cellular()->interface_index());
+  ReleaseProxies();
+  callback.Run(Error());
+}
+
+void CellularCapabilityClassic::RunNextStep(CellularTaskList *tasks) {
+  CHECK(!tasks->empty());
+  SLOG(this, 2) << __func__ << ": " << tasks->size() << " remaining tasks";
+  Closure task = (*tasks)[0];
+  tasks->erase(tasks->begin());
+  cellular()->dispatcher()->PostTask(task);
+}
+
+void CellularCapabilityClassic::StepCompletedCallback(
+    const ResultCallback &callback,
+    bool ignore_error,
+    CellularTaskList *tasks,
+    const Error &error) {
+  if ((ignore_error || error.IsSuccess()) && !tasks->empty()) {
+    RunNextStep(tasks);
+    return;
+  }
+  delete tasks;
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::EnableModem(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  modem_info()->metrics()->NotifyDeviceEnableStarted(
+      cellular()->interface_index());
+  proxy_->Enable(true, &error, callback, kTimeoutEnable);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::DisableModem(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  modem_info()->metrics()->NotifyDeviceDisableStarted(
+      cellular()->interface_index());
+  proxy_->Enable(false, &error, callback, kTimeoutEnable);
+  if (error.IsFailure())
+      callback.Run(error);
+}
+
+// always called from an async context
+void CellularCapabilityClassic::GetModemStatus(const ResultCallback &callback) {
+  SLOG(this, 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) {
+  SLOG(this, 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) {
+  SLOG(this, 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);
+  // TODO(ers): We can skip the call to Disconnect if the modem has
+  // told us that the modem state is Disabled or Registered.
+  tasks->push_back(Bind(&CellularCapabilityClassic::Disconnect,
+                        weak_ptr_factory_.GetWeakPtr(), nullptr,
+                        cb_ignore_error));
+  // TODO(ers): We can skip the call to Disable if the modem has
+  // told us that the modem state is Disabled.
+  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) {
+  SLOG(this, 2) << __func__;
+  simple_proxy_->Connect(properties, error, callback, kTimeoutConnect);
+}
+
+void CellularCapabilityClassic::Disconnect(Error *error,
+                                           const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  if (proxy_.get())
+    proxy_->Disconnect(error, callback, kTimeoutDisconnect);
+  else
+    LOG(ERROR) << "No proxy found in disconnect.";
+}
+
+void CellularCapabilityClassic::SetCarrier(const string &carrier,
+                                           Error *error,
+                                           const ResultCallback &callback) {
+  LOG(INFO) << __func__ << "(" << carrier << ")";
+  if (!gobi_proxy_.get()) {
+    gobi_proxy_.reset(proxy_factory()->CreateModemGobiProxy(
+        cellular()->dbus_path(), cellular()->dbus_owner()));
+  }
+  CHECK(error);
+  gobi_proxy_->SetCarrier(carrier, error, callback,
+                          kTimeoutSetCarrierMilliseconds);
+}
+
+void CellularCapabilityClassic::OnDBusPropertiesChanged(
+    const std::string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const std::vector<std::string> &invalidated_properties) {
+  SLOG(this, 2) << __func__;
+  bool enabled;
+  // This solves a bootstrapping problem: If the modem is not yet
+  // enabled, there are no proxy objects associated with the capability
+  // object, so modem signals like StateChanged aren't seen. By monitoring
+  // changes to the Enabled property via the ModemManager, we're able to
+  // get the initialization process started, which will result in the
+  // creation of the proxy objects.
+  //
+  // We handle all state changes to ENABLED from a disabled state (including,
+  // UNKNOWN) through Cellular::OnModemStateChanged. This will try to enable
+  // the device regardless of whether it has been registered with the Manager.
+  //
+  // All other state changes are handled from OnModemStateChangedSignal.
+  if (DBusProperties::GetBool(changed_properties,
+                              kModemPropertyEnabled, &enabled)) {
+    SLOG(this, 2) << "Property \"Enabled\" changed: " << enabled;
+    Cellular::ModemState prev_modem_state = cellular()->modem_state();
+    if (!Cellular::IsEnabledModemState(prev_modem_state)) {
+      cellular()->OnModemStateChanged(
+          enabled ? Cellular::kModemStateEnabled :
+                    Cellular::kModemStateDisabled);
+    }
+  }
+}
+
+void CellularCapabilityClassic::OnGetModemStatusReply(
+    const ResultCallback &callback,
+    const DBusPropertiesMap &props,
+    const Error &error) {
+  string prop_value;
+  SLOG(this, 2) << __func__ << " " << props.size() << " props. error "
+                << error;
+  if (error.IsSuccess()) {
+    if (DBusProperties::GetString(props, "carrier", &prop_value)) {
+      cellular()->set_carrier(prop_value);
+      cellular()->home_provider_info()->UpdateOperatorName(prop_value);
+    }
+    if (DBusProperties::GetString(props, "meid", &prop_value)) {
+      cellular()->set_meid(prop_value);
+    }
+    if (DBusProperties::GetString(props, "imei", &prop_value)) {
+     cellular()->set_imei(prop_value);
+    }
+    if (DBusProperties::GetString(props, kModemPropertyIMSI, &prop_value)) {
+      cellular()->set_imsi(prop_value);
+      cellular()->home_provider_info()->UpdateIMSI(prop_value);
+      // We do not currently obtain the IMSI OTA at all. Provide the IMSI from
+      // the SIM to the serving operator as well to aid in MVNO identification.
+      cellular()->serving_operator_info()->UpdateIMSI(prop_value);
+    }
+    if (DBusProperties::GetString(props, "esn", &prop_value)) {
+      cellular()->set_esn(prop_value);
+    }
+    if (DBusProperties::GetString(props, "mdn", &prop_value)) {
+      cellular()->set_mdn(prop_value);
+    }
+    if (DBusProperties::GetString(props, "min", &prop_value)) {
+      cellular()->set_min(prop_value);
+    }
+    if (DBusProperties::GetString(props, "firmware_revision", &prop_value)) {
+      cellular()->set_firmware_revision(prop_value);
+    }
+    UpdateStatus(props);
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityClassic::UpdateStatus(
+    const DBusPropertiesMap &properties) {
+  SLOG(this, 3) << __func__;
+}
+
+void CellularCapabilityClassic::OnGetModemInfoReply(
+    const ResultCallback &callback,
+    const ModemHardwareInfo &info,
+    const Error &error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+  if (error.IsSuccess()) {
+    cellular()->set_manufacturer(info._1);
+    cellular()->set_model_id(info._2);
+    cellular()->set_hardware_revision(info._3);
+    SLOG(this, 2) << __func__ << ": " << info._1 << ", " << info._2 << ", "
+                  << info._3;
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityClassic::OnModemStateChangedSignal(
+    uint32_t old_state, uint32_t new_state, uint32_t reason) {
+  SLOG(this, 2) << __func__ << "(" << old_state << ", " << new_state << ", "
+                << reason << ")";
+  cellular()->OnModemStateChanged(ConvertClassicToModemState(new_state));
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_classic.h b/cellular/cellular_capability_classic.h
new file mode 100644
index 0000000..4e32a54
--- /dev/null
+++ b/cellular/cellular_capability_classic.h
@@ -0,0 +1,170 @@
+// 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_CELLULAR_CAPABILITY_CLASSIC_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_CLASSIC_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/modem_proxy_interface.h"
+#include "shill/cellular/modem_simple_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+
+class Cellular;
+class Error;
+class EventDispatcher;
+class ModemGobiProxyInterface;
+class ModemInfo;
+class ProxyFactory;
+
+enum ModemClassicState {
+  kModemClassicStateUnknown = 0,
+  kModemClassicStateDisabled = 10,
+  kModemClassicStateDisabling = 20,
+  kModemClassicStateEnabling = 30,
+  kModemClassicStateEnabled = 40,
+  kModemClassicStateSearching = 50,
+  kModemClassicStateRegistered = 60,
+  kModemClassicStateDisconnecting = 70,
+  kModemClassicStateConnecting = 80,
+  kModemClassicStateConnected = 90,
+};
+
+// 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[];
+  static const char kModemPropertyEnabled[];
+  static const int kTimeoutSetCarrierMilliseconds;
+
+  // |cellular| is the parent Cellular device.
+  CellularCapabilityClassic(Cellular *cellular,
+                            ProxyFactory *proxy_factory,
+                            ModemInfo *modem_info);
+  ~CellularCapabilityClassic() override;
+
+  // Inherited from CellularCapability.
+  void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) override;
+  void StopModem(Error *error, const ResultCallback &callback) override;
+  bool AreProxiesInitialized() const override;
+  void SetCarrier(const std::string &carrier,
+                  Error *error,
+                  const ResultCallback &callback) override;
+  void Connect(const DBusPropertiesMap &properties,
+               Error *error,
+               const ResultCallback &callback) override;
+  void Disconnect(Error *error,
+                  const ResultCallback &callback) override;
+
+ protected:
+  typedef std::vector<base::Closure> CellularTaskList;
+
+  virtual void GetRegistrationState() = 0;
+
+  // 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();
+
+  // Default implementation is no-op.
+  virtual void UpdateStatus(const DBusPropertiesMap &properties);
+
+  // Runs the next task in a list.
+  // Precondition: |tasks| is not empty.
+  void RunNextStep(CellularTaskList *tasks);
+  // StepCompletedCallback is called after a task completes.
+  // |callback| is the original callback that needs to be invoked when all of
+  // the tasks complete or if there is a failure.  |ignore_error| will be set
+  // to true if the next task should be run regardless of the result of the
+  // just-completed task.  |tasks| is the list of tasks remaining.  |error| is
+  // the result of the just-completed task.
+  void StepCompletedCallback(const ResultCallback &callback,
+                             bool ignore_error,
+                             CellularTaskList *tasks,
+                             const Error &error);
+
+  std::unique_ptr<ModemSimpleProxyInterface> simple_proxy_;
+
+ private:
+  friend class CellularTest;
+  friend class CellularCapabilityCDMATest;
+  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, ConnectFailureNoService);
+  FRIEND_TEST(CellularTest, ConnectSuccessNoService);
+  FRIEND_TEST(CellularTest, Disconnect);
+  FRIEND_TEST(CellularTest, DisconnectFailure);
+  FRIEND_TEST(CellularTest, DisconnectWithCallback);
+  FRIEND_TEST(CellularTest, ModemStateChangeEnable);
+  FRIEND_TEST(CellularTest, ModemStateChangeDisable);
+
+  // Method reply and signal callbacks from Modem interface
+  void OnModemStateChangedSignal(uint32_t old_state,
+                                 uint32_t new_state,
+                                 uint32_t reason);
+  void OnGetModemInfoReply(const ResultCallback &callback,
+                           const ModemHardwareInfo &info,
+                           const Error &error);
+
+  // Method reply callbacks from Modem.Simple interface
+  void OnGetModemStatusReply(const ResultCallback &callback,
+                             const DBusPropertiesMap &props,
+                             const Error &error);
+
+  Cellular *cellular_;
+  base::WeakPtrFactory<CellularCapabilityClassic> weak_ptr_factory_;
+  std::unique_ptr<ModemProxyInterface> proxy_;
+  std::unique_ptr<ModemGobiProxyInterface> gobi_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityClassic);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_CLASSIC_H_
diff --git a/cellular/cellular_capability_classic_unittest.cc b/cellular/cellular_capability_classic_unittest.cc
new file mode 100644
index 0000000..9752c77
--- /dev/null
+++ b/cellular/cellular_capability_classic_unittest.cc
@@ -0,0 +1,519 @@
+// 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/cellular_capability_gsm.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_modem_cdma_proxy.h"
+#include "shill/cellular/mock_modem_gobi_proxy.h"
+#include "shill/cellular/mock_modem_gsm_card_proxy.h"
+#include "shill/cellular/mock_modem_gsm_network_proxy.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_modem_proxy.h"
+#include "shill/cellular/mock_modem_simple_proxy.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_profile.h"
+#include "shill/net/mock_rtnl_handler.h"
+#include "shill/proxy_factory.h"
+#include "shill/testing.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+using testing::InSequence;
+using testing::NiceMock;
+using testing::_;
+
+namespace shill {
+
+class CellularCapabilityTest : public testing::Test {
+ public:
+  CellularCapabilityTest()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        create_gsm_card_proxy_from_factory_(false),
+        proxy_(new MockModemProxy()),
+        simple_proxy_(new MockModemSimpleProxy()),
+        cdma_proxy_(new MockModemCDMAProxy()),
+        gsm_card_proxy_(new MockModemGSMCardProxy()),
+        gsm_network_proxy_(new MockModemGSMNetworkProxy()),
+        gobi_proxy_(new MockModemGobiProxy()),
+        proxy_factory_(this),
+        capability_(nullptr),
+        device_adaptor_(nullptr),
+        cellular_(new Cellular(&modem_info_,
+                               "",
+                               "",
+                               0,
+                               Cellular::kTypeGSM,
+                               "",
+                               "",
+                               "",
+                               &proxy_factory_)) {
+    modem_info_.metrics()->RegisterDevice(cellular_->interface_index(),
+                            Technology::kCellular);
+  }
+
+  virtual ~CellularCapabilityTest() {
+    cellular_->service_ = nullptr;
+    capability_ = nullptr;
+    device_adaptor_ = nullptr;
+  }
+
+  virtual void SetUp() {
+    static_cast<Device *>(cellular_)->rtnl_handler_ = &rtnl_handler_;
+
+    capability_ = dynamic_cast<CellularCapabilityClassic *>(
+        cellular_->capability_.get());
+    device_adaptor_ =
+        dynamic_cast<DeviceMockAdaptor*>(cellular_->adaptor());
+    ASSERT_NE(nullptr, device_adaptor_);;
+  }
+
+  virtual void TearDown() {
+    capability_->proxy_factory_ = nullptr;
+  }
+
+  void CreateService() {
+    // The following constants are never directly accessed by the tests.
+    const char kStorageIdentifier[] = "default_test_storage_id";
+    const char kFriendlyServiceName[] = "default_test_service_name";
+    const char kOperatorCode[] = "10010";
+    const char kOperatorName[] = "default_test_operator_name";
+    const char kOperatorCountry[] = "us";
+
+    // Simulate all the side-effects of Cellular::CreateService
+    auto service = new CellularService(&modem_info_, cellular_);
+    service->SetStorageIdentifier(kStorageIdentifier);
+    service->SetFriendlyName(kFriendlyServiceName);
+
+    Stringmap serving_operator;
+    serving_operator[kOperatorCodeKey] = kOperatorCode;
+    serving_operator[kOperatorNameKey] = kOperatorName;
+    serving_operator[kOperatorCountryKey] = kOperatorCountry;
+
+    service->set_serving_operator(serving_operator);
+    cellular_->set_home_provider(serving_operator);
+    cellular_->service_ = service;
+  }
+
+  CellularCapabilityGSM *GetGsmCapability() {
+    return dynamic_cast<CellularCapabilityGSM *>(cellular_->capability_.get());
+  }
+
+  void ReleaseCapabilityProxies() {
+    capability_->ReleaseProxies();
+  }
+
+  void InvokeEnable(bool enable, Error *error,
+                    const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeEnableFail(bool enable, Error *error,
+                        const ResultCallback &callback, int timeout) {
+    callback.Run(Error(Error::kOperationFailed));
+  }
+  void InvokeDisconnect(Error *error, const ResultCallback &callback,
+                        int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeDisconnectFail(Error *error, const ResultCallback &callback,
+                            int timeout) {
+    callback.Run(Error(Error::kOperationFailed));
+  }
+  void InvokeGetModemStatus(Error *error,
+                            const DBusPropertyMapCallback &callback,
+                            int timeout) {
+    DBusPropertiesMap props;
+    props["carrier"].writer().append_string(kTestCarrier);
+    props["unknown-property"].writer().append_string("irrelevant-value");
+    callback.Run(props, Error());
+  }
+  void InvokeGetModemInfo(Error *error, const ModemInfoCallback &callback,
+                          int timeout) {
+    ModemHardwareInfo info;
+    info._1 = kManufacturer;
+    info._2 = kModelID;
+    info._3 = kHWRev;
+    callback.Run(info, Error());
+  }
+  void InvokeSetCarrier(const string &carrier, Error *error,
+                        const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+
+  MOCK_METHOD1(TestCallback, void(const Error &error));
+
+ protected:
+  static const char kTestMobileProviderDBPath[];
+  static const char kTestCarrier[];
+  static const char kManufacturer[];
+  static const char kModelID[];
+  static const char kHWRev[];
+
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(CellularCapabilityTest *test) : test_(test) {}
+
+    virtual ModemProxyInterface *CreateModemProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->proxy_.release();
+    }
+
+    virtual ModemSimpleProxyInterface *CreateModemSimpleProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->simple_proxy_.release();
+    }
+
+    virtual ModemCDMAProxyInterface *CreateModemCDMAProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->cdma_proxy_.release();
+    }
+
+    virtual ModemGSMCardProxyInterface *CreateModemGSMCardProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      // TODO(benchan): This code conditionally returns a nullptr to avoid
+      // CellularCapabilityGSM::InitProperties (and thus
+      // CellularCapabilityGSM::GetIMSI) from being called during the
+      // construction. Remove this workaround after refactoring the tests.
+      return test_->create_gsm_card_proxy_from_factory_ ?
+          test_->gsm_card_proxy_.release() : nullptr;
+    }
+
+    virtual ModemGSMNetworkProxyInterface *CreateModemGSMNetworkProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->gsm_network_proxy_.release();
+    }
+
+    virtual ModemGobiProxyInterface *CreateModemGobiProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->gobi_proxy_.release();
+    }
+
+   private:
+    CellularCapabilityTest *test_;
+  };
+
+  void SetProxy() {
+    capability_->proxy_.reset(proxy_.release());
+  }
+
+  void SetSimpleProxy() {
+    capability_->simple_proxy_.reset(simple_proxy_.release());
+  }
+
+  void SetGSMNetworkProxy() {
+    CellularCapabilityGSM *gsm_capability =
+        dynamic_cast<CellularCapabilityGSM *>(cellular_->capability_.get());
+    gsm_capability->network_proxy_.reset(gsm_network_proxy_.release());
+  }
+
+  void SetCellularType(Cellular::Type type) {
+    cellular_->InitCapability(type);
+    capability_ = dynamic_cast<CellularCapabilityClassic *>(
+        cellular_->capability_.get());
+  }
+
+  void AllowCreateGSMCardProxyFromFactory() {
+    create_gsm_card_proxy_from_factory_ = true;
+  }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  MockRTNLHandler rtnl_handler_;
+  bool create_gsm_card_proxy_from_factory_;
+  std::unique_ptr<MockModemProxy> proxy_;
+  std::unique_ptr<MockModemSimpleProxy> simple_proxy_;
+  std::unique_ptr<MockModemCDMAProxy> cdma_proxy_;
+  std::unique_ptr<MockModemGSMCardProxy> gsm_card_proxy_;
+  std::unique_ptr<MockModemGSMNetworkProxy> gsm_network_proxy_;
+  std::unique_ptr<MockModemGobiProxy> gobi_proxy_;
+  TestProxyFactory proxy_factory_;
+  CellularCapabilityClassic *capability_;  // Owned by |cellular_|.
+  DeviceMockAdaptor *device_adaptor_;  // Owned by |cellular_|.
+  CellularRefPtr cellular_;
+};
+
+const char CellularCapabilityTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
+const char CellularCapabilityTest::kTestCarrier[] = "The Cellular Carrier";
+const char CellularCapabilityTest::kManufacturer[] = "Company";
+const char CellularCapabilityTest::kModelID[] = "Gobi 2000";
+const char CellularCapabilityTest::kHWRev[] = "A00B1234";
+
+TEST_F(CellularCapabilityTest, GetModemStatus) {
+  SetCellularType(Cellular::kTypeCDMA);
+  EXPECT_CALL(*simple_proxy_,
+              GetModemStatus(_, _, CellularCapability::kTimeoutDefault)).
+      WillOnce(Invoke(this, &CellularCapabilityTest::InvokeGetModemStatus));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetSimpleProxy();
+  ResultCallback callback =
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this));
+  capability_->GetModemStatus(callback);
+  EXPECT_EQ(kTestCarrier, cellular_->carrier());
+}
+
+TEST_F(CellularCapabilityTest, GetModemInfo) {
+  EXPECT_CALL(*proxy_, GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityTest::InvokeGetModemInfo));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetProxy();
+  ResultCallback callback =
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this));
+  capability_->GetModemInfo(callback);
+  EXPECT_EQ(kManufacturer, cellular_->manufacturer());
+  EXPECT_EQ(kModelID, cellular_->model_id());
+  EXPECT_EQ(kHWRev, cellular_->hardware_revision());
+}
+
+TEST_F(CellularCapabilityTest, EnableModemSucceed) {
+  EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this, &CellularCapabilityTest::InvokeEnable));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  ResultCallback callback =
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this));
+  SetProxy();
+  capability_->EnableModem(callback);
+}
+
+TEST_F(CellularCapabilityTest, EnableModemFail) {
+  EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this, &CellularCapabilityTest::InvokeEnableFail));
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  ResultCallback callback =
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this));
+  SetProxy();
+  capability_->EnableModem(callback);
+}
+
+TEST_F(CellularCapabilityTest, FinishEnable) {
+  EXPECT_CALL(*gsm_network_proxy_,
+              GetRegistrationInfo(nullptr, _,
+                                  CellularCapability::kTimeoutDefault));
+  EXPECT_CALL(
+      *gsm_network_proxy_,
+      GetSignalQuality(nullptr, _, CellularCapability::kTimeoutDefault));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetGSMNetworkProxy();
+  capability_->FinishEnable(
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this)));
+}
+
+TEST_F(CellularCapabilityTest, UnsupportedOperation) {
+  Error error;
+  EXPECT_CALL(*this, TestCallback(IsSuccess())).Times(0);
+  capability_->CellularCapability::Reset(
+      &error,
+      Bind(&CellularCapabilityTest::TestCallback, Unretained(this)));
+  EXPECT_TRUE(error.IsFailure());
+  EXPECT_EQ(Error::kNotSupported, error.type());
+}
+
+TEST_F(CellularCapabilityTest, AllowRoaming) {
+  EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
+  cellular_->SetAllowRoaming(false, nullptr);
+  EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
+
+  {
+    InSequence seq;
+    EXPECT_CALL(*device_adaptor_,
+                EmitBoolChanged(kCellularAllowRoamingProperty, true));
+    EXPECT_CALL(*device_adaptor_,
+                EmitBoolChanged(kCellularAllowRoamingProperty, false));
+  }
+
+  cellular_->state_ = Cellular::kStateConnected;
+  dynamic_cast<CellularCapabilityGSM *>(capability_)->registration_state_ =
+      MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING;
+  cellular_->SetAllowRoaming(true, nullptr);
+  EXPECT_TRUE(cellular_->GetAllowRoaming(nullptr));
+  EXPECT_EQ(Cellular::kStateConnected, cellular_->state_);
+
+  EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .WillOnce(Invoke(this, &CellularCapabilityTest::InvokeDisconnect));
+  SetProxy();
+  cellular_->state_ = Cellular::kStateConnected;
+  cellular_->SetAllowRoaming(false, nullptr);
+  EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
+  EXPECT_EQ(Cellular::kStateRegistered, cellular_->state_);
+}
+
+TEST_F(CellularCapabilityTest, SetCarrier) {
+  static const char kCarrier[] = "Generic UMTS";
+  EXPECT_CALL(
+      *gobi_proxy_,
+      SetCarrier(kCarrier, _, _,
+                 CellularCapabilityClassic::kTimeoutSetCarrierMilliseconds))
+      .WillOnce(Invoke(this, &CellularCapabilityTest::InvokeSetCarrier));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  Error error;
+  capability_->SetCarrier(kCarrier, &error,
+                          Bind(&CellularCapabilityTest::TestCallback,
+                               Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+MATCHER_P(HasApn, apn, "") {
+  DBusPropertiesMap::const_iterator it = arg.find(kApnProperty);
+  return it != arg.end() && apn == it->second.reader().get_string();
+}
+
+MATCHER(HasNoApn, "") {
+  return arg.find(kApnProperty) == arg.end();
+}
+
+TEST_F(CellularCapabilityTest, TryApns) {
+  static const string kLastGoodApn("remembered.apn");
+  static const string kLastGoodUsername("remembered.user");
+  static const string kSuppliedApn("my.apn");
+  static const string kTmobileApn1("epc.tmobile.com");
+  static const string kTmobileApn2("wap.voicestream.com");
+  static const string kTmobileApn3("internet2.voicestream.com");
+  static const string kTmobileApn4("internet3.voicestream.com");
+  const Stringmaps kDatabaseApnList {{{ kApnProperty, kTmobileApn1 }},
+                                     {{ kApnProperty, kTmobileApn2 }},
+                                     {{ kApnProperty, kTmobileApn3 }},
+                                     {{ kApnProperty, kTmobileApn4 }}};
+
+
+  CreateService();
+  // Supply the database APNs to |cellular_| object.
+  cellular_->set_apn_list(kDatabaseApnList);
+  ProfileRefPtr profile(new NiceMock<MockProfile>(
+      modem_info_.control_interface(), modem_info_.metrics(),
+      modem_info_.manager()));
+  cellular_->service()->set_profile(profile);
+
+  Error error;
+  Stringmap apn_info;
+  DBusPropertiesMap props;
+  CellularCapabilityGSM *gsm_capability = GetGsmCapability();
+
+  apn_info[kApnProperty] = kLastGoodApn;
+  apn_info[kApnUsernameProperty] = kLastGoodUsername;
+  cellular_->service()->SetLastGoodApn(apn_info);
+  props.clear();
+  EXPECT_TRUE(props.find(kApnProperty) == props.end());
+  gsm_capability->SetupConnectProperties(&props);
+  // We expect the list to contain the last good APN, plus
+  // the 4 APNs from the mobile provider info database.
+  EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
+  EXPECT_FALSE(props.find(kApnProperty) == props.end());
+  EXPECT_EQ(kLastGoodApn, props[kApnProperty].reader().get_string());
+  EXPECT_FALSE(props.find(kApnUsernameProperty) == props.end());
+  EXPECT_EQ(kLastGoodUsername,
+            props[kApnUsernameProperty].reader().get_string());
+
+  apn_info.clear();
+  props.clear();
+  apn_info[kApnProperty] = kSuppliedApn;
+  // Setting the APN has the side effect of clearing the LastGoodApn,
+  // so the try list will have 5 elements, with the first one being
+  // the supplied APN.
+  cellular_->service()->SetApn(apn_info, &error);
+  EXPECT_TRUE(props.find(kApnProperty) == props.end());
+  gsm_capability->SetupConnectProperties(&props);
+  EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
+  EXPECT_FALSE(props.find(kApnProperty) == props.end());
+  EXPECT_EQ(kSuppliedApn, props[kApnProperty].reader().get_string());
+
+  apn_info.clear();
+  props.clear();
+  apn_info[kApnProperty] = kLastGoodApn;
+  apn_info[kApnUsernameProperty] = kLastGoodUsername;
+  // Now when LastGoodAPN is set, it will be the one selected.
+  cellular_->service()->SetLastGoodApn(apn_info);
+  EXPECT_TRUE(props.find(kApnProperty) == props.end());
+  gsm_capability->SetupConnectProperties(&props);
+  // We expect the list to contain the last good APN, plus
+  // the user-supplied APN, plus the 4 APNs from the mobile
+  // provider info database.
+  EXPECT_EQ(6, gsm_capability->apn_try_list_.size());
+  EXPECT_FALSE(props.find(kApnProperty) == props.end());
+  EXPECT_EQ(kLastGoodApn, props[kApnProperty].reader().get_string());
+
+  // Now try all the given APNs.
+  using testing::InSequence;
+  {
+    InSequence dummy;
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kLastGoodApn), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kSuppliedApn), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn1), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn2), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn3), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn4), _, _, _));
+    EXPECT_CALL(*simple_proxy_, Connect(HasNoApn(), _, _, _));
+  }
+  SetSimpleProxy();
+  gsm_capability->Connect(props, &error, ResultCallback());
+  Error cerror(Error::kInvalidApn);
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(4, gsm_capability->apn_try_list_.size());
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(3, gsm_capability->apn_try_list_.size());
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(2, gsm_capability->apn_try_list_.size());
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(1, gsm_capability->apn_try_list_.size());
+  gsm_capability->OnConnectReply(ResultCallback(), cerror);
+  EXPECT_EQ(0, gsm_capability->apn_try_list_.size());
+}
+
+TEST_F(CellularCapabilityTest, StopModemDisconnectSuccess) {
+  EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityTest::InvokeDisconnect));
+  EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityTest::InvokeEnable));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetProxy();
+
+  Error error;
+  capability_->StopModem(
+      &error, Bind(&CellularCapabilityTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityTest, StopModemDisconnectFail) {
+  EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityTest::InvokeDisconnectFail));
+  EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityTest::InvokeEnable));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetProxy();
+
+  Error error;
+  capability_->StopModem(
+      &error, Bind(&CellularCapabilityTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityTest, DisconnectNoProxy) {
+  Error error;
+  ResultCallback disconnect_callback;
+  EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .Times(0);
+  ReleaseCapabilityProxies();
+  capability_->Disconnect(&error, disconnect_callback);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_gsm.cc b/cellular/cellular_capability_gsm.cc
new file mode 100644
index 0000000..7bbd66c
--- /dev/null
+++ b/cellular/cellular_capability_gsm.cc
@@ -0,0 +1,798 @@
+// 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/cellular_capability_gsm.h"
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/stl_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mm/mm-modem.h>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/property_accessor.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapabilityGSM *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+// static
+const char CellularCapabilityGSM::kNetworkPropertyAccessTechnology[] =
+    "access-tech";
+const char CellularCapabilityGSM::kNetworkPropertyID[] = "operator-num";
+const char CellularCapabilityGSM::kNetworkPropertyLongName[] = "operator-long";
+const char CellularCapabilityGSM::kNetworkPropertyShortName[] =
+    "operator-short";
+const char CellularCapabilityGSM::kNetworkPropertyStatus[] = "status";
+const char CellularCapabilityGSM::kPhoneNumber[] = "*99#";
+const char CellularCapabilityGSM::kPropertyAccessTechnology[] =
+    "AccessTechnology";
+const char CellularCapabilityGSM::kPropertyEnabledFacilityLocks[] =
+    "EnabledFacilityLocks";
+const char CellularCapabilityGSM::kPropertyUnlockRequired[] = "UnlockRequired";
+const char CellularCapabilityGSM::kPropertyUnlockRetries[] = "UnlockRetries";
+
+const int CellularCapabilityGSM::kGetIMSIRetryLimit = 40;
+const int64_t CellularCapabilityGSM::kGetIMSIRetryDelayMilliseconds = 500;
+
+
+CellularCapabilityGSM::CellularCapabilityGSM(Cellular *cellular,
+                                             ProxyFactory *proxy_factory,
+                                             ModemInfo *modem_info)
+    : CellularCapabilityClassic(cellular, proxy_factory, modem_info),
+      weak_ptr_factory_(this),
+      mobile_operator_info_(new MobileOperatorInfo(cellular->dispatcher(),
+                                                   "ParseScanResult")),
+      registration_state_(MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN),
+      access_technology_(MM_MODEM_GSM_ACCESS_TECH_UNKNOWN),
+      home_provider_info_(nullptr),
+      get_imsi_retries_(0),
+      get_imsi_retry_delay_milliseconds_(kGetIMSIRetryDelayMilliseconds) {
+  SLOG(this, 2) << "Cellular capability constructed: GSM";
+  mobile_operator_info_->Init();
+  HelpRegisterConstDerivedKeyValueStore(
+      kSIMLockStatusProperty, &CellularCapabilityGSM::SimLockStatusToProperty);
+  this->cellular()->set_scanning_supported(true);
+
+  // TODO(benchan): This is a hack to initialize the GSM card proxy for GetIMSI
+  // before InitProxies is called. There are side-effects of calling InitProxies
+  // before the device is enabled. It's better to refactor InitProxies such that
+  // proxies can be created when the cellular device/capability is constructed,
+  // but callbacks for DBus signal updates are not set up until the device is
+  // enabled.
+  card_proxy_.reset(
+      proxy_factory->CreateModemGSMCardProxy(cellular->dbus_path(),
+                                             cellular->dbus_owner()));
+  // TODO(benchan): To allow unit testing using a mock proxy without further
+  // complicating the code, the test proxy factory is set up to return a nullptr
+  // pointer when CellularCapabilityGSM is constructed. Refactor the code to
+  // avoid this hack.
+  if (card_proxy_.get())
+    InitProperties();
+}
+
+CellularCapabilityGSM::~CellularCapabilityGSM() {}
+
+string CellularCapabilityGSM::GetTypeString() const {
+  return kTechnologyFamilyGsm;
+}
+
+KeyValueStore CellularCapabilityGSM::SimLockStatusToProperty(Error */*error*/) {
+  KeyValueStore status;
+  status.SetBool(kSIMLockEnabledProperty, sim_lock_status_.enabled);
+  status.SetString(kSIMLockTypeProperty, sim_lock_status_.lock_type);
+  status.SetUint(kSIMLockRetriesLeftProperty, sim_lock_status_.retries_left);
+  return status;
+}
+
+void CellularCapabilityGSM::HelpRegisterConstDerivedKeyValueStore(
+    const string &name,
+    KeyValueStore(CellularCapabilityGSM::*get)(Error *error)) {
+  cellular()->mutable_store()->RegisterDerivedKeyValueStore(
+      name,
+      KeyValueStoreAccessor(
+          new CustomAccessor<CellularCapabilityGSM, KeyValueStore>(
+              this, get, nullptr)));
+}
+
+void CellularCapabilityGSM::InitProxies() {
+  CellularCapabilityClassic::InitProxies();
+  // TODO(benchan): Remove this check after refactoring the proxy
+  // initialization.
+  if (!card_proxy_.get()) {
+    card_proxy_.reset(
+        proxy_factory()->CreateModemGSMCardProxy(cellular()->dbus_path(),
+                                                 cellular()->dbus_owner()));
+  }
+  network_proxy_.reset(
+      proxy_factory()->CreateModemGSMNetworkProxy(cellular()->dbus_path(),
+                                                  cellular()->dbus_owner()));
+  network_proxy_->set_signal_quality_callback(
+      Bind(&CellularCapabilityGSM::OnSignalQualitySignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  network_proxy_->set_network_mode_callback(
+      Bind(&CellularCapabilityGSM::OnNetworkModeSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  network_proxy_->set_registration_info_callback(
+      Bind(&CellularCapabilityGSM::OnRegistrationInfoSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CellularCapabilityGSM::InitProperties() {
+  CellularTaskList *tasks = new CellularTaskList();
+  ResultCallback cb_ignore_error =
+      Bind(&CellularCapabilityGSM::StepCompletedCallback,
+           weak_ptr_factory_.GetWeakPtr(), ResultCallback(), true, tasks);
+  // Chrome checks if a SIM is present before allowing the modem to be enabled,
+  // so shill needs to obtain IMSI, as an indicator of SIM presence, even
+  // before the device is enabled.
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetIMSI,
+                        weak_ptr_factory_.GetWeakPtr(), cb_ignore_error));
+  RunNextStep(tasks);
+}
+
+void CellularCapabilityGSM::StartModem(Error *error,
+                                       const ResultCallback &callback) {
+  InitProxies();
+
+  CellularTaskList *tasks = new CellularTaskList();
+  ResultCallback cb =
+      Bind(&CellularCapabilityGSM::StepCompletedCallback,
+           weak_ptr_factory_.GetWeakPtr(), callback, false, tasks);
+  ResultCallback cb_ignore_error =
+        Bind(&CellularCapabilityGSM::StepCompletedCallback,
+                   weak_ptr_factory_.GetWeakPtr(), callback, true, tasks);
+  if (!cellular()->IsUnderlyingDeviceEnabled())
+    tasks->push_back(Bind(&CellularCapabilityGSM::EnableModem,
+                          weak_ptr_factory_.GetWeakPtr(), cb));
+  // If we're within range of the home network, the modem will try to
+  // register once it's enabled, or may be already registered if we
+  // started out enabled.
+  if (!IsUnderlyingDeviceRegistered() &&
+      !cellular()->selected_network().empty())
+    tasks->push_back(Bind(&CellularCapabilityGSM::Register,
+                          weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetIMEI,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  get_imsi_retries_ = 0;
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetIMSI,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetSPN,
+                        weak_ptr_factory_.GetWeakPtr(), cb_ignore_error));
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetMSISDN,
+                        weak_ptr_factory_.GetWeakPtr(), cb_ignore_error));
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetProperties,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+  tasks->push_back(Bind(&CellularCapabilityGSM::GetModemInfo,
+                        weak_ptr_factory_.GetWeakPtr(), cb_ignore_error));
+  tasks->push_back(Bind(&CellularCapabilityGSM::FinishEnable,
+                        weak_ptr_factory_.GetWeakPtr(), cb));
+
+  RunNextStep(tasks);
+}
+
+bool CellularCapabilityGSM::IsUnderlyingDeviceRegistered() const {
+  switch (cellular()->modem_state()) {
+    case Cellular::kModemStateFailed:
+    case Cellular::kModemStateUnknown:
+    case Cellular::kModemStateDisabled:
+    case Cellular::kModemStateInitializing:
+    case Cellular::kModemStateLocked:
+    case Cellular::kModemStateDisabling:
+    case Cellular::kModemStateEnabling:
+    case Cellular::kModemStateEnabled:
+      return false;
+    case Cellular::kModemStateSearching:
+    case Cellular::kModemStateRegistered:
+    case Cellular::kModemStateDisconnecting:
+    case Cellular::kModemStateConnecting:
+    case Cellular::kModemStateConnected:
+      return true;
+  }
+  return false;
+}
+
+void CellularCapabilityGSM::ReleaseProxies() {
+  SLOG(this, 2) << __func__;
+  CellularCapabilityClassic::ReleaseProxies();
+  card_proxy_.reset();
+  network_proxy_.reset();
+}
+
+bool CellularCapabilityGSM::AreProxiesInitialized() const {
+  return (CellularCapabilityClassic::AreProxiesInitialized() &&
+          card_proxy_.get() && network_proxy_.get());
+}
+
+void CellularCapabilityGSM::OnServiceCreated() {
+  cellular()->service()->SetActivationState(kActivationStateActivated);
+}
+
+// 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 CellularCapabilityGSM::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(),
+                       cellular()->apn_list().begin(),
+                       cellular()->apn_list().end());
+}
+
+void CellularCapabilityGSM::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  SetupApnTryList();
+  FillConnectPropertyMap(properties);
+}
+
+void CellularCapabilityGSM::FillConnectPropertyMap(
+    DBusPropertiesMap *properties) {
+  (*properties)[kConnectPropertyPhoneNumber].writer().append_string(
+      kPhoneNumber);
+
+  if (!AllowRoaming())
+    (*properties)[kConnectPropertyHomeOnly].writer().append_bool(true);
+
+  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();
+    SLOG(this, 2) << __func__ << ": Using APN " << apn_info[kApnProperty];
+    (*properties)[kConnectPropertyApn].writer().append_string(
+        apn_info[kApnProperty].c_str());
+    if (ContainsKey(apn_info, kApnUsernameProperty))
+      (*properties)[kConnectPropertyApnUsername].writer().append_string(
+          apn_info[kApnUsernameProperty].c_str());
+    if (ContainsKey(apn_info, kApnPasswordProperty))
+      (*properties)[kConnectPropertyApnPassword].writer().append_string(
+          apn_info[kApnPasswordProperty].c_str());
+  }
+}
+
+void CellularCapabilityGSM::Connect(const DBusPropertiesMap &properties,
+                                    Error *error,
+                                    const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  ResultCallback cb = Bind(&CellularCapabilityGSM::OnConnectReply,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           callback);
+  simple_proxy_->Connect(properties, error, cb, kTimeoutConnect);
+}
+
+void CellularCapabilityGSM::OnConnectReply(const ResultCallback &callback,
+                                           const Error &error) {
+  CellularServiceRefPtr service = cellular()->service();
+  if (!service) {
+    // The service could have been deleted before our Connect() request
+    // completes if the modem was enabled and then quickly disabled.
+    apn_try_list_.clear();
+  } else if (error.IsFailure()) {
+    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();
+      SLOG(this, 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;
+    }
+  } else if (!apn_try_list_.empty()) {
+    service->SetLastGoodApn(apn_try_list_.front());
+    apn_try_list_.clear();
+  }
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+bool CellularCapabilityGSM::AllowRoaming() {
+  return cellular()->provider_requires_roaming() || allow_roaming_property();
+}
+
+// always called from an async context
+void CellularCapabilityGSM::GetIMEI(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  if (cellular()->imei().empty()) {
+    GSMIdentifierCallback cb = Bind(&CellularCapabilityGSM::OnGetIMEIReply,
+                                    weak_ptr_factory_.GetWeakPtr(), callback);
+    card_proxy_->GetIMEI(&error, cb, kTimeoutDefault);
+    if (error.IsFailure())
+      callback.Run(error);
+  } else {
+    SLOG(this, 2) << "Already have IMEI " << cellular()->imei();
+    callback.Run(error);
+  }
+}
+
+// always called from an async context
+void CellularCapabilityGSM::GetIMSI(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  if (cellular()->imsi().empty()) {
+    GSMIdentifierCallback cb = Bind(&CellularCapabilityGSM::OnGetIMSIReply,
+                                    weak_ptr_factory_.GetWeakPtr(),
+                                    callback);
+    card_proxy_->GetIMSI(&error, cb, kTimeoutDefault);
+    if (error.IsFailure()) {
+      cellular()->home_provider_info()->Reset();
+      callback.Run(error);
+    }
+  } else {
+    SLOG(this, 2) << "Already have IMSI " << cellular()->imsi();
+    callback.Run(error);
+  }
+}
+
+// always called from an async context
+void CellularCapabilityGSM::GetSPN(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  if (spn_.empty()) {
+    GSMIdentifierCallback cb = Bind(&CellularCapabilityGSM::OnGetSPNReply,
+                                    weak_ptr_factory_.GetWeakPtr(),
+                                    callback);
+    card_proxy_->GetSPN(&error, cb, kTimeoutDefault);
+    if (error.IsFailure())
+      callback.Run(error);
+  } else {
+    SLOG(this, 2) << "Already have SPN " << spn_;
+    callback.Run(error);
+  }
+}
+
+// always called from an async context
+void CellularCapabilityGSM::GetMSISDN(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+  CHECK(!callback.is_null());
+  Error error;
+  string mdn = cellular()->mdn();
+  if (mdn.empty()) {
+    GSMIdentifierCallback cb = Bind(&CellularCapabilityGSM::OnGetMSISDNReply,
+                                    weak_ptr_factory_.GetWeakPtr(),
+                                    callback);
+    card_proxy_->GetMSISDN(&error, cb, kTimeoutDefault);
+    if (error.IsFailure())
+      callback.Run(error);
+  } else {
+    SLOG(this, 2) << "Already have MSISDN " << mdn;
+    callback.Run(error);
+  }
+}
+
+void CellularCapabilityGSM::GetSignalQuality() {
+  SLOG(this, 2) << __func__;
+  SignalQualityCallback callback =
+      Bind(&CellularCapabilityGSM::OnGetSignalQualityReply,
+           weak_ptr_factory_.GetWeakPtr());
+  network_proxy_->GetSignalQuality(nullptr, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::GetRegistrationState() {
+  SLOG(this, 2) << __func__;
+  RegistrationInfoCallback callback =
+      Bind(&CellularCapabilityGSM::OnGetRegistrationInfoReply,
+           weak_ptr_factory_.GetWeakPtr());
+  network_proxy_->GetRegistrationInfo(nullptr, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::GetProperties(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__;
+
+  // TODO(petkov): Switch to asynchronous calls (crbug.com/200687).
+  uint32_t tech = network_proxy_->AccessTechnology();
+  SetAccessTechnology(tech);
+  SLOG(this, 2) << "GSM AccessTechnology: " << tech;
+
+  // TODO(petkov): Switch to asynchronous calls (crbug.com/200687).
+  uint32_t locks = card_proxy_->EnabledFacilityLocks();
+  sim_lock_status_.enabled = locks & MM_MODEM_GSM_FACILITY_SIM;
+  SLOG(this, 2) << "GSM EnabledFacilityLocks: " << locks;
+
+  callback.Run(Error());
+}
+
+// always called from an async context
+void CellularCapabilityGSM::Register(const ResultCallback &callback) {
+  SLOG(this, 2) << __func__ << " \"" << cellular()->selected_network()
+                << "\"";
+  CHECK(!callback.is_null());
+  Error error;
+  ResultCallback cb = Bind(&CellularCapabilityGSM::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  network_proxy_->Register(cellular()->selected_network(), &error, cb,
+                           kTimeoutRegister);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityGSM::RegisterOnNetwork(
+    const string &network_id,
+    Error *error,
+    const ResultCallback &callback) {
+  SLOG(this, 2) << __func__ << "(" << network_id << ")";
+  CHECK(error);
+  desired_network_ = network_id;
+  ResultCallback cb = Bind(&CellularCapabilityGSM::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  network_proxy_->Register(network_id, error, cb, kTimeoutRegister);
+}
+
+void CellularCapabilityGSM::OnRegisterReply(const ResultCallback &callback,
+                                            const Error &error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+
+  if (error.IsSuccess()) {
+    cellular()->set_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();
+    cellular()->set_selected_network("");
+    LOG(INFO) << "Couldn't register on selected network, trying home network";
+    Register(callback);
+    return;
+  }
+  callback.Run(error);
+}
+
+bool CellularCapabilityGSM::IsRegistered() const {
+  return (registration_state_ == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
+          registration_state_ == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING);
+}
+
+void CellularCapabilityGSM::SetUnregistered(bool searching) {
+  // If we're already in some non-registered state, don't override that
+  if (registration_state_ == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME ||
+      registration_state_ == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) {
+    registration_state_ =
+        (searching ? MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING :
+                     MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE);
+  }
+}
+
+void CellularCapabilityGSM::RequirePIN(
+    const std::string &pin, bool require,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  card_proxy_->EnablePIN(pin, require, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::EnterPIN(const string &pin,
+                                     Error *error,
+                                     const ResultCallback &callback) {
+  CHECK(error);
+  card_proxy_->SendPIN(pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::UnblockPIN(const string &unblock_code,
+                                       const string &pin,
+                                       Error *error,
+                                       const ResultCallback &callback) {
+  CHECK(error);
+  card_proxy_->SendPUK(unblock_code, pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::ChangePIN(
+    const string &old_pin, const string &new_pin,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  card_proxy_->ChangePIN(old_pin, new_pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityGSM::Scan(Error *error,
+                                 const ResultStringmapsCallback &callback) {
+  ScanResultsCallback cb = Bind(&CellularCapabilityGSM::OnScanReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  network_proxy_->Scan(error, cb, kTimeoutScan);
+}
+
+void CellularCapabilityGSM::OnScanReply(
+    const ResultStringmapsCallback &callback,
+    const GSMScanResults &results,
+    const Error &error) {
+  Stringmaps found_networks;
+  for (const auto &result : results)
+    found_networks.push_back(ParseScanResult(result));
+  callback.Run(found_networks, error);
+}
+
+Stringmap CellularCapabilityGSM::ParseScanResult(const GSMScanResult &result) {
+  Stringmap parsed;
+  for (GSMScanResult::const_iterator it = result.begin();
+       it != result.end(); ++it) {
+    // TODO(petkov): Define these in system_api/service_constants.h. The
+    // numerical values are taken from 3GPP TS 27.007 Section 7.3.
+    static const char * const kStatusString[] = {
+      "unknown",
+      "available",
+      "current",
+      "forbidden",
+    };
+    static const char * const kTechnologyString[] = {
+      kNetworkTechnologyGsm,
+      "GSM Compact",
+      kNetworkTechnologyUmts,
+      kNetworkTechnologyEdge,
+      "HSDPA",
+      "HSUPA",
+      kNetworkTechnologyHspa,
+    };
+    SLOG(this, 2) << "Network property: " << it->first << " = "
+                  << it->second;
+    if (it->first == kNetworkPropertyStatus) {
+      int status = 0;
+      if (base::StringToInt(it->second, &status) &&
+          status >= 0 &&
+          status < static_cast<int>(arraysize(kStatusString))) {
+        parsed[kStatusProperty] = kStatusString[status];
+      } else {
+        LOG(ERROR) << "Unexpected status value: " << it->second;
+      }
+    } else if (it->first == kNetworkPropertyID) {
+      parsed[kNetworkIdProperty] = it->second;
+    } else if (it->first == kNetworkPropertyLongName) {
+      parsed[kLongNameProperty] = it->second;
+    } else if (it->first == kNetworkPropertyShortName) {
+      parsed[kShortNameProperty] = it->second;
+    } else if (it->first == kNetworkPropertyAccessTechnology) {
+      int tech = 0;
+      if (base::StringToInt(it->second, &tech) &&
+          tech >= 0 &&
+          tech < static_cast<int>(arraysize(kTechnologyString))) {
+        parsed[kTechnologyProperty] = kTechnologyString[tech];
+      } else {
+        LOG(ERROR) << "Unexpected technology value: " << it->second;
+      }
+    } else {
+      LOG(WARNING) << "Unknown network property ignored: " << it->first;
+    }
+  }
+  // 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, kLongNameProperty) ||
+       parsed[kLongNameProperty].empty()) &&
+      ContainsKey(parsed, kNetworkIdProperty)) {
+    mobile_operator_info_->Reset();
+    mobile_operator_info_->UpdateMCCMNC(parsed[kNetworkIdProperty]);
+    if (mobile_operator_info_->IsMobileNetworkOperatorKnown() &&
+        !mobile_operator_info_->operator_name().empty()) {
+      parsed[kLongNameProperty] = mobile_operator_info_->operator_name();
+    }
+  }
+  return parsed;
+}
+
+void CellularCapabilityGSM::SetAccessTechnology(uint32_t access_technology) {
+  access_technology_ = access_technology;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
+  }
+}
+
+string CellularCapabilityGSM::GetNetworkTechnologyString() const {
+  switch (access_technology_) {
+    case MM_MODEM_GSM_ACCESS_TECH_GSM:
+    case MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT:
+      return kNetworkTechnologyGsm;
+    case MM_MODEM_GSM_ACCESS_TECH_GPRS:
+      return kNetworkTechnologyGprs;
+    case MM_MODEM_GSM_ACCESS_TECH_EDGE:
+      return kNetworkTechnologyEdge;
+    case MM_MODEM_GSM_ACCESS_TECH_UMTS:
+      return kNetworkTechnologyUmts;
+    case MM_MODEM_GSM_ACCESS_TECH_HSDPA:
+    case MM_MODEM_GSM_ACCESS_TECH_HSUPA:
+    case MM_MODEM_GSM_ACCESS_TECH_HSPA:
+      return kNetworkTechnologyHspa;
+    case MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS:
+      return kNetworkTechnologyHspaPlus;
+    default:
+      break;
+  }
+  return "";
+}
+
+string CellularCapabilityGSM::GetRoamingStateString() const {
+  switch (registration_state_) {
+    case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME:
+      return kRoamingStateHome;
+    case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING:
+      return kRoamingStateRoaming;
+    default:
+      break;
+  }
+  return kRoamingStateUnknown;
+}
+
+void CellularCapabilityGSM::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &properties,
+    const vector<string> &invalidated_properties) {
+  CellularCapabilityClassic::OnDBusPropertiesChanged(interface,
+                                                     properties,
+                                                     invalidated_properties);
+  if (interface == MM_MODEM_GSM_NETWORK_INTERFACE) {
+    uint32_t access_technology = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN;
+    if (DBusProperties::GetUint32(properties,
+                                  kPropertyAccessTechnology,
+                                  &access_technology)) {
+      SetAccessTechnology(access_technology);
+    }
+  } else {
+    bool emit = false;
+    if (interface == MM_MODEM_GSM_CARD_INTERFACE) {
+      uint32_t locks = 0;
+      if (DBusProperties::GetUint32(
+              properties, kPropertyEnabledFacilityLocks, &locks)) {
+        sim_lock_status_.enabled = locks & MM_MODEM_GSM_FACILITY_SIM;
+        emit = true;
+      }
+    } else if (interface == MM_MODEM_INTERFACE) {
+      if (DBusProperties::GetString(properties, kPropertyUnlockRequired,
+                                    &sim_lock_status_.lock_type)) {
+        emit = true;
+      }
+      if (DBusProperties::GetUint32(properties, kPropertyUnlockRetries,
+                                    &sim_lock_status_.retries_left)) {
+        emit = true;
+      }
+    }
+    // TODO(pprabhu) Rename |emit| to |sim_present| after |sim_lock_status|
+    // moves to cellular.
+    if (emit) {
+      cellular()->set_sim_present(true);
+      cellular()->adaptor()->EmitKeyValueStoreChanged(
+          kSIMLockStatusProperty, SimLockStatusToProperty(nullptr));
+    }
+  }
+}
+
+void CellularCapabilityGSM::OnNetworkModeSignal(uint32_t /*mode*/) {
+  // TODO(petkov): Implement this.
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityGSM::OnRegistrationInfoSignal(
+    uint32_t status, const string &operator_code, const string &operator_name) {
+  SLOG(this, 2) << __func__ << ": regstate=" << status
+                << ", opercode=" << operator_code
+                << ", opername=" << operator_name;
+  registration_state_ = status;
+  cellular()->serving_operator_info()->UpdateMCCMNC(operator_code);
+  cellular()->serving_operator_info()->UpdateOperatorName(operator_name);
+  cellular()->HandleNewRegistrationState();
+}
+
+void CellularCapabilityGSM::OnSignalQualitySignal(uint32_t quality) {
+  cellular()->HandleNewSignalQuality(quality);
+}
+
+void CellularCapabilityGSM::OnGetRegistrationInfoReply(
+    uint32_t status, const string &operator_code, const string &operator_name,
+    const Error &error) {
+  if (error.IsSuccess())
+    OnRegistrationInfoSignal(status, operator_code, operator_name);
+}
+
+void CellularCapabilityGSM::OnGetSignalQualityReply(uint32_t quality,
+                                                    const Error &error) {
+  if (error.IsSuccess())
+    OnSignalQualitySignal(quality);
+}
+
+void CellularCapabilityGSM::OnGetIMEIReply(const ResultCallback &callback,
+                                           const string &imei,
+                                           const Error &error) {
+  if (error.IsSuccess()) {
+    SLOG(this, 2) << "IMEI: " << imei;
+    cellular()->set_imei(imei);
+  } else {
+    SLOG(this, 2) << "GetIMEI failed - " << error;
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityGSM::OnGetIMSIReply(const ResultCallback &callback,
+                                           const string &imsi,
+                                           const Error &error) {
+  if (error.IsSuccess()) {
+    SLOG(this, 2) << "IMSI: " << imsi;
+    cellular()->set_imsi(imsi);
+    cellular()->set_sim_present(true);
+    cellular()->home_provider_info()->UpdateIMSI(imsi);
+    // We do not currently obtain the IMSI OTA at all. Provide the IMSI from the
+    // SIM to the serving operator as well to aid in MVNO identification.
+    cellular()->serving_operator_info()->UpdateIMSI(imsi);
+    callback.Run(error);
+  } else if (!sim_lock_status_.lock_type.empty()) {
+    SLOG(this, 2) << "GetIMSI failed - SIM lock in place.";
+    cellular()->set_sim_present(true);
+    callback.Run(error);
+  } else {
+    cellular()->set_sim_present(false);
+    if (get_imsi_retries_++ < kGetIMSIRetryLimit) {
+      SLOG(this, 2) << "GetIMSI failed - " << error << ". Retrying";
+      base::Callback<void(void)> retry_get_imsi_cb =
+          Bind(&CellularCapabilityGSM::GetIMSI,
+               weak_ptr_factory_.GetWeakPtr(), callback);
+      cellular()->dispatcher()->PostDelayedTask(
+          retry_get_imsi_cb,
+          get_imsi_retry_delay_milliseconds_);
+    } else {
+      LOG(INFO) << "GetIMSI failed - " << error;
+      cellular()->home_provider_info()->Reset();
+      callback.Run(error);
+    }
+  }
+}
+
+void CellularCapabilityGSM::OnGetSPNReply(const ResultCallback &callback,
+                                          const string &spn,
+                                          const Error &error) {
+  if (error.IsSuccess()) {
+    SLOG(this, 2) << "SPN: " << spn;
+    spn_ = spn;
+    cellular()->home_provider_info()->UpdateOperatorName(spn);
+  } else {
+    SLOG(this, 2) << "GetSPN failed - " << error;
+  }
+  callback.Run(error);
+}
+
+void CellularCapabilityGSM::OnGetMSISDNReply(const ResultCallback &callback,
+                                             const string &msisdn,
+                                             const Error &error) {
+  if (error.IsSuccess()) {
+    SLOG(this, 2) << "MSISDN: " << msisdn;
+    cellular()->set_mdn(msisdn);
+  } else {
+    SLOG(this, 2) << "GetMSISDN failed - " << error;
+  }
+  callback.Run(error);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_gsm.h b/cellular/cellular_capability_gsm.h
new file mode 100644
index 0000000..f98b979
--- /dev/null
+++ b/cellular/cellular_capability_gsm.h
@@ -0,0 +1,231 @@
+// 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_CELLULAR_CAPABILITY_GSM_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_GSM_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/accessor_interface.h"
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/cellular_capability_classic.h"
+#include "shill/cellular/modem_gsm_card_proxy_interface.h"
+#include "shill/cellular/modem_gsm_network_proxy_interface.h"
+
+struct mobile_provider;
+
+namespace shill {
+
+class ModemInfo;
+
+class CellularCapabilityGSM : public CellularCapabilityClassic {
+ public:
+  CellularCapabilityGSM(Cellular *cellular,
+                        ProxyFactory *proxy_factory,
+                        ModemInfo *modem_info);
+  ~CellularCapabilityGSM() override;
+
+  // Inherited from CellularCapability.
+  std::string GetTypeString() const override;
+  void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) override;
+  void StartModem(Error *error, const ResultCallback &callback) override;
+  bool AreProxiesInitialized() const override;
+  void Scan(Error *error, const ResultStringmapsCallback &callback) override;
+  void RegisterOnNetwork(const std::string &network_id,
+                         Error *error,
+                         const ResultCallback &callback) override;
+  bool IsRegistered() const override;
+  void SetUnregistered(bool searching) override;
+  void OnServiceCreated() override;
+  std::string GetNetworkTechnologyString() const override;
+  std::string GetRoamingStateString() const override;
+  bool AllowRoaming() override;
+  void GetSignalQuality() override;
+  void SetupConnectProperties(DBusPropertiesMap *properties) override;
+  void Connect(const DBusPropertiesMap &properties,
+               Error *error,
+               const ResultCallback &callback) override;
+  void RequirePIN(const std::string &pin,
+                  bool require,
+                  Error *error,
+                  const ResultCallback &callback) override;
+  void EnterPIN(const std::string &pin,
+                Error *error,
+                const ResultCallback &callback) override;
+  void UnblockPIN(const std::string &unblock_code,
+                  const std::string &pin,
+                  Error *error,
+                  const ResultCallback &callback) override;
+  void ChangePIN(const std::string &old_pin,
+                 const std::string &new_pin,
+                 Error *error,
+                 const ResultCallback &callback) override;
+
+  // Inherited from CellularCapabilityClassic.
+  void GetRegistrationState() override;
+  // The following six methods are only ever called as callbacks (from the main
+  // loop), which is why they don't take an Error * argument.
+  void GetProperties(const ResultCallback &callback) override;
+
+  virtual void GetIMEI(const ResultCallback &callback);
+  virtual void GetIMSI(const ResultCallback &callback);
+  virtual void GetSPN(const ResultCallback &callback);
+  virtual void GetMSISDN(const ResultCallback &callback);
+  virtual void Register(const ResultCallback &callback);
+
+ protected:
+  // Inherited from CellularCapabilityClassic.
+  void InitProxies() override;
+  void ReleaseProxies() override;
+
+  // Initializes properties, such as IMSI, which are required before the device
+  // is enabled.
+  virtual void InitProperties();
+
+ private:
+  friend class CellularTest;
+  friend class CellularCapabilityGSMTest;
+  friend class CellularCapabilityTest;
+  FRIEND_TEST(CellularCapabilityGSMTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityGSMTest, CreateDeviceFromProperties);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetIMEI);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetIMSI);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetIMSIFails);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetMSISDN);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetSPN);
+  FRIEND_TEST(CellularCapabilityGSMTest, RequirePIN);
+  FRIEND_TEST(CellularCapabilityGSMTest, EnterPIN);
+  FRIEND_TEST(CellularCapabilityGSMTest, UnblockPIN);
+  FRIEND_TEST(CellularCapabilityGSMTest, ChangePIN);
+  FRIEND_TEST(CellularCapabilityGSMTest, ParseScanResult);
+  FRIEND_TEST(CellularCapabilityGSMTest, ParseScanResultProviderLookup);
+  FRIEND_TEST(CellularCapabilityGSMTest, RegisterOnNetwork);
+  FRIEND_TEST(CellularCapabilityGSMTest, SetAccessTechnology);
+  FRIEND_TEST(CellularCapabilityGSMTest, GetRegistrationState);
+  FRIEND_TEST(CellularCapabilityGSMTest, OnDBusPropertiesChanged);
+  FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityTest, TryApns);
+  FRIEND_TEST(CellularTest, ScanAsynchronousFailure);
+  FRIEND_TEST(CellularTest, ScanImmediateFailure);
+  FRIEND_TEST(CellularTest, ScanSuccess);
+  FRIEND_TEST(CellularTest, StartGSMRegister);
+  FRIEND_TEST(ModemTest, CreateDeviceFromProperties);
+
+  // SimLockStatus represents the fields in the Cellular.SIMLockStatus
+  // DBUS property of the shill device.
+  struct SimLockStatus {
+   public:
+    SimLockStatus() : enabled(false), retries_left(0) {}
+
+    bool enabled;
+    std::string lock_type;
+    uint32_t retries_left;
+  };
+
+  static const char kNetworkPropertyAccessTechnology[];
+  static const char kNetworkPropertyID[];
+  static const char kNetworkPropertyLongName[];
+  static const char kNetworkPropertyShortName[];
+  static const char kNetworkPropertyStatus[];
+  static const char kPhoneNumber[];
+  static const char kPropertyAccessTechnology[];
+  static const char kPropertyEnabledFacilityLocks[];
+  static const char kPropertyUnlockRequired[];
+  static const char kPropertyUnlockRetries[];
+
+  // Calls to the proxy's GetIMSI() will be retried this many times.
+  static const int kGetIMSIRetryLimit;
+
+  // This much time will pass between retries of GetIMSI().
+  static const int64_t kGetIMSIRetryDelayMilliseconds;
+
+  void SetAccessTechnology(uint32_t access_technology);
+
+  Stringmap ParseScanResult(const GSMScanResult &result);
+
+  KeyValueStore SimLockStatusToProperty(Error *error);
+
+  void SetupApnTryList();
+  void FillConnectPropertyMap(DBusPropertiesMap *properties);
+
+  void HelpRegisterConstDerivedKeyValueStore(
+      const std::string &name,
+      KeyValueStore(CellularCapabilityGSM::*get)(Error *error));
+
+  bool IsUnderlyingDeviceRegistered() const;
+
+  // Signal callbacks
+  void OnNetworkModeSignal(uint32_t mode);
+  void OnRegistrationInfoSignal(uint32_t status,
+                                const std::string &operator_code,
+                                const std::string &operator_name);
+  void OnSignalQualitySignal(uint32_t quality);
+
+  // Method callbacks
+  void OnGetRegistrationInfoReply(uint32_t status,
+                                  const std::string &operator_code,
+                                  const std::string &operator_name,
+                                  const Error &error);
+  void OnGetSignalQualityReply(uint32_t quality, const Error &error);
+  void OnRegisterReply(const ResultCallback &callback,
+                       const Error &error);
+  void OnGetIMEIReply(const ResultCallback &callback,
+                      const std::string &imei,
+                      const Error &error);
+  void OnGetIMSIReply(const ResultCallback &callback,
+                      const std::string &imsi,
+                      const Error &error);
+  void OnGetSPNReply(const ResultCallback &callback,
+                     const std::string &spn,
+                     const Error &error);
+  void OnGetMSISDNReply(const ResultCallback &callback,
+                        const std::string &msisdn,
+                        const Error &error);
+  void OnScanReply(const ResultStringmapsCallback &callback,
+                   const GSMScanResults &results,
+                   const Error &error);
+  void OnConnectReply(const ResultCallback &callback, const Error &error);
+
+  std::unique_ptr<ModemGSMCardProxyInterface> card_proxy_;
+  std::unique_ptr<ModemGSMNetworkProxyInterface> network_proxy_;
+  base::WeakPtrFactory<CellularCapabilityGSM> weak_ptr_factory_;
+  // Used to enrich information about the network operator in |ParseScanResult|.
+  // TODO(pprabhu) Instead instantiate a local |MobileOperatorInfo| instance
+  // once the context has been separated out. (crbug.com/363874)
+  std::unique_ptr<MobileOperatorInfo> mobile_operator_info_;
+
+  uint32_t registration_state_;
+  uint32_t access_technology_;
+  std::string spn_;
+  mobile_provider *home_provider_info_;
+  std::string desired_network_;
+
+  // The number of times GetIMSI() has been retried.
+  int get_imsi_retries_;
+
+  // Amount of time to wait between retries of GetIMSI.  Defaults to
+  // kGetIMSIRetryDelayMilliseconds, but can be altered by a unit test.
+  int64_t get_imsi_retry_delay_milliseconds_;
+
+  // Properties.
+  std::deque<Stringmap> apn_try_list_;
+  SimLockStatus sim_lock_status_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityGSM);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_GSM_H_
diff --git a/cellular/cellular_capability_gsm_unittest.cc b/cellular/cellular_capability_gsm_unittest.cc
new file mode 100644
index 0000000..7c57e0e
--- /dev/null
+++ b/cellular/cellular_capability_gsm_unittest.cc
@@ -0,0 +1,805 @@
+// 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/cellular_capability_gsm.h"
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_mobile_operator_info.h"
+#include "shill/cellular/mock_modem_gsm_card_proxy.h"
+#include "shill/cellular/mock_modem_gsm_network_proxy.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_modem_proxy.h"
+#include "shill/cellular/mock_modem_simple_proxy.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_log.h"
+#include "shill/mock_profile.h"
+#include "shill/proxy_factory.h"
+#include "shill/testing.h"
+
+using base::Bind;
+using base::StringPrintf;
+using base::Unretained;
+using std::string;
+using std::vector;
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+using testing::Return;
+using testing::SaveArg;
+
+namespace shill {
+
+class CellularCapabilityGSMTest : public testing::Test {
+ public:
+  CellularCapabilityGSMTest()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        create_card_proxy_from_factory_(false),
+        proxy_(new MockModemProxy()),
+        simple_proxy_(new MockModemSimpleProxy()),
+        card_proxy_(new MockModemGSMCardProxy()),
+        network_proxy_(new MockModemGSMNetworkProxy()),
+        proxy_factory_(this),
+        capability_(nullptr),
+        device_adaptor_(nullptr),
+        cellular_(new Cellular(&modem_info_,
+                               "",
+                               kAddress,
+                               0,
+                               Cellular::kTypeGSM,
+                               "",
+                               "",
+                               "",
+                               &proxy_factory_)),
+        mock_home_provider_info_(nullptr),
+        mock_serving_operator_info_(nullptr) {
+    modem_info_.metrics()->RegisterDevice(cellular_->interface_index(),
+                                          Technology::kCellular);
+  }
+
+  virtual ~CellularCapabilityGSMTest() {
+    cellular_->service_ = nullptr;
+    capability_ = nullptr;
+    device_adaptor_ = nullptr;
+  }
+
+  virtual void SetUp() {
+    capability_ =
+        dynamic_cast<CellularCapabilityGSM *>(cellular_->capability_.get());
+    device_adaptor_ =
+        dynamic_cast<DeviceMockAdaptor *>(cellular_->adaptor());
+  }
+
+  void InvokeEnable(bool enable, Error *error,
+                    const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeGetIMEI(Error *error, const GSMIdentifierCallback &callback,
+                     int timeout) {
+    callback.Run(kIMEI, Error());
+  }
+  void InvokeGetIMSI(Error *error, const GSMIdentifierCallback &callback,
+                     int timeout) {
+    callback.Run(kIMSI, Error());
+  }
+  void InvokeGetIMSIFails(Error *error, const GSMIdentifierCallback &callback,
+                          int timeout) {
+    callback.Run("", Error(Error::kOperationFailed));
+  }
+  void InvokeGetMSISDN(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout) {
+    callback.Run(kMSISDN, Error());
+  }
+  void InvokeGetMSISDNFail(Error *error, const GSMIdentifierCallback &callback,
+                           int timeout) {
+    callback.Run("", Error(Error::kOperationFailed));
+  }
+  void InvokeGetSPN(Error *error, const GSMIdentifierCallback &callback,
+                    int timeout) {
+    callback.Run(kTestCarrier, Error());
+  }
+  void InvokeGetSPNFail(Error *error, const GSMIdentifierCallback &callback,
+                        int timeout) {
+    callback.Run("", Error(Error::kOperationFailed));
+  }
+  void InvokeGetSignalQuality(Error *error,
+                              const SignalQualityCallback &callback,
+                              int timeout) {
+    callback.Run(kStrength, Error());
+  }
+  void InvokeGetRegistrationInfo(Error *error,
+                                 const RegistrationInfoCallback &callback,
+                                 int timeout) {
+    callback.Run(MM_MODEM_GSM_NETWORK_REG_STATUS_HOME,
+                 kTestNetwork, kTestCarrier, Error());
+  }
+  void InvokeRegister(const string &network_id,
+                      Error *error,
+                      const ResultCallback &callback,
+                      int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeEnablePIN(const string &pin, bool enable,
+                       Error *error, const ResultCallback &callback,
+                       int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeSendPIN(const string &pin, Error *error,
+                     const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeSendPUK(const string &puk, const string &pin, Error *error,
+                     const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeChangePIN(const string &old_pin, const string &pin, Error *error,
+                       const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeGetModemStatus(Error *error,
+                            const DBusPropertyMapCallback &callback,
+                            int timeout) {
+    DBusPropertiesMap props;
+    callback.Run(props, Error());
+  }
+  void InvokeGetModemInfo(Error *error, const ModemInfoCallback &callback,
+                          int timeout) {
+    ModemHardwareInfo info;
+    callback.Run(info, Error());
+  }
+
+  void InvokeConnectFail(DBusPropertiesMap props, Error *error,
+                         const ResultCallback &callback, int timeout) {
+    callback.Run(Error(Error::kOperationFailed));
+  }
+
+  MOCK_METHOD1(TestCallback, void(const Error &error));
+
+ protected:
+  static const char kAddress[];
+  static const char kTestMobileProviderDBPath[];
+  static const char kTestNetwork[];
+  static const char kTestCarrier[];
+  static const char kPIN[];
+  static const char kPUK[];
+  static const char kIMEI[];
+  static const char kIMSI[];
+  static const char kMSISDN[];
+  static const int kStrength;
+
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(CellularCapabilityGSMTest *test) : test_(test) {}
+
+    virtual ModemProxyInterface *CreateModemProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->proxy_.release();
+    }
+
+    virtual ModemSimpleProxyInterface *CreateModemSimpleProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->simple_proxy_.release();
+    }
+
+    virtual ModemGSMCardProxyInterface *CreateModemGSMCardProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      // TODO(benchan): This code conditionally returns a nullptr to avoid
+      // CellularCapabilityGSM::InitProperties (and thus
+      // CellularCapabilityGSM::GetIMSI) from being called during the
+      // construction. Remove this workaround after refactoring the tests.
+      return test_->create_card_proxy_from_factory_ ?
+          test_->card_proxy_.release() : nullptr;
+    }
+
+    virtual ModemGSMNetworkProxyInterface *CreateModemGSMNetworkProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      return test_->network_proxy_.release();
+    }
+
+   private:
+    CellularCapabilityGSMTest *test_;
+  };
+
+  void SetProxy() {
+    capability_->proxy_.reset(proxy_.release());
+  }
+
+  void SetCardProxy() {
+    capability_->card_proxy_.reset(card_proxy_.release());
+  }
+
+  void SetNetworkProxy() {
+    capability_->network_proxy_.reset(network_proxy_.release());
+  }
+
+  void SetAccessTechnology(uint32_t technology) {
+    capability_->access_technology_ = technology;
+  }
+
+  void SetRegistrationState(uint32_t state) {
+    capability_->registration_state_ = state;
+  }
+
+  void CreateService() {
+    // The following constants are never directly accessed by the tests.
+    const char kStorageIdentifier[] = "default_test_storage_id";
+    const char kFriendlyServiceName[] = "default_test_service_name";
+    const char kOperatorCode[] = "10010";
+    const char kOperatorName[] = "default_test_operator_name";
+    const char kOperatorCountry[] = "us";
+
+    // Simulate all the side-effects of Cellular::CreateService
+    auto service = new CellularService(&modem_info_, cellular_);
+    service->SetStorageIdentifier(kStorageIdentifier);
+    service->SetFriendlyName(kFriendlyServiceName);
+
+    Stringmap serving_operator;
+    serving_operator[kOperatorCodeKey] = kOperatorCode;
+    serving_operator[kOperatorNameKey] = kOperatorName;
+    serving_operator[kOperatorCountryKey] = kOperatorCountry;
+
+    service->set_serving_operator(serving_operator);
+    cellular_->set_home_provider(serving_operator);
+    cellular_->service_ = service;
+  }
+
+  void SetMockMobileOperatorInfoObjects() {
+    CHECK(!mock_home_provider_info_);
+    CHECK(!mock_serving_operator_info_);
+    mock_home_provider_info_ =
+        new MockMobileOperatorInfo(&dispatcher_, "HomeProvider");
+    mock_serving_operator_info_ =
+        new MockMobileOperatorInfo(&dispatcher_, "ServingOperator");
+    cellular_->set_home_provider_info(mock_home_provider_info_);
+    cellular_->set_serving_operator_info(mock_serving_operator_info_);
+  }
+
+  void SetupCommonProxiesExpectations() {
+    EXPECT_CALL(*proxy_, set_state_changed_callback(_));
+    EXPECT_CALL(*network_proxy_, set_signal_quality_callback(_));
+    EXPECT_CALL(*network_proxy_, set_network_mode_callback(_));
+    EXPECT_CALL(*network_proxy_, set_registration_info_callback(_));
+  }
+
+  void SetupCommonStartModemExpectations() {
+    SetupCommonProxiesExpectations();
+
+    EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable))
+        .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeEnable));
+    EXPECT_CALL(*card_proxy_,
+                GetIMEI(_, _, CellularCapability::kTimeoutDefault))
+        .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetIMEI));
+    EXPECT_CALL(*card_proxy_,
+                GetIMSI(_, _, CellularCapability::kTimeoutDefault))
+        .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetIMSI));
+    EXPECT_CALL(*network_proxy_, AccessTechnology());
+    EXPECT_CALL(*card_proxy_, EnabledFacilityLocks());
+    EXPECT_CALL(*proxy_,
+                GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
+        .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetModemInfo));
+    EXPECT_CALL(*network_proxy_,
+                GetRegistrationInfo(_, _, CellularCapability::kTimeoutDefault));
+    EXPECT_CALL(*network_proxy_,
+                GetSignalQuality(_, _, CellularCapability::kTimeoutDefault));
+    EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  }
+
+  void InitProxies() {
+    AllowCreateCardProxyFromFactory();
+    capability_->InitProxies();
+  }
+
+  void AllowCreateCardProxyFromFactory() {
+    create_card_proxy_from_factory_ = true;
+  }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  bool create_card_proxy_from_factory_;
+  std::unique_ptr<MockModemProxy> proxy_;
+  std::unique_ptr<MockModemSimpleProxy> simple_proxy_;
+  std::unique_ptr<MockModemGSMCardProxy> card_proxy_;
+  std::unique_ptr<MockModemGSMNetworkProxy> network_proxy_;
+  TestProxyFactory proxy_factory_;
+  CellularCapabilityGSM *capability_;  // Owned by |cellular_|.
+  DeviceMockAdaptor *device_adaptor_;  // Owned by |cellular_|.
+  CellularRefPtr cellular_;
+
+  // Set when required and passed to |cellular_|. Owned by |cellular_|.
+  MockMobileOperatorInfo *mock_home_provider_info_;
+  MockMobileOperatorInfo *mock_serving_operator_info_;
+};
+
+const char CellularCapabilityGSMTest::kAddress[] = "1122334455";
+const char CellularCapabilityGSMTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
+const char CellularCapabilityGSMTest::kTestCarrier[] = "The Cellular Carrier";
+const char CellularCapabilityGSMTest::kTestNetwork[] = "310555";
+const char CellularCapabilityGSMTest::kPIN[] = "9876";
+const char CellularCapabilityGSMTest::kPUK[] = "8765";
+const char CellularCapabilityGSMTest::kIMEI[] = "987654321098765";
+const char CellularCapabilityGSMTest::kIMSI[] = "310150123456789";
+const char CellularCapabilityGSMTest::kMSISDN[] = "12345678901";
+const int CellularCapabilityGSMTest::kStrength = 80;
+
+TEST_F(CellularCapabilityGSMTest, PropertyStore) {
+  EXPECT_TRUE(cellular_->store().Contains(kSIMLockStatusProperty));
+}
+
+TEST_F(CellularCapabilityGSMTest, GetIMEI) {
+  EXPECT_CALL(*card_proxy_, GetIMEI(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetIMEI));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  ASSERT_TRUE(cellular_->imei().empty());
+  capability_->GetIMEI(Bind(&CellularCapabilityGSMTest::TestCallback,
+                            Unretained(this)));
+  EXPECT_EQ(kIMEI, cellular_->imei());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetIMSI) {
+  SetMockMobileOperatorInfoObjects();
+  EXPECT_CALL(*card_proxy_, GetIMSI(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetIMSI));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  ResultCallback callback = Bind(&CellularCapabilityGSMTest::TestCallback,
+                                 Unretained(this));
+  EXPECT_TRUE(cellular_->imsi().empty());
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_CALL(*mock_home_provider_info_, UpdateIMSI(kIMSI));
+  capability_->GetIMSI(callback);
+  EXPECT_EQ(kIMSI, cellular_->imsi());
+  EXPECT_TRUE(cellular_->sim_present());
+}
+
+// In this test, the call to the proxy's GetIMSI() will always indicate failure,
+// which will cause the retry logic to call the proxy again a number of times.
+// Eventually, the retries expire.
+TEST_F(CellularCapabilityGSMTest, GetIMSIFails) {
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(logging::LOG_INFO,
+                       ::testing::EndsWith("cellular_capability_gsm.cc"),
+                       ::testing::StartsWith("GetIMSI failed - ")));
+  EXPECT_CALL(*card_proxy_, GetIMSI(_, _, CellularCapability::kTimeoutDefault))
+      .Times(CellularCapabilityGSM::kGetIMSIRetryLimit + 2)
+      .WillRepeatedly(Invoke(this,
+                             &CellularCapabilityGSMTest::InvokeGetIMSIFails));
+  EXPECT_CALL(*this, TestCallback(IsFailure())).Times(2);
+  SetCardProxy();
+  ResultCallback callback = Bind(&CellularCapabilityGSMTest::TestCallback,
+                                 Unretained(this));
+  EXPECT_TRUE(cellular_->imsi().empty());
+  EXPECT_FALSE(cellular_->sim_present());
+
+  capability_->sim_lock_status_.lock_type = "sim-pin";
+  capability_->GetIMSI(callback);
+  EXPECT_TRUE(cellular_->imsi().empty());
+  EXPECT_TRUE(cellular_->sim_present());
+
+  capability_->sim_lock_status_.lock_type.clear();
+  cellular_->set_sim_present(false);
+  capability_->get_imsi_retries_ = 0;
+  EXPECT_EQ(CellularCapabilityGSM::kGetIMSIRetryDelayMilliseconds,
+            capability_->get_imsi_retry_delay_milliseconds_);
+
+  // Set the delay to zero to speed up the test.
+  capability_->get_imsi_retry_delay_milliseconds_ = 0;
+  capability_->GetIMSI(callback);
+  for (int i = 0; i < CellularCapabilityGSM::kGetIMSIRetryLimit; ++i) {
+    dispatcher_.DispatchPendingEvents();
+  }
+  EXPECT_EQ(CellularCapabilityGSM::kGetIMSIRetryLimit + 1,
+            capability_->get_imsi_retries_);
+  EXPECT_TRUE(cellular_->imsi().empty());
+  EXPECT_FALSE(cellular_->sim_present());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetMSISDN) {
+  EXPECT_CALL(*card_proxy_, GetMSISDN(_, _,
+                                      CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetMSISDN));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  ASSERT_TRUE(cellular_->mdn().empty());
+  capability_->GetMSISDN(Bind(&CellularCapabilityGSMTest::TestCallback,
+                            Unretained(this)));
+  EXPECT_EQ(kMSISDN, cellular_->mdn());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetSPN) {
+  EXPECT_CALL(*card_proxy_, GetSPN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetSPN));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  ASSERT_TRUE(capability_->spn_.empty());
+  capability_->GetSPN(Bind(&CellularCapabilityGSMTest::TestCallback,
+                            Unretained(this)));
+  EXPECT_EQ(kTestCarrier, capability_->spn_);
+}
+
+TEST_F(CellularCapabilityGSMTest, GetSignalQuality) {
+  EXPECT_CALL(*network_proxy_,
+              GetSignalQuality(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetSignalQuality));
+  SetNetworkProxy();
+  CreateService();
+  EXPECT_EQ(0, cellular_->service()->strength());
+  capability_->GetSignalQuality();
+  EXPECT_EQ(kStrength, cellular_->service()->strength());
+}
+
+TEST_F(CellularCapabilityGSMTest, RegisterOnNetwork) {
+  EXPECT_CALL(*network_proxy_, Register(kTestNetwork, _, _,
+                                        CellularCapability::kTimeoutRegister))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeRegister));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetNetworkProxy();
+  Error error;
+  capability_->RegisterOnNetwork(kTestNetwork, &error,
+                                 Bind(&CellularCapabilityGSMTest::TestCallback,
+                                      Unretained(this)));
+  EXPECT_EQ(kTestNetwork, cellular_->selected_network());
+}
+
+TEST_F(CellularCapabilityGSMTest, IsRegistered) {
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_HOME);
+  EXPECT_TRUE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN);
+  EXPECT_FALSE(capability_->IsRegistered());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING);
+  EXPECT_TRUE(capability_->IsRegistered());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetRegistrationState) {
+  ASSERT_FALSE(capability_->IsRegistered());
+  EXPECT_CALL(*network_proxy_,
+              GetRegistrationInfo(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this,
+                       &CellularCapabilityGSMTest::InvokeGetRegistrationInfo));
+  SetNetworkProxy();
+  capability_->GetRegistrationState();
+  EXPECT_TRUE(capability_->IsRegistered());
+  EXPECT_EQ(MM_MODEM_GSM_NETWORK_REG_STATUS_HOME,
+            capability_->registration_state_);
+}
+
+TEST_F(CellularCapabilityGSMTest, RequirePIN) {
+  EXPECT_CALL(*card_proxy_, EnablePIN(kPIN, true, _, _,
+                                      CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeEnablePIN));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  Error error;
+  capability_->RequirePIN(kPIN, true, &error,
+                          Bind(&CellularCapabilityGSMTest::TestCallback,
+                               Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+TEST_F(CellularCapabilityGSMTest, EnterPIN) {
+  EXPECT_CALL(*card_proxy_, SendPIN(kPIN, _, _,
+                                    CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeSendPIN));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  Error error;
+  capability_->EnterPIN(kPIN, &error,
+                        Bind(&CellularCapabilityGSMTest::TestCallback,
+                             Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+TEST_F(CellularCapabilityGSMTest, UnblockPIN) {
+  EXPECT_CALL(*card_proxy_, SendPUK(kPUK, kPIN, _, _,
+                                    CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeSendPUK));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  Error error;
+  capability_->UnblockPIN(kPUK, kPIN, &error,
+                          Bind(&CellularCapabilityGSMTest::TestCallback,
+                             Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+TEST_F(CellularCapabilityGSMTest, ChangePIN) {
+  static const char kOldPIN[] = "1111";
+  EXPECT_CALL(*card_proxy_, ChangePIN(kOldPIN, kPIN, _, _,
+                                    CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeChangePIN));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetCardProxy();
+  Error error;
+  capability_->ChangePIN(kOldPIN, kPIN, &error,
+                         Bind(&CellularCapabilityGSMTest::TestCallback,
+                             Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+
+TEST_F(CellularCapabilityGSMTest, ParseScanResult) {
+  static const char kID[] = "123";
+  static const char kLongName[] = "long name";
+  static const char kShortName[] = "short name";
+  GSMScanResult result;
+  result[CellularCapabilityGSM::kNetworkPropertyStatus] = "1";
+  result[CellularCapabilityGSM::kNetworkPropertyID] = kID;
+  result[CellularCapabilityGSM::kNetworkPropertyLongName] = kLongName;
+  result[CellularCapabilityGSM::kNetworkPropertyShortName] = kShortName;
+  result[CellularCapabilityGSM::kNetworkPropertyAccessTechnology] = "3";
+  result["unknown property"] = "random value";
+  Stringmap parsed = capability_->ParseScanResult(result);
+  EXPECT_EQ(5, parsed.size());
+  EXPECT_EQ("available", parsed[kStatusProperty]);
+  EXPECT_EQ(kID, parsed[kNetworkIdProperty]);
+  EXPECT_EQ(kLongName, parsed[kLongNameProperty]);
+  EXPECT_EQ(kShortName, parsed[kShortNameProperty]);
+  EXPECT_EQ(kNetworkTechnologyEdge, parsed[kTechnologyProperty]);
+}
+
+TEST_F(CellularCapabilityGSMTest, ParseScanResultProviderLookup) {
+  static const char kID[] = "10001";
+  const string kLongName = "TestNetworkLongName";
+  // Replace the |MobileOperatorInfo| used by |ParseScanResult| by a mock.
+  auto *mock_mobile_operator_info = new MockMobileOperatorInfo(
+      &dispatcher_,
+      "MockParseScanResult");
+  capability_->mobile_operator_info_.reset(mock_mobile_operator_info);
+
+  mock_mobile_operator_info->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_mobile_operator_info, UpdateMCCMNC(kID));
+  EXPECT_CALL(*mock_mobile_operator_info, IsMobileNetworkOperatorKnown()).
+      WillOnce(Return(true));
+  EXPECT_CALL(*mock_mobile_operator_info, operator_name()).
+      WillRepeatedly(ReturnRef(kLongName));
+  GSMScanResult result;
+  result[CellularCapabilityGSM::kNetworkPropertyID] = kID;
+  Stringmap parsed = capability_->ParseScanResult(result);
+  EXPECT_EQ(2, parsed.size());
+  EXPECT_EQ(kID, parsed[kNetworkIdProperty]);
+  EXPECT_EQ(kLongName, parsed[kLongNameProperty]);
+}
+
+TEST_F(CellularCapabilityGSMTest, SetAccessTechnology) {
+  capability_->SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GSM);
+  EXPECT_EQ(MM_MODEM_GSM_ACCESS_TECH_GSM, capability_->access_technology_);
+  CreateService();
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_HOME);
+  capability_->SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GPRS);
+  EXPECT_EQ(MM_MODEM_GSM_ACCESS_TECH_GPRS, capability_->access_technology_);
+  EXPECT_EQ(kNetworkTechnologyGprs, cellular_->service()->network_technology());
+}
+
+TEST_F(CellularCapabilityGSMTest, AllowRoaming) {
+  EXPECT_FALSE(cellular_->allow_roaming_);
+  EXPECT_FALSE(cellular_->provider_requires_roaming());
+  EXPECT_FALSE(capability_->AllowRoaming());
+  cellular_->set_provider_requires_roaming(true);
+  EXPECT_TRUE(capability_->AllowRoaming());
+  cellular_->set_provider_requires_roaming(false);
+  cellular_->allow_roaming_ = true;
+  EXPECT_TRUE(capability_->AllowRoaming());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetNetworkTechnologyString) {
+  EXPECT_EQ("", capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GSM);
+  EXPECT_EQ(kNetworkTechnologyGsm, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT);
+  EXPECT_EQ(kNetworkTechnologyGsm, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GPRS);
+  EXPECT_EQ(kNetworkTechnologyGprs, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_EDGE);
+  EXPECT_EQ(kNetworkTechnologyEdge, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_UMTS);
+  EXPECT_EQ(kNetworkTechnologyUmts, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_HSDPA);
+  EXPECT_EQ(kNetworkTechnologyHspa, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_HSUPA);
+  EXPECT_EQ(kNetworkTechnologyHspa, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_HSPA);
+  EXPECT_EQ(kNetworkTechnologyHspa, capability_->GetNetworkTechnologyString());
+  SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_HSPA_PLUS);
+  EXPECT_EQ(kNetworkTechnologyHspaPlus,
+            capability_->GetNetworkTechnologyString());
+}
+
+TEST_F(CellularCapabilityGSMTest, GetRoamingStateString) {
+  EXPECT_EQ(kRoamingStateUnknown, capability_->GetRoamingStateString());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_HOME);
+  EXPECT_EQ(kRoamingStateHome, capability_->GetRoamingStateString());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING);
+  EXPECT_EQ(kRoamingStateRoaming, capability_->GetRoamingStateString());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING);
+  EXPECT_EQ(kRoamingStateUnknown, capability_->GetRoamingStateString());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED);
+  EXPECT_EQ(kRoamingStateUnknown, capability_->GetRoamingStateString());
+  SetRegistrationState(MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE);
+  EXPECT_EQ(kRoamingStateUnknown, capability_->GetRoamingStateString());
+}
+
+MATCHER_P(KeyValueStoreEq, value, "") {
+  bool match = value.bool_properties() == arg.bool_properties() &&
+      value.int_properties() == arg.int_properties() &&
+      value.string_properties() == arg.string_properties() &&
+      value.uint_properties() == arg.uint_properties();
+  if (!match) {
+    *result_listener << "\nExpected KeyValueStore:\n"
+                     << "\tbool_properties: "
+                     << testing::PrintToString(value.bool_properties())
+                     << "\n\tint_properties: "
+                     << testing::PrintToString(value.int_properties())
+                     << "\n\tstring_properties: "
+                     << testing::PrintToString(value.string_properties())
+                     << "\n\tint_properties: "
+                     << testing::PrintToString(value.uint_properties())
+                     << "\nGot KeyValueStore:\n"
+                     << "\tbool_properties: "
+                     << testing::PrintToString(arg.bool_properties())
+                     << "\n\tint_properties: "
+                     << testing::PrintToString(arg.int_properties())
+                     << "\n\tstring_properties: "
+                     << testing::PrintToString(arg.string_properties())
+                     << "\n\tuint_properties: "
+                     << testing::PrintToString(arg.uint_properties());
+  }
+  return match;
+}
+
+TEST_F(CellularCapabilityGSMTest, OnDBusPropertiesChanged) {
+  EXPECT_EQ(MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, capability_->access_technology_);
+  EXPECT_FALSE(capability_->sim_lock_status_.enabled);
+  EXPECT_EQ("", capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(0, capability_->sim_lock_status_.retries_left);
+  DBusPropertiesMap props;
+  static const char kLockType[] = "sim-pin";
+  const int kRetries = 3;
+  props[CellularCapabilityGSM::kPropertyAccessTechnology].writer().
+      append_uint32(MM_MODEM_GSM_ACCESS_TECH_EDGE);
+  props[CellularCapabilityGSM::kPropertyEnabledFacilityLocks].writer().
+      append_uint32(MM_MODEM_GSM_FACILITY_SIM);
+  props[CellularCapabilityGSM::kPropertyUnlockRequired].writer().append_string(
+      kLockType);
+  props[CellularCapabilityGSM::kPropertyUnlockRetries].writer().append_uint32(
+      kRetries);
+  // Call with the 'wrong' interface and nothing should change.
+  capability_->OnDBusPropertiesChanged(MM_MODEM_GSM_INTERFACE, props,
+                                       vector<string>());
+  EXPECT_EQ(MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, capability_->access_technology_);
+  EXPECT_FALSE(capability_->sim_lock_status_.enabled);
+  EXPECT_EQ("", capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(0, capability_->sim_lock_status_.retries_left);
+
+  // Call with the MM_MODEM_GSM_NETWORK_INTERFACE interface and expect a change
+  // to the enabled state of the SIM lock.
+  KeyValueStore lock_status;
+  lock_status.SetBool(kSIMLockEnabledProperty, true);
+  lock_status.SetString(kSIMLockTypeProperty, "");
+  lock_status.SetUint(kSIMLockRetriesLeftProperty, 0);
+
+  EXPECT_CALL(*device_adaptor_, EmitKeyValueStoreChanged(
+      kSIMLockStatusProperty,
+      KeyValueStoreEq(lock_status)));
+
+  capability_->OnDBusPropertiesChanged(MM_MODEM_GSM_NETWORK_INTERFACE, props,
+                                       vector<string>());
+  EXPECT_EQ(MM_MODEM_GSM_ACCESS_TECH_EDGE, capability_->access_technology_);
+  capability_->OnDBusPropertiesChanged(MM_MODEM_GSM_CARD_INTERFACE, props,
+                                       vector<string>());
+  EXPECT_TRUE(capability_->sim_lock_status_.enabled);
+  EXPECT_TRUE(capability_->sim_lock_status_.lock_type.empty());
+  EXPECT_EQ(0, capability_->sim_lock_status_.retries_left);
+
+  // Some properties are sent on the MM_MODEM_INTERFACE.
+  capability_->sim_lock_status_.enabled = false;
+  capability_->sim_lock_status_.lock_type = "";
+  capability_->sim_lock_status_.retries_left = 0;
+  KeyValueStore lock_status2;
+  lock_status2.SetBool(kSIMLockEnabledProperty, false);
+  lock_status2.SetString(kSIMLockTypeProperty, kLockType);
+  lock_status2.SetUint(kSIMLockRetriesLeftProperty, kRetries);
+  EXPECT_CALL(*device_adaptor_,
+              EmitKeyValueStoreChanged(kSIMLockStatusProperty,
+                                       KeyValueStoreEq(lock_status2)));
+  capability_->OnDBusPropertiesChanged(MM_MODEM_INTERFACE, props,
+                                       vector<string>());
+  EXPECT_FALSE(capability_->sim_lock_status_.enabled);
+  EXPECT_EQ(kLockType, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(kRetries, capability_->sim_lock_status_.retries_left);
+}
+
+TEST_F(CellularCapabilityGSMTest, StartModemSuccess) {
+  SetupCommonStartModemExpectations();
+  EXPECT_CALL(*card_proxy_,
+              GetSPN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetSPN));
+  EXPECT_CALL(*card_proxy_,
+              GetMSISDN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetMSISDN));
+  AllowCreateCardProxyFromFactory();
+
+  Error error;
+  capability_->StartModem(
+      &error, Bind(&CellularCapabilityGSMTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityGSMTest, StartModemGetSPNFail) {
+  SetupCommonStartModemExpectations();
+  EXPECT_CALL(*card_proxy_,
+              GetSPN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetSPNFail));
+  EXPECT_CALL(*card_proxy_,
+              GetMSISDN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetMSISDN));
+  AllowCreateCardProxyFromFactory();
+
+  Error error;
+  capability_->StartModem(
+      &error, Bind(&CellularCapabilityGSMTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityGSMTest, StartModemGetMSISDNFail) {
+  SetupCommonStartModemExpectations();
+  EXPECT_CALL(*card_proxy_,
+              GetSPN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetSPN));
+  EXPECT_CALL(*card_proxy_,
+              GetMSISDN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeGetMSISDNFail));
+  AllowCreateCardProxyFromFactory();
+
+  Error error;
+  capability_->StartModem(
+      &error, Bind(&CellularCapabilityGSMTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityGSMTest, ConnectFailureNoService) {
+  // Make sure we don't crash if the connect failed and there is no
+  // CellularService object.  This can happen if the modem is enabled and
+  // then quickly disabled.
+  SetupCommonProxiesExpectations();
+  EXPECT_CALL(*simple_proxy_,
+              Connect(_, _, _, CellularCapabilityGSM::kTimeoutConnect))
+       .WillOnce(Invoke(this, &CellularCapabilityGSMTest::InvokeConnectFail));
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  InitProxies();
+  EXPECT_FALSE(capability_->cellular()->service());
+  Error error;
+  DBusPropertiesMap props;
+  capability_->Connect(props, &error,
+                       Bind(&CellularCapabilityGSMTest::TestCallback,
+                            Unretained(this)));
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_universal.cc b/cellular/cellular_capability_universal.cc
new file mode 100644
index 0000000..f3fb0fd
--- /dev/null
+++ b/cellular/cellular_capability_universal.cc
@@ -0,0 +1,1699 @@
+// 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/cellular_capability_universal.h"
+
+#include <base/bind.h>
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <ModemManager/ModemManager.h>
+
+#include <string>
+#include <vector>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mobile_operator_info.h"
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/pending_activation_store.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
+
+using base::Bind;
+using base::Closure;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapabilityUniversal *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+// static
+const char CellularCapabilityUniversal::kConnectPin[] = "pin";
+const char CellularCapabilityUniversal::kConnectOperatorId[] = "operator-id";
+const char CellularCapabilityUniversal::kConnectApn[] = "apn";
+const char CellularCapabilityUniversal::kConnectIPType[] = "ip-type";
+const char CellularCapabilityUniversal::kConnectUser[] = "user";
+const char CellularCapabilityUniversal::kConnectPassword[] = "password";
+const char CellularCapabilityUniversal::kConnectNumber[] = "number";
+const char CellularCapabilityUniversal::kConnectAllowRoaming[] =
+    "allow-roaming";
+const char CellularCapabilityUniversal::kConnectRMProtocol[] = "rm-protocol";
+const int64_t CellularCapabilityUniversal::kEnterPinTimeoutMilliseconds = 20000;
+const int64_t
+CellularCapabilityUniversal::kRegistrationDroppedUpdateTimeoutMilliseconds =
+    15000;
+const char CellularCapabilityUniversal::kRootPath[] = "/";
+const char CellularCapabilityUniversal::kStatusProperty[] = "status";
+const char CellularCapabilityUniversal::kOperatorLongProperty[] =
+    "operator-long";
+const char CellularCapabilityUniversal::kOperatorShortProperty[] =
+    "operator-short";
+const char CellularCapabilityUniversal::kOperatorCodeProperty[] =
+    "operator-code";
+const char CellularCapabilityUniversal::kOperatorAccessTechnologyProperty[] =
+    "access-technology";
+const char CellularCapabilityUniversal::kAltairLTEMMPlugin[] = "Altair LTE";
+const char CellularCapabilityUniversal::kNovatelLTEMMPlugin[] = "Novatel LTE";
+const int CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds =
+    20000;
+
+namespace {
+
+const char kPhoneNumber[] = "*99#";
+
+// This identifier is specified in the serviceproviders.prototxt file.
+const char kVzwIdentifier[] = "c83d6597-dc91-4d48-a3a7-d86b80123751";
+const size_t kVzwMdnLength = 10;
+
+string AccessTechnologyToString(uint32_t access_technologies) {
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_LTE)
+    return kNetworkTechnologyLte;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOB))
+    return kNetworkTechnologyEvdo;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT)
+    return kNetworkTechnology1Xrtt;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)
+    return kNetworkTechnologyHspaPlus;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_HSPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSDPA))
+    return kNetworkTechnologyHspa;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+    return kNetworkTechnologyUmts;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_EDGE)
+    return kNetworkTechnologyEdge;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
+    return kNetworkTechnologyGprs;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT |
+                              MM_MODEM_ACCESS_TECHNOLOGY_GSM))
+      return kNetworkTechnologyGsm;
+  return "";
+}
+
+string AccessTechnologyToTechnologyFamily(uint32_t access_technologies) {
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_LTE |
+                             MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS |
+                             MM_MODEM_ACCESS_TECHNOLOGY_HSPA |
+                             MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
+                             MM_MODEM_ACCESS_TECHNOLOGY_HSDPA |
+                             MM_MODEM_ACCESS_TECHNOLOGY_UMTS |
+                             MM_MODEM_ACCESS_TECHNOLOGY_EDGE |
+                             MM_MODEM_ACCESS_TECHNOLOGY_GPRS |
+                             MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT |
+                             MM_MODEM_ACCESS_TECHNOLOGY_GSM))
+    return kTechnologyFamilyGsm;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 |
+                             MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
+                             MM_MODEM_ACCESS_TECHNOLOGY_EVDOB |
+                             MM_MODEM_ACCESS_TECHNOLOGY_1XRTT))
+    return kTechnologyFamilyCdma;
+  return "";
+}
+
+}  // namespace
+
+CellularCapabilityUniversal::CellularCapabilityUniversal(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory,
+    ModemInfo *modem_info)
+    : CellularCapability(cellular, proxy_factory, modem_info),
+      mobile_operator_info_(new MobileOperatorInfo(cellular->dispatcher(),
+                                                   "ParseScanResult")),
+      weak_ptr_factory_(this),
+      registration_state_(MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN),
+      current_capabilities_(MM_MODEM_CAPABILITY_NONE),
+      access_technologies_(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN),
+      resetting_(false),
+      subscription_state_(kSubscriptionStateUnknown),
+      reset_done_(false),
+      registration_dropped_update_timeout_milliseconds_(
+          kRegistrationDroppedUpdateTimeoutMilliseconds) {
+  SLOG(this, 2) << "Cellular capability constructed: Universal";
+  mobile_operator_info_->Init();
+  HelpRegisterConstDerivedKeyValueStore(
+      kSIMLockStatusProperty,
+      &CellularCapabilityUniversal::SimLockStatusToProperty);
+}
+
+CellularCapabilityUniversal::~CellularCapabilityUniversal() {}
+
+KeyValueStore CellularCapabilityUniversal::SimLockStatusToProperty(
+    Error */*error*/) {
+  KeyValueStore status;
+  string lock_type;
+  switch (sim_lock_status_.lock_type) {
+    case MM_MODEM_LOCK_SIM_PIN:
+      lock_type = "sim-pin";
+      break;
+    case MM_MODEM_LOCK_SIM_PUK:
+      lock_type = "sim-puk";
+      break;
+    default:
+      lock_type = "";
+      break;
+  }
+  status.SetBool(kSIMLockEnabledProperty, sim_lock_status_.enabled);
+  status.SetString(kSIMLockTypeProperty, lock_type);
+  status.SetUint(kSIMLockRetriesLeftProperty, sim_lock_status_.retries_left);
+  return status;
+}
+
+void CellularCapabilityUniversal::HelpRegisterConstDerivedKeyValueStore(
+    const string &name,
+    KeyValueStore(CellularCapabilityUniversal::*get)(Error *error)) {
+  cellular()->mutable_store()->RegisterDerivedKeyValueStore(
+      name,
+      KeyValueStoreAccessor(
+          new CustomAccessor<CellularCapabilityUniversal, KeyValueStore>(
+              this, get, nullptr)));
+}
+
+void CellularCapabilityUniversal::InitProxies() {
+  modem_3gpp_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModem3gppProxy(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()));
+
+  modem_proxy_->set_state_changed_callback(
+      Bind(&CellularCapabilityUniversal::OnModemStateChangedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  // 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) {
+  SLOG(this, 3) << __func__;
+  InitProxies();
+  deferred_enable_modem_callback_.Reset();
+  EnableModem(true, error, callback);
+}
+
+void CellularCapabilityUniversal::EnableModem(bool deferrable,
+                                              Error *error,
+                                              const ResultCallback &callback) {
+  SLOG(this, 3) << __func__ << "(deferrable=" << deferrable << ")";
+  CHECK(!callback.is_null());
+  Error local_error(Error::kOperationInitiated);
+  modem_info()->metrics()->NotifyDeviceEnableStarted(
+      cellular()->interface_index());
+  modem_proxy_->Enable(
+      true,
+      &local_error,
+      Bind(&CellularCapabilityUniversal::EnableModemCompleted,
+           weak_ptr_factory_.GetWeakPtr(), deferrable, callback),
+      kTimeoutEnable);
+  if (local_error.IsFailure()) {
+    SLOG(this, 2) << __func__ << "Call to modem_proxy_->Enable() failed";
+  }
+  if (error) {
+    error->CopyFrom(local_error);
+  }
+}
+
+void CellularCapabilityUniversal::EnableModemCompleted(
+    bool deferrable, const ResultCallback &callback, const Error &error) {
+  SLOG(this, 3) << __func__ << "(deferrable=" << deferrable
+                            << ", error=" << error << ")";
+
+  // If the enable operation failed with Error::kWrongState, the modem is not
+  // in the expected state (i.e. disabled). If |deferrable| indicates that the
+  // enable operation can be deferred, we defer the operation until the modem
+  // goes into the expected state (see OnModemStateChangedSignal).
+  //
+  // Note that when the SIM is locked, the enable operation also fails with
+  // Error::kWrongState. The enable operation is deferred until the modem goes
+  // into the disabled state after the SIM is unlocked. We may choose not to
+  // defer the enable operation when the SIM is locked, but the UI needs to
+  // trigger the enable operation after the SIM is unlocked, which is currently
+  // not the case.
+  if (error.IsFailure()) {
+    if (!deferrable || error.type() != Error::kWrongState) {
+      callback.Run(error);
+      return;
+    }
+
+    if (deferred_enable_modem_callback_.is_null()) {
+      SLOG(this, 2) << "Defer enable operation.";
+      // The Enable operation to be deferred should not be further deferrable.
+      deferred_enable_modem_callback_ =
+          Bind(&CellularCapabilityUniversal::EnableModem,
+               weak_ptr_factory_.GetWeakPtr(),
+               false,  // non-deferrable
+               nullptr,
+               callback);
+    }
+    return;
+  }
+
+  // After modem is enabled, it should be possible to get properties
+  // TODO(jglasgow): handle errors from GetProperties
+  GetProperties();
+  // We expect the modem to start scanning after it has been enabled.
+  // Change this if this behavior is no longer the case in the future.
+  modem_info()->metrics()->NotifyDeviceEnableFinished(
+      cellular()->interface_index());
+  modem_info()->metrics()->NotifyDeviceScanStarted(
+      cellular()->interface_index());
+  callback.Run(error);
+}
+
+void CellularCapabilityUniversal::StopModem(Error *error,
+                                            const ResultCallback &callback) {
+  CHECK(!callback.is_null());
+  CHECK(error);
+  // If there is an outstanding registration change, simply ignore it since
+  // the service will be destroyed anyway.
+  if (!registration_dropped_update_callback_.IsCancelled()) {
+    registration_dropped_update_callback_.Cancel();
+    SLOG(this, 2) << __func__ << " Cancelled delayed deregister.";
+  }
+
+  // Some modems will implicitly disconnect the bearer when transitioning to
+  // low power state. For such modems, it's faster to let the modem disconnect
+  // the bearer. To do that, we just remove the bearer from the list so
+  // ModemManager doesn't try to disconnect it during disable.
+  Closure task;
+  if (cellular()->mm_plugin() == kAltairLTEMMPlugin) {
+    task = Bind(&CellularCapabilityUniversal::Stop_DeleteActiveBearer,
+                weak_ptr_factory_.GetWeakPtr(),
+                callback);
+  } else {
+    task = Bind(&CellularCapabilityUniversal::Stop_Disable,
+                weak_ptr_factory_.GetWeakPtr(),
+                callback);
+  }
+  cellular()->dispatcher()->PostTask(task);
+  deferred_enable_modem_callback_.Reset();
+}
+
+void CellularCapabilityUniversal::Stop_DeleteActiveBearer(
+    const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+
+  if (!active_bearer_) {
+    Stop_Disable(callback);
+    return;
+  }
+
+  Error error;
+  modem_proxy_->DeleteBearer(
+      active_bearer_->dbus_path(), &error,
+      Bind(&CellularCapabilityUniversal::Stop_DeleteActiveBearerCompleted,
+           weak_ptr_factory_.GetWeakPtr(), callback),
+      kTimeoutDefault);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::Stop_DeleteActiveBearerCompleted(
+    const ResultCallback &callback, const Error &error) {
+  SLOG(this, 3) << __func__;
+  // Disregard the error from the bearer deletion since the disable will clean
+  // up any remaining bearers.
+  Stop_Disable(callback);
+}
+
+void CellularCapabilityUniversal::Stop_Disable(const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+  Error error;
+  modem_info()->metrics()->NotifyDeviceDisableStarted(
+      cellular()->interface_index());
+  modem_proxy_->Enable(
+      false, &error,
+      Bind(&CellularCapabilityUniversal::Stop_DisableCompleted,
+           weak_ptr_factory_.GetWeakPtr(), callback),
+      kTimeoutEnable);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::Stop_DisableCompleted(
+    const ResultCallback &callback, const Error &error) {
+  SLOG(this, 3) << __func__;
+
+  if (error.IsSuccess()) {
+    // The modem has been successfully disabled, but we still need to power it
+    // down.
+    Stop_PowerDown(callback);
+  } else {
+    // An error occurred; terminate the disable sequence.
+    callback.Run(error);
+  }
+}
+
+void CellularCapabilityUniversal::Stop_PowerDown(
+    const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+  Error error;
+  modem_proxy_->SetPowerState(
+      MM_MODEM_POWER_STATE_LOW,
+      &error,
+      Bind(&CellularCapabilityUniversal::Stop_PowerDownCompleted,
+           weak_ptr_factory_.GetWeakPtr(), callback),
+      kSetPowerStateTimeoutMilliseconds);
+
+  if (error.IsFailure())
+    // This really shouldn't happen, but if it does, report success,
+    // because a stop initiated power down is only called if the
+    // modem was successfully disabled, but the failure of this
+    // operation should still be propagated up as a successful disable.
+    Stop_PowerDownCompleted(callback, error);
+}
+
+// Note: if we were in the middle of powering down the modem when the
+// system suspended, we might not get this event from
+// ModemManager. And we might not even get a timeout from dbus-c++,
+// because StartModem re-initializes proxies.
+void CellularCapabilityUniversal::Stop_PowerDownCompleted(
+    const ResultCallback &callback,
+    const Error &error) {
+  SLOG(this, 3) << __func__;
+
+  if (error.IsFailure())
+    SLOG(this, 2) << "Ignoring error returned by SetPowerState: " << error;
+
+  // Since the disable succeeded, if power down fails, we currently fail
+  // silently, i.e. we need to report the disable operation as having
+  // succeeded.
+  modem_info()->metrics()->NotifyDeviceDisableFinished(
+      cellular()->interface_index());
+  ReleaseProxies();
+  callback.Run(Error());
+}
+
+void CellularCapabilityUniversal::Connect(const DBusPropertiesMap &properties,
+                                          Error *error,
+                                          const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+  DBusPathCallback cb = Bind(&CellularCapabilityUniversal::OnConnectReply,
+                             weak_ptr_factory_.GetWeakPtr(),
+                             callback);
+  modem_simple_proxy_->Connect(properties, error, cb, kTimeoutConnect);
+}
+
+void CellularCapabilityUniversal::Disconnect(Error *error,
+                                             const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+  if (modem_simple_proxy_.get()) {
+    SLOG(this, 2) << "Disconnect all bearers.";
+    // If "/" is passed as the bearer path, ModemManager will disconnect all
+    // bearers.
+    modem_simple_proxy_->Disconnect(DBus::Path(kRootPath),
+                                    error,
+                                    callback,
+                                    kTimeoutDisconnect);
+  }
+}
+
+void CellularCapabilityUniversal::CompleteActivation(Error *error) {
+  SLOG(this, 3) << __func__;
+
+  // Persist the ICCID as "Pending Activation".
+  // We're assuming that when this function gets called,
+  // |cellular()->sim_identifier()| will be non-empty. We still check here that
+  // is non-empty, though something is wrong if it is empty.
+  const string &sim_identifier = cellular()->sim_identifier();
+  if (sim_identifier.empty()) {
+    SLOG(this, 2) << "SIM identifier not available. Nothing to do.";
+    return;
+  }
+
+  modem_info()->pending_activation_store()->SetActivationState(
+      PendingActivationStore::kIdentifierICCID,
+      sim_identifier,
+      PendingActivationStore::kStatePending);
+  UpdatePendingActivationState();
+
+  SLOG(this, 2) << "Resetting modem for activation.";
+  ResetAfterActivation();
+}
+
+void CellularCapabilityUniversal::ResetAfterActivation() {
+  SLOG(this, 3) << __func__;
+
+  // Here the initial call to Reset might fail in rare cases. Simply ignore.
+  Error error;
+  ResultCallback callback = Bind(
+      &CellularCapabilityUniversal::OnResetAfterActivationReply,
+      weak_ptr_factory_.GetWeakPtr());
+  Reset(&error, callback);
+  if (error.IsFailure())
+    SLOG(this, 2) << "Failed to reset after activation.";
+}
+
+void CellularCapabilityUniversal::OnResetAfterActivationReply(
+    const Error &error) {
+  SLOG(this, 3) << __func__;
+  if (error.IsFailure()) {
+    SLOG(this, 2) << "Failed to reset after activation. Try again later.";
+    // TODO(armansito): Maybe post a delayed reset task?
+    return;
+  }
+  reset_done_ = true;
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversal::UpdatePendingActivationState() {
+  SLOG(this, 3) << __func__;
+
+  const string &sim_identifier = cellular()->sim_identifier();
+  bool registered =
+      registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
+
+  // We know a service is activated if |subscription_state_| is
+  // kSubscriptionStateProvisioned / kSubscriptionStateOutOfData
+  // In the case that |subscription_state_| is kSubscriptionStateUnknown, we
+  // fallback on checking for a valid MDN.
+  bool activated =
+    ((subscription_state_ == kSubscriptionStateProvisioned) ||
+     (subscription_state_ == kSubscriptionStateOutOfData)) ||
+    ((subscription_state_ == kSubscriptionStateUnknown) && IsMdnValid());
+
+  if (activated && !sim_identifier.empty())
+      modem_info()->pending_activation_store()->RemoveEntry(
+          PendingActivationStore::kIdentifierICCID,
+          sim_identifier);
+
+  CellularServiceRefPtr service = cellular()->service();
+
+  if (!service.get())
+    return;
+
+  if (service->activation_state() == kActivationStateActivated)
+      // Either no service or already activated. Nothing to do.
+      return;
+
+  // If the ICCID is not available, the following logic can be delayed until it
+  // becomes available.
+  if (sim_identifier.empty())
+    return;
+
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierICCID,
+          sim_identifier);
+  switch (state) {
+    case PendingActivationStore::kStatePending:
+      // Always mark the service as activating here, as the ICCID could have
+      // been unavailable earlier.
+      service->SetActivationState(kActivationStateActivating);
+      if (reset_done_) {
+        SLOG(this, 2) << "Post-payment activation reset complete.";
+        modem_info()->pending_activation_store()->SetActivationState(
+            PendingActivationStore::kIdentifierICCID,
+            sim_identifier,
+            PendingActivationStore::kStateActivated);
+      }
+      break;
+    case PendingActivationStore::kStateActivated:
+      if (registered) {
+        // Trigger auto connect here.
+        SLOG(this, 2) << "Modem has been reset at least once, try to "
+                      << "autoconnect to force MDN to update.";
+        service->AutoConnect();
+      }
+      break;
+    case PendingActivationStore::kStateUnknown:
+      // No entry exists for this ICCID. Nothing to do.
+      break;
+    default:
+      NOTREACHED();
+  }
+}
+
+string CellularCapabilityUniversal::GetMdnForOLP(
+    const MobileOperatorInfo *operator_info) const {
+  // TODO(benchan): This is ugly. Remove carrier specific code once we move
+  // mobile activation logic to carrier-specifc extensions (crbug.com/260073).
+  const string &mdn = cellular()->mdn();
+  if (!operator_info->IsMobileNetworkOperatorKnown()) {
+    // Can't make any carrier specific modifications.
+    return mdn;
+  }
+
+  if (operator_info->uuid() == kVzwIdentifier) {
+    // subscription_state_ is the definitive indicator of whether we need
+    // activation. The OLP expects an all zero MDN in that case.
+    if (subscription_state_ == kSubscriptionStateUnprovisioned || mdn.empty()) {
+      return string(kVzwMdnLength, '0');
+    }
+    if (mdn.length() > kVzwMdnLength) {
+      return mdn.substr(mdn.length() - kVzwMdnLength);
+    }
+  }
+  return mdn;
+}
+
+void CellularCapabilityUniversal::ReleaseProxies() {
+  SLOG(this, 3) << __func__;
+  modem_3gpp_proxy_.reset();
+  modem_proxy_.reset();
+  modem_simple_proxy_.reset();
+  sim_proxy_.reset();
+}
+
+bool CellularCapabilityUniversal::AreProxiesInitialized() const {
+  return (modem_3gpp_proxy_.get() && modem_proxy_.get() &&
+          modem_simple_proxy_.get() && sim_proxy_.get());
+}
+
+void CellularCapabilityUniversal::UpdateServiceActivationState() {
+  if (!cellular()->service().get())
+    return;
+
+  const string &sim_identifier = cellular()->sim_identifier();
+  string activation_state;
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierICCID,
+          sim_identifier);
+  if ((subscription_state_ == kSubscriptionStateUnknown ||
+       subscription_state_ == kSubscriptionStateUnprovisioned) &&
+      !sim_identifier.empty() &&
+      state == PendingActivationStore::kStatePending) {
+    activation_state = kActivationStateActivating;
+  } else if (IsServiceActivationRequired()) {
+    activation_state = kActivationStateNotActivated;
+  } else {
+    activation_state = kActivationStateActivated;
+
+    // Mark an activated service for auto-connect by default. Since data from
+    // the user profile will be loaded after the call to OnServiceCreated, this
+    // property will be corrected based on the user data at that time.
+    // NOTE: This function can be called outside the service initialization
+    // path so make sure we don't overwrite the auto-connect setting.
+    if (cellular()->service()->activation_state() != activation_state)
+      cellular()->service()->SetAutoConnect(true);
+  }
+  cellular()->service()->SetActivationState(activation_state);
+}
+
+void CellularCapabilityUniversal::OnServiceCreated() {
+  cellular()->service()->SetActivationType(CellularService::kActivationTypeOTA);
+  UpdateServiceActivationState();
+
+  // WORKAROUND:
+  // E362 modems on Verizon network does not properly redirect when a SIM
+  // runs out of credits, we need to enforce out-of-credits detection.
+  //
+  // The out-of-credits detection is also needed on ALT3100 modems until the PCO
+  // support is ready (crosbug.com/p/20461).
+  cellular()->service()->InitOutOfCreditsDetection(
+      GetOutOfCreditsDetectionType());
+
+  // Make sure that the network technology is set when the service gets
+  // created, just in case.
+  cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
+}
+
+// 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(),
+                       cellular()->apn_list().begin(),
+                       cellular()->apn_list().end());
+}
+
+void CellularCapabilityUniversal::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  SetupApnTryList();
+  FillConnectPropertyMap(properties);
+}
+
+void CellularCapabilityUniversal::FillConnectPropertyMap(
+    DBusPropertiesMap *properties) {
+
+  // TODO(jglasgow): Is this really needed anymore?
+  (*properties)[kConnectNumber].writer().append_string(
+      kPhoneNumber);
+
+  (*properties)[kConnectAllowRoaming].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();
+    SLOG(this, 2) << __func__ << ": Using APN " << apn_info[kApnProperty];
+    (*properties)[kConnectApn].writer().append_string(
+        apn_info[kApnProperty].c_str());
+    if (ContainsKey(apn_info, kApnUsernameProperty))
+      (*properties)[kConnectUser].writer().append_string(
+          apn_info[kApnUsernameProperty].c_str());
+    if (ContainsKey(apn_info, kApnPasswordProperty))
+      (*properties)[kConnectPassword].writer().append_string(
+          apn_info[kApnPasswordProperty].c_str());
+  }
+}
+
+void CellularCapabilityUniversal::OnConnectReply(const ResultCallback &callback,
+                                                 const DBus::Path &path,
+                                                 const Error &error) {
+  SLOG(this, 3) << __func__ << "(" << error << ")";
+
+  CellularServiceRefPtr service = cellular()->service();
+  if (!service) {
+    // The service could have been deleted before our Connect() request
+    // completes if the modem was enabled and then quickly disabled.
+    apn_try_list_.clear();
+  } else if (error.IsFailure()) {
+    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 (RetriableConnectError(error) && !apn_try_list_.empty()) {
+      apn_try_list_.pop_front();
+      SLOG(this, 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;
+    }
+  } else {
+    if (!apn_try_list_.empty()) {
+      service->SetLastGoodApn(apn_try_list_.front());
+      apn_try_list_.clear();
+    }
+    SLOG(this, 2) << "Connected bearer " << path;
+  }
+
+  if (!callback.is_null())
+    callback.Run(error);
+
+  UpdatePendingActivationState();
+}
+
+bool CellularCapabilityUniversal::AllowRoaming() {
+  return cellular()->provider_requires_roaming() || allow_roaming_property();
+}
+
+void CellularCapabilityUniversal::GetProperties() {
+  SLOG(this, 3) << __func__;
+
+  std::unique_ptr<DBusPropertiesProxyInterface> properties_proxy(
+      proxy_factory()->CreateDBusPropertiesProxy(cellular()->dbus_path(),
+                                                 cellular()->dbus_owner()));
+  DBusPropertiesMap properties(
+      properties_proxy->GetAll(MM_DBUS_INTERFACE_MODEM));
+  OnModemPropertiesChanged(properties, vector<string>());
+
+  properties = properties_proxy->GetAll(MM_DBUS_INTERFACE_MODEM_MODEM3GPP);
+  OnModem3GPPPropertiesChanged(properties, vector<string>());
+}
+
+void CellularCapabilityUniversal::UpdateServiceOLP() {
+  SLOG(this, 3) << __func__;
+
+  // OLP is based off of the Home Provider.
+  if (!cellular()->home_provider_info()->IsMobileNetworkOperatorKnown()) {
+    return;
+  }
+
+  const vector<MobileOperatorInfo::OnlinePortal> &olp_list =
+      cellular()->home_provider_info()->olp_list();
+  if (olp_list.empty()) {
+    return;
+  }
+
+  if (olp_list.size() > 1) {
+    SLOG(this, 1) << "Found multiple online portals. Choosing the first.";
+  }
+  string post_data = olp_list[0].post_data;
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${iccid}",
+                               cellular()->sim_identifier());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${imei}", cellular()->imei());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${imsi}", cellular()->imsi());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${mdn}",
+                               GetMdnForOLP(cellular()->home_provider_info()));
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${min}", cellular()->min());
+  cellular()->service()->SetOLP(olp_list[0].url, olp_list[0].method, post_data);
+}
+
+void CellularCapabilityUniversal::UpdateActiveBearer() {
+  SLOG(this, 3) << __func__;
+
+  // Look for the first active bearer and use its path as the connected
+  // one. Right now, we don't allow more than one active bearer.
+  active_bearer_.reset();
+  for (const auto &path : bearer_paths_) {
+    std::unique_ptr<CellularBearer> bearer(
+        new CellularBearer(proxy_factory(), path, cellular()->dbus_service()));
+    // The bearer object may have vanished before ModemManager updates the
+    // 'Bearers' property.
+    if (!bearer->Init())
+      continue;
+
+    if (!bearer->connected())
+      continue;
+
+    SLOG(this, 2) << "Found active bearer \"" << path << "\".";
+    CHECK(!active_bearer_) << "Found more than one active bearer.";
+    active_bearer_ = std::move(bearer);
+  }
+
+  if (!active_bearer_)
+    SLOG(this, 2) << "No active bearer found.";
+}
+
+bool CellularCapabilityUniversal::IsServiceActivationRequired() const {
+  const string &sim_identifier = cellular()->sim_identifier();
+  // subscription_state_ is the definitive answer. If that does not work,
+  // fallback on MDN based logic.
+  if (subscription_state_ == kSubscriptionStateProvisioned ||
+      subscription_state_ == kSubscriptionStateOutOfData)
+    return false;
+
+  // We are in the process of activating, ignore all other clues from the
+  // network and use our own knowledge about the activation state.
+  if (!sim_identifier.empty() &&
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierICCID,
+          sim_identifier) != PendingActivationStore::kStateUnknown)
+    return false;
+
+  // Network notification that the service needs to be activated.
+  if (subscription_state_ == kSubscriptionStateUnprovisioned)
+    return true;
+
+  // If there is no online payment portal information, it's safer to assume
+  // the service does not require activation.
+  if (!cellular()->home_provider_info()->IsMobileNetworkOperatorKnown() ||
+      cellular()->home_provider_info()->olp_list().empty()) {
+    return false;
+  }
+
+  // If the MDN is invalid (i.e. empty or contains only zeros), the service
+  // requires activation.
+  return !IsMdnValid();
+}
+
+bool CellularCapabilityUniversal::IsMdnValid() const {
+  const string &mdn = cellular()->mdn();
+  // Note that |mdn| is normalized to contain only digits in OnMdnChanged().
+  for (size_t i = 0; i < mdn.size(); ++i) {
+    if (mdn[i] != '0')
+      return true;
+  }
+  return false;
+}
+
+// always called from an async context
+void CellularCapabilityUniversal::Register(const ResultCallback &callback) {
+  SLOG(this, 3) << __func__ << " \"" << cellular()->selected_network()
+                            << "\"";
+  CHECK(!callback.is_null());
+  Error error;
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Register(cellular()->selected_network(), &error, cb,
+                              kTimeoutRegister);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::RegisterOnNetwork(
+    const string &network_id,
+    Error *error,
+    const ResultCallback &callback) {
+  SLOG(this, 3) << __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) {
+  SLOG(this, 3) << __func__ << "(" << error << ")";
+
+  if (error.IsSuccess()) {
+    cellular()->set_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();
+    cellular()->set_selected_network("");
+    LOG(INFO) << "Couldn't register on selected network, trying home network";
+    Register(callback);
+    return;
+  }
+  callback.Run(error);
+}
+
+bool CellularCapabilityUniversal::IsRegistered() const {
+  return IsRegisteredState(registration_state_);
+}
+
+bool CellularCapabilityUniversal::IsRegisteredState(
+    MMModem3gppRegistrationState state) {
+  return (state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+          state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING);
+}
+
+void CellularCapabilityUniversal::SetUnregistered(bool searching) {
+  // If we're already in some non-registered state, don't override that
+  if (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+          registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+    registration_state_ =
+        (searching ? MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING :
+                     MM_MODEM_3GPP_REGISTRATION_STATE_IDLE);
+  }
+}
+
+void CellularCapabilityUniversal::RequirePIN(
+    const 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);
+  SLOG(this, 3) << __func__;
+  sim_proxy_->SendPin(pin, error, callback, kEnterPinTimeoutMilliseconds);
+}
+
+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::Reset(Error *error,
+                                        const ResultCallback &callback) {
+  SLOG(this, 3) << __func__;
+  CHECK(error);
+  if (resetting_) {
+    Error::PopulateAndLog(error, Error::kInProgress, "Already resetting");
+    return;
+  }
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnResetReply,
+                           weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_proxy_->Reset(error, cb, kTimeoutReset);
+  if (!error->IsFailure()) {
+    resetting_ = true;
+  }
+}
+
+void CellularCapabilityUniversal::OnResetReply(const ResultCallback &callback,
+                                               const Error &error) {
+  SLOG(this, 3) << __func__;
+  resetting_ = false;
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::Scan(
+    Error *error,
+    const ResultStringmapsCallback &callback) {
+  DBusPropertyMapsCallback cb = Bind(&CellularCapabilityUniversal::OnScanReply,
+                                     weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Scan(error, cb, kTimeoutScan);
+}
+
+void CellularCapabilityUniversal::OnScanReply(
+    const ResultStringmapsCallback &callback,
+    const ScanResults &results,
+    const Error &error) {
+  Stringmaps found_networks;
+  for (const auto &result : results)
+    found_networks.push_back(ParseScanResult(result));
+  callback.Run(found_networks, error);
+}
+
+Stringmap CellularCapabilityUniversal::ParseScanResult(
+    const ScanResult &result) {
+
+  /* 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_t 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[kStatusProperty] = kStatusString[status];
+  }
+
+  uint32_t tech;  // MMModemAccessTechnology
+  if (DBusProperties::GetUint32(result, kOperatorAccessTechnologyProperty,
+                                &tech)) {
+    parsed[kTechnologyProperty] = AccessTechnologyToString(tech);
+  }
+
+  string operator_long, operator_short, operator_code;
+  if (DBusProperties::GetString(result, kOperatorLongProperty, &operator_long))
+    parsed[kLongNameProperty] = operator_long;
+  if (DBusProperties::GetString(result, kOperatorShortProperty,
+                                &operator_short))
+    parsed[kShortNameProperty] = operator_short;
+  if (DBusProperties::GetString(result, kOperatorCodeProperty, &operator_code))
+    parsed[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, kLongNameProperty) ||
+       parsed[kLongNameProperty].empty()) &&
+      ContainsKey(parsed, kNetworkIdProperty)) {
+    mobile_operator_info_->Reset();
+    mobile_operator_info_->UpdateMCCMNC(parsed[kNetworkIdProperty]);
+    if (mobile_operator_info_->IsMobileNetworkOperatorKnown() &&
+        !mobile_operator_info_->operator_name().empty()) {
+      parsed[kLongNameProperty] = mobile_operator_info_->operator_name();
+    }
+  }
+  return parsed;
+}
+
+CellularBearer *CellularCapabilityUniversal::GetActiveBearer() const {
+  return active_bearer_.get();
+}
+
+string CellularCapabilityUniversal::GetNetworkTechnologyString() const {
+  // If we know that the modem is an E362 modem supported by the Novatel LTE
+  // plugin, return LTE here to make sure that Chrome sees LTE as the network
+  // technology even if the actual technology is unknown.
+  //
+  // This hack will cause the UI to display LTE even if the modem doesn't
+  // support it at a given time. This might be problematic if we ever want to
+  // support switching between access technologies (e.g. falling back to 3G
+  // when LTE is not available).
+  if (cellular()->mm_plugin() == kNovatelLTEMMPlugin)
+    return kNetworkTechnologyLte;
+
+  // Order is important.  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 kRoamingStateHome;
+    case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+      return kRoamingStateRoaming;
+    default:
+      break;
+  }
+  return kRoamingStateUnknown;
+}
+
+// TODO(armansito): Remove this method once cromo is deprecated.
+void CellularCapabilityUniversal::GetSignalQuality() {
+  // ModemManager always returns the cached value, so there is no need to
+  // trigger an update here. The true value is updated through a property
+  // change signal.
+}
+
+string CellularCapabilityUniversal::GetTypeString() const {
+  return AccessTechnologyToTechnologyFamily(access_technologies_);
+}
+
+void CellularCapabilityUniversal::OnModemPropertiesChanged(
+    const DBusPropertiesMap &properties,
+    const vector<string> &/* invalidated_properties */) {
+
+  // Update the bearers property before the modem state property as
+  // OnModemStateChanged may call UpdateActiveBearer, which reads the bearers
+  // property.
+  RpcIdentifiers bearers;
+  if (DBusProperties::GetRpcIdentifiers(properties, MM_MODEM_PROPERTY_BEARERS,
+                                        &bearers)) {
+    OnBearersChanged(bearers);
+  }
+
+  // This solves a bootstrapping problem: If the modem is not yet
+  // enabled, there are no proxy objects associated with the capability
+  // object, so modem signals like StateChanged aren't seen. By monitoring
+  // changes to the State property via the ModemManager, we're able to
+  // get the initialization process started, which will result in the
+  // creation of the proxy objects.
+  //
+  // The first time we see the change to State (when the modem state
+  // is Unknown), we simply update the state, and rely on the Manager to
+  // enable the device when it is registered with the Manager. On subsequent
+  // changes to State, we need to explicitly enable the device ourselves.
+  int32_t istate;
+  if (DBusProperties::GetInt32(properties, MM_MODEM_PROPERTY_STATE, &istate)) {
+    Cellular::ModemState state = static_cast<Cellular::ModemState>(istate);
+    OnModemStateChanged(state);
+  }
+  DBus::Path object_path_value;
+  if (DBusProperties::GetObjectPath(properties,
+                                    MM_MODEM_PROPERTY_SIM, &object_path_value))
+    OnSimPathChanged(object_path_value);
+
+  DBusPropertiesMap::const_iterator it =
+      properties.find(MM_MODEM_PROPERTY_SUPPORTEDCAPABILITIES);
+  if (it != properties.end()) {
+    const vector<uint32_t> &supported_capabilities = it->second;
+    OnSupportedCapabilitesChanged(supported_capabilities);
+  }
+
+  uint32_t uint_value;
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_CURRENTCAPABILITIES,
+                                &uint_value))
+    OnModemCurrentCapabilitiesChanged(uint_value);
+  // not needed: MM_MODEM_PROPERTY_MAXBEARERS
+  // not needed: MM_MODEM_PROPERTY_MAXACTIVEBEARERS
+  string string_value;
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_PROPERTY_MANUFACTURER,
+                                &string_value))
+    cellular()->set_manufacturer(string_value);
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_PROPERTY_MODEL,
+                                &string_value))
+    cellular()->set_model_id(string_value);
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_PROPERTY_PLUGIN,
+                                &string_value))
+    cellular()->set_mm_plugin(string_value);
+  if (DBusProperties::GetString(properties,
+                               MM_MODEM_PROPERTY_REVISION,
+                               &string_value))
+    OnModemRevisionChanged(string_value);
+  // not needed: MM_MODEM_PROPERTY_DEVICEIDENTIFIER
+  // not needed: MM_MODEM_PROPERTY_DEVICE
+  // not needed: MM_MODEM_PROPERTY_DRIVER
+  // not needed: MM_MODEM_PROPERTY_PLUGIN
+  // not needed: MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER
+
+  // Unlock required and SimLock
+  uint32_t unlock_required;  // This is really of type MMModemLock
+  bool lock_status_changed = false;
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_UNLOCKREQUIRED,
+                                &unlock_required)) {
+    OnLockTypeChanged(static_cast<MMModemLock>(unlock_required));
+    lock_status_changed = true;
+  }
+
+  // Unlock retries
+  it = properties.find(MM_MODEM_PROPERTY_UNLOCKRETRIES);
+  if (it != properties.end()) {
+    LockRetryData lock_retries = it->second.operator LockRetryData();
+    OnLockRetriesChanged(lock_retries);
+    lock_status_changed = true;
+  }
+
+  if (lock_status_changed)
+    OnSimLockStatusChanged();
+
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES,
+                                &uint_value))
+    OnAccessTechnologiesChanged(uint_value);
+
+  it = properties.find(MM_MODEM_PROPERTY_SIGNALQUALITY);
+  if (it != properties.end()) {
+    DBus::Struct<unsigned int, bool> quality = it->second;
+    OnSignalQualityChanged(quality._1);
+  }
+  vector<string> numbers;
+  if (DBusProperties::GetStrings(properties, MM_MODEM_PROPERTY_OWNNUMBERS,
+                                 &numbers)) {
+    string mdn;
+    if (numbers.size() > 0)
+      mdn = numbers[0];
+    OnMdnChanged(mdn);
+  }
+
+  it = properties.find(MM_MODEM_PROPERTY_SUPPORTEDMODES);
+  if (it != properties.end()) {
+    const vector<DBus::Struct<uint32_t, uint32_t>> &mm_supported_modes =
+        it->second;
+    vector<ModemModes> supported_modes;
+    for (const auto &modes : mm_supported_modes) {
+      supported_modes.push_back(
+          ModemModes(modes._1, static_cast<MMModemMode>(modes._2)));
+    }
+    OnSupportedModesChanged(supported_modes);
+  }
+
+  it = properties.find(MM_MODEM_PROPERTY_CURRENTMODES);
+  if (it != properties.end()) {
+    const DBus::Struct<uint32_t, uint32_t> &current_modes = it->second;
+    OnCurrentModesChanged(ModemModes(
+        current_modes._1, static_cast<MMModemMode>(current_modes._2)));
+  }
+
+  // au: MM_MODEM_PROPERTY_SUPPORTEDBANDS,
+  // au: MM_MODEM_PROPERTY_BANDS
+}
+
+void CellularCapabilityUniversal::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  SLOG(this, 3) << __func__ << "(" << interface << ")";
+  if (interface == MM_DBUS_INTERFACE_MODEM) {
+    OnModemPropertiesChanged(changed_properties, invalidated_properties);
+  }
+  if (interface == MM_DBUS_INTERFACE_MODEM_MODEM3GPP) {
+    OnModem3GPPPropertiesChanged(changed_properties, invalidated_properties);
+  }
+  if (interface == MM_DBUS_INTERFACE_SIM) {
+    OnSimPropertiesChanged(changed_properties, invalidated_properties);
+  }
+}
+
+bool CellularCapabilityUniversal::RetriableConnectError(
+    const Error &error) const {
+  if (error.type() == Error::kInvalidApn)
+    return true;
+
+  // ModemManager does not ever return kInvalidApn for an E362 modem (with
+  // firmware version 1.41) supported by the Novatel LTE plugin.
+  if ((cellular()->mm_plugin() == kNovatelLTEMMPlugin) &&
+      (error.type() == Error::kOperationFailed)) {
+    return true;
+  }
+  return false;
+}
+
+void CellularCapabilityUniversal::OnNetworkModeSignal(uint32_t /*mode*/) {
+  // TODO(petkov): Implement this.
+  NOTIMPLEMENTED();
+}
+
+bool CellularCapabilityUniversal::IsValidSimPath(const string &sim_path) const {
+  return !sim_path.empty() && sim_path != kRootPath;
+}
+
+string CellularCapabilityUniversal::NormalizeMdn(const string &mdn) const {
+  string normalized_mdn;
+  for (size_t i = 0; i < mdn.size(); ++i) {
+    if (IsAsciiDigit(mdn[i]))
+      normalized_mdn += mdn[i];
+  }
+  return normalized_mdn;
+}
+
+void CellularCapabilityUniversal::OnSimPathChanged(
+    const string &sim_path) {
+  if (sim_path == sim_path_)
+    return;
+
+  mm1::SimProxyInterface *proxy = nullptr;
+  if (IsValidSimPath(sim_path))
+    proxy = proxy_factory()->CreateSimProxy(sim_path,
+                                            cellular()->dbus_owner());
+  sim_path_ = sim_path;
+  sim_proxy_.reset(proxy);
+
+  if (!IsValidSimPath(sim_path)) {
+    // Clear all data about the sim
+    cellular()->set_imsi("");
+    spn_ = "";
+    cellular()->set_sim_present(false);
+    OnSimIdentifierChanged("");
+    OnOperatorIdChanged("");
+    cellular()->home_provider_info()->Reset();
+  } else {
+    cellular()->set_sim_present(true);
+    std::unique_ptr<DBusPropertiesProxyInterface> properties_proxy(
+        proxy_factory()->CreateDBusPropertiesProxy(sim_path,
+                                                   cellular()->dbus_owner()));
+    // TODO(jglasgow): convert to async interface
+    DBusPropertiesMap properties(
+        properties_proxy->GetAll(MM_DBUS_INTERFACE_SIM));
+    OnSimPropertiesChanged(properties, vector<string>());
+  }
+}
+
+void CellularCapabilityUniversal::OnSupportedCapabilitesChanged(
+    const vector<uint32_t> &supported_capabilities) {
+  supported_capabilities_ = supported_capabilities;
+}
+
+void CellularCapabilityUniversal::OnModemCurrentCapabilitiesChanged(
+    uint32_t current_capabilities) {
+  current_capabilities_ = current_capabilities;
+
+  // Only allow network scan when the modem's current capabilities support
+  // GSM/UMTS.
+  //
+  // TODO(benchan): We should consider having the modem plugins in ModemManager
+  // reporting whether network scan is supported.
+  cellular()->set_scanning_supported(
+      (current_capabilities & MM_MODEM_CAPABILITY_GSM_UMTS) != 0);
+}
+
+void CellularCapabilityUniversal::OnMdnChanged(
+    const string &mdn) {
+  cellular()->set_mdn(NormalizeMdn(mdn));
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversal::OnModemRevisionChanged(
+    const string &revision) {
+  cellular()->set_firmware_revision(revision);
+}
+
+void CellularCapabilityUniversal::OnModemStateChanged(
+    Cellular::ModemState state) {
+  SLOG(this, 3) << __func__ << ": " << Cellular::GetModemStateString(state);
+
+  if (state == Cellular::kModemStateConnected) {
+    // This assumes that ModemManager updates the Bearers list and the Bearer
+    // properties before changing Modem state to Connected.
+    SLOG(this, 2) << "Update active bearer.";
+    UpdateActiveBearer();
+  }
+
+  cellular()->OnModemStateChanged(state);
+  // TODO(armansito): Move the deferred enable logic to Cellular
+  // (See crbug.com/279499).
+  if (!deferred_enable_modem_callback_.is_null() &&
+      state == Cellular::kModemStateDisabled) {
+    SLOG(this, 2) << "Enabling modem after deferring.";
+    deferred_enable_modem_callback_.Run();
+    deferred_enable_modem_callback_.Reset();
+  }
+}
+
+void CellularCapabilityUniversal::OnAccessTechnologiesChanged(
+    uint32_t access_technologies) {
+  if (access_technologies_ != access_technologies) {
+    const string old_type_string(GetTypeString());
+    access_technologies_ = access_technologies;
+    const string new_type_string(GetTypeString());
+    if (new_type_string != old_type_string) {
+      // TODO(jglasgow): address layering violation of emitting change
+      // signal here for a property owned by Cellular.
+      cellular()->adaptor()->EmitStringChanged(
+          kTechnologyFamilyProperty, new_type_string);
+    }
+    if (cellular()->service().get()) {
+      cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
+    }
+  }
+}
+
+void CellularCapabilityUniversal::OnSupportedModesChanged(
+    const vector<ModemModes> &supported_modes) {
+  supported_modes_ = supported_modes;
+}
+
+void CellularCapabilityUniversal::OnCurrentModesChanged(
+    const ModemModes &current_modes) {
+  current_modes_ = current_modes;
+}
+
+void CellularCapabilityUniversal::OnBearersChanged(
+    const RpcIdentifiers &bearers) {
+  bearer_paths_ = bearers;
+}
+
+void CellularCapabilityUniversal::OnLockRetriesChanged(
+    const LockRetryData &lock_retries) {
+  SLOG(this, 3) << __func__;
+
+  // Look for the retries left for the current lock. Try the obtain the count
+  // that matches the current count. If no count for the current lock is
+  // available, report the first one in the dictionary.
+  LockRetryData::const_iterator it =
+      lock_retries.find(sim_lock_status_.lock_type);
+  if (it == lock_retries.end())
+      it = lock_retries.begin();
+  if (it != lock_retries.end())
+    sim_lock_status_.retries_left = it->second;
+  else
+    // Unknown, use 999
+    sim_lock_status_.retries_left = 999;
+}
+
+void CellularCapabilityUniversal::OnLockTypeChanged(
+    MMModemLock lock_type) {
+  SLOG(this, 3) << __func__ << ": " << lock_type;
+  sim_lock_status_.lock_type = lock_type;
+
+  // If the SIM is in a locked state |sim_lock_status_.enabled| might be false.
+  // This is because the corresponding property 'EnabledFacilityLocks' is on
+  // the 3GPP interface and the 3GPP interface is not available while the Modem
+  // is in the 'LOCKED' state.
+  if (lock_type != MM_MODEM_LOCK_NONE &&
+      lock_type != MM_MODEM_LOCK_UNKNOWN &&
+      !sim_lock_status_.enabled)
+    sim_lock_status_.enabled = true;
+}
+
+void CellularCapabilityUniversal::OnSimLockStatusChanged() {
+  SLOG(this, 3) << __func__;
+  cellular()->adaptor()->EmitKeyValueStoreChanged(
+      kSIMLockStatusProperty, SimLockStatusToProperty(nullptr));
+
+  // If the SIM is currently unlocked, assume that we need to refresh
+  // carrier information, since a locked SIM prevents shill from obtaining
+  // the necessary data to establish a connection later (e.g. IMSI).
+  if (IsValidSimPath(sim_path_) &&
+      (sim_lock_status_.lock_type == MM_MODEM_LOCK_NONE ||
+       sim_lock_status_.lock_type == MM_MODEM_LOCK_UNKNOWN)) {
+    std::unique_ptr<DBusPropertiesProxyInterface> properties_proxy(
+        proxy_factory()->CreateDBusPropertiesProxy(sim_path_,
+                                                   cellular()->dbus_owner()));
+    DBusPropertiesMap properties(
+        properties_proxy->GetAll(MM_DBUS_INTERFACE_SIM));
+    OnSimPropertiesChanged(properties, vector<string>());
+  }
+}
+
+void CellularCapabilityUniversal::OnModem3GPPPropertiesChanged(
+    const DBusPropertiesMap &properties,
+    const vector<string> &/* invalidated_properties */) {
+  SLOG(this, 3) << __func__;
+  uint32_t uint_value;
+  string imei;
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_IMEI,
+                                &imei))
+    cellular()->set_imei(imei);
+
+  // Handle registration state changes as a single change
+  Stringmap::const_iterator it;
+  string operator_code;
+  string operator_name;
+  it = serving_operator_.find(kOperatorCodeKey);
+  if (it != serving_operator_.end())
+    operator_code = it->second;
+  it = serving_operator_.find(kOperatorNameKey);
+  if (it != serving_operator_.end())
+    operator_name = it->second;
+
+  MMModem3gppRegistrationState state = registration_state_;
+  bool registration_changed = false;
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE,
+                                &uint_value)) {
+    state = static_cast<MMModem3gppRegistrationState>(uint_value);
+    registration_changed = true;
+  }
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE,
+                                &operator_code))
+    registration_changed = true;
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME,
+                                &operator_name))
+    registration_changed = true;
+  if (registration_changed)
+    On3GPPRegistrationChanged(state, operator_code, operator_name);
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_SUBSCRIPTIONSTATE,
+                                &uint_value))
+    On3GPPSubscriptionStateChanged(
+        static_cast<MMModem3gppSubscriptionState>(uint_value));
+
+  uint32_t subscription_state;
+  CellularServiceRefPtr service = cellular()->service();
+  if (service.get() &&
+      DBusProperties::GetUint32(properties,
+                                MM_MODEM_MODEM3GPP_PROPERTY_SUBSCRIPTIONSTATE,
+                                &subscription_state)) {
+    SLOG(this, 3) << __func__ << ": Subscription state = "
+                              << subscription_state;
+    service->out_of_credits_detector()->NotifySubscriptionStateChanged(
+        subscription_state);
+  }
+
+  uint32_t locks = 0;
+  if (DBusProperties::GetUint32(
+          properties, MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS,
+          &locks))
+    OnFacilityLocksChanged(locks);
+}
+
+void CellularCapabilityUniversal::On3GPPRegistrationChanged(
+    MMModem3gppRegistrationState state,
+    const string &operator_code,
+    const string &operator_name) {
+  SLOG(this, 3) << __func__ << ": regstate=" << state
+                            << ", opercode=" << operator_code
+                            << ", opername=" << operator_name;
+
+  // While the modem is connected, if the state changed from a registered state
+  // to a non registered state, defer the state change by 15 seconds.
+  if (cellular()->modem_state() == Cellular::kModemStateConnected &&
+      IsRegistered() && !IsRegisteredState(state)) {
+    if (!registration_dropped_update_callback_.IsCancelled()) {
+      LOG(WARNING) << "Modem reported consecutive 3GPP registration drops. "
+                   << "Ignoring earlier notifications.";
+      registration_dropped_update_callback_.Cancel();
+    } else {
+      // This is not a repeated post. So, count this instance of delayed drop
+      // posted.
+      modem_info()->metrics()->Notify3GPPRegistrationDelayedDropPosted();
+    }
+    SLOG(this, 2) << "Posted deferred registration state update";
+    registration_dropped_update_callback_.Reset(
+        Bind(&CellularCapabilityUniversal::Handle3GPPRegistrationChange,
+             weak_ptr_factory_.GetWeakPtr(),
+             state,
+             operator_code,
+             operator_name));
+    cellular()->dispatcher()->PostDelayedTask(
+        registration_dropped_update_callback_.callback(),
+        registration_dropped_update_timeout_milliseconds_);
+  } else {
+    if (!registration_dropped_update_callback_.IsCancelled()) {
+      SLOG(this, 2) << "Cancelled a deferred registration state update";
+      registration_dropped_update_callback_.Cancel();
+      // If we cancelled the callback here, it means we had flaky network for a
+      // small duration.
+      modem_info()->metrics()->Notify3GPPRegistrationDelayedDropCanceled();
+    }
+    Handle3GPPRegistrationChange(state, operator_code, operator_name);
+  }
+}
+
+void CellularCapabilityUniversal::Handle3GPPRegistrationChange(
+    MMModem3gppRegistrationState updated_state,
+    string updated_operator_code,
+    string updated_operator_name) {
+  // A finished callback does not qualify as a canceled callback.
+  // We test for a canceled callback to check for outstanding callbacks.
+  // So, explicitly cancel the callback here.
+  registration_dropped_update_callback_.Cancel();
+
+  SLOG(this, 3) << __func__ << ": regstate=" << updated_state
+                            << ", opercode=" << updated_operator_code
+                            << ", opername=" << updated_operator_name;
+
+  registration_state_ = updated_state;
+  serving_operator_[kOperatorCodeKey] = updated_operator_code;
+  serving_operator_[kOperatorNameKey] = updated_operator_name;
+  cellular()->serving_operator_info()->UpdateMCCMNC(updated_operator_code);
+  cellular()->serving_operator_info()->UpdateOperatorName(
+      updated_operator_name);
+
+  cellular()->HandleNewRegistrationState();
+
+  // If the modem registered with the network and the current ICCID is pending
+  // activation, then reset the modem.
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversal::On3GPPSubscriptionStateChanged(
+    MMModem3gppSubscriptionState updated_state) {
+  SLOG(this, 3) << __func__ << ": Updated subscription state = "
+                            << updated_state;
+
+  // A one-to-one enum mapping.
+  SubscriptionState new_subscription_state;
+  switch (updated_state) {
+    case MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN:
+      new_subscription_state = kSubscriptionStateUnknown;
+      break;
+    case MM_MODEM_3GPP_SUBSCRIPTION_STATE_PROVISIONED:
+      new_subscription_state = kSubscriptionStateProvisioned;
+      break;
+    case MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNPROVISIONED:
+      new_subscription_state = kSubscriptionStateUnprovisioned;
+      break;
+    case MM_MODEM_3GPP_SUBSCRIPTION_STATE_OUT_OF_DATA:
+      new_subscription_state = kSubscriptionStateOutOfData;
+      break;
+    default:
+      LOG(ERROR) << "Unrecognized MMModem3gppSubscriptionState: "
+                 << updated_state;
+      new_subscription_state = kSubscriptionStateUnknown;
+      return;
+  }
+  if (new_subscription_state == subscription_state_)
+    return;
+
+  subscription_state_ = new_subscription_state;
+
+  UpdateServiceActivationState();
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversal::OnModemStateChangedSignal(
+    int32_t old_state, int32_t new_state, uint32_t reason) {
+  Cellular::ModemState old_modem_state =
+      static_cast<Cellular::ModemState>(old_state);
+  Cellular::ModemState new_modem_state =
+      static_cast<Cellular::ModemState>(new_state);
+  SLOG(this, 3) << __func__ << "("
+                            << Cellular::GetModemStateString(old_modem_state)
+                            << ", "
+                            << Cellular::GetModemStateString(new_modem_state)
+                            << ", "
+                            << reason << ")";
+}
+
+void CellularCapabilityUniversal::OnSignalQualityChanged(uint32_t quality) {
+  cellular()->HandleNewSignalQuality(quality);
+}
+
+void CellularCapabilityUniversal::OnFacilityLocksChanged(uint32_t locks) {
+  bool sim_enabled = !!(locks & MM_MODEM_3GPP_FACILITY_SIM);
+  if (sim_lock_status_.enabled != sim_enabled) {
+    sim_lock_status_.enabled = sim_enabled;
+    OnSimLockStatusChanged();
+  }
+}
+
+void CellularCapabilityUniversal::OnSimPropertiesChanged(
+    const DBusPropertiesMap &props,
+    const vector<string> &/* invalidated_properties */) {
+  SLOG(this, 3) << __func__;
+  string value;
+  if (DBusProperties::GetString(props, MM_SIM_PROPERTY_SIMIDENTIFIER, &value))
+    OnSimIdentifierChanged(value);
+  if (DBusProperties::GetString(props, MM_SIM_PROPERTY_OPERATORIDENTIFIER,
+                                &value))
+    OnOperatorIdChanged(value);
+  if (DBusProperties::GetString(props, MM_SIM_PROPERTY_OPERATORNAME, &value))
+    OnSpnChanged(value);
+  if (DBusProperties::GetString(props, MM_SIM_PROPERTY_IMSI, &value)) {
+    cellular()->set_imsi(value);
+    cellular()->home_provider_info()->UpdateIMSI(value);
+    // We do not obtain IMSI OTA right now. Provide the value from the SIM to
+    // serving operator as well, to aid in MVNO identification.
+    cellular()->serving_operator_info()->UpdateIMSI(value);
+  }
+}
+
+void CellularCapabilityUniversal::OnSpnChanged(const std::string &spn) {
+  spn_ = spn;
+  cellular()->home_provider_info()->UpdateOperatorName(spn);
+}
+
+void CellularCapabilityUniversal::OnSimIdentifierChanged(const string &id) {
+  cellular()->set_sim_identifier(id);
+  cellular()->home_provider_info()->UpdateICCID(id);
+  // Provide ICCID to serving operator as well to aid in MVNO identification.
+  cellular()->serving_operator_info()->UpdateICCID(id);
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversal::OnOperatorIdChanged(
+    const string &operator_id) {
+  SLOG(this, 2) << "Operator ID = '" << operator_id << "'";
+  cellular()->home_provider_info()->UpdateMCCMNC(operator_id);
+}
+
+OutOfCreditsDetector::OOCType
+CellularCapabilityUniversal::GetOutOfCreditsDetectionType() const {
+  if (cellular()->mm_plugin() == kAltairLTEMMPlugin) {
+    return OutOfCreditsDetector::OOCTypeSubscriptionState;
+  } else {
+    return OutOfCreditsDetector::OOCTypeNone;
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_universal.h b/cellular/cellular_capability_universal.h
new file mode 100644
index 0000000..7c5a56c
--- /dev/null
+++ b/cellular/cellular_capability_universal.h
@@ -0,0 +1,438 @@
+// 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_CELLULAR_CAPABILITY_UNIVERSAL_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_UNIVERSAL_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <ModemManager/ModemManager.h>
+
+#include "shill/accessor_interface.h"
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/mm1_modem_modem3gpp_proxy_interface.h"
+#include "shill/cellular/mm1_modem_proxy_interface.h"
+#include "shill/cellular/mm1_modem_simple_proxy_interface.h"
+#include "shill/cellular/mm1_sim_proxy_interface.h"
+#include "shill/cellular/out_of_credits_detector.h"
+
+struct mobile_provider;
+
+namespace shill {
+
+class ModemInfo;
+
+// 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;
+  typedef std::map<uint32_t, uint32_t> LockRetryData;
+
+  // Constants used in connect method call.  Make available to test matchers.
+  // TODO(jglasgow): Generate from modem manager into
+  // ModemManager-names.h.
+  // See http://crbug.com/212909.
+  static const char kConnectPin[];
+  static const char kConnectOperatorId[];
+  static const char kConnectApn[];
+  static const char kConnectIPType[];
+  static const char kConnectUser[];
+  static const char kConnectPassword[];
+  static const char kConnectNumber[];
+  static const char kConnectAllowRoaming[];
+  static const char kConnectRMProtocol[];
+
+  CellularCapabilityUniversal(Cellular *cellular,
+                              ProxyFactory *proxy_factory,
+                              ModemInfo *modem_info);
+  ~CellularCapabilityUniversal() override;
+
+  // Inherited from CellularCapability.
+  std::string GetTypeString() const override;
+  void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) override;
+  // Checks the modem state.  If the state is kModemStateDisabled, then the
+  // modem is enabled.  Otherwise, the enable command is buffered until the
+  // modem becomes disabled.  ModemManager rejects the enable command if the
+  // modem is not disabled, for example, if it is initializing instead.
+  void StartModem(Error *error, const ResultCallback &callback) override;
+  void StopModem(Error *error, const ResultCallback &callback) override;
+  void Reset(Error *error, const ResultCallback &callback) override;
+  bool AreProxiesInitialized() const override;
+  bool IsServiceActivationRequired() const override;
+  void CompleteActivation(Error *error) override;
+  void Scan(Error *error, const ResultStringmapsCallback &callback) override;
+  void RegisterOnNetwork(const std::string &network_id,
+                         Error *error,
+                         const ResultCallback &callback) override;
+  bool IsRegistered() const override;
+  void SetUnregistered(bool searching) override;
+  void OnServiceCreated() override;
+  std::string GetNetworkTechnologyString() const override;
+  std::string GetRoamingStateString() const override;
+  bool AllowRoaming() override;
+  void GetSignalQuality() override;
+  void SetupConnectProperties(DBusPropertiesMap *properties) override;
+  void Connect(const DBusPropertiesMap &properties,
+               Error *error,
+               const ResultCallback &callback) override;
+  void Disconnect(Error *error, const ResultCallback &callback) override;
+  CellularBearer *GetActiveBearer() const override;
+  void RequirePIN(const std::string &pin,
+                  bool require,
+                  Error *error,
+                  const ResultCallback &callback) override;
+  void EnterPIN(const std::string &pin,
+                Error *error,
+                const ResultCallback &callback) override;
+  void UnblockPIN(const std::string &unblock_code,
+                  const std::string &pin,
+                  Error *error,
+                  const ResultCallback &callback) override;
+  void ChangePIN(const std::string &old_pin,
+                 const std::string &new_pin,
+                 Error *error,
+                 const ResultCallback &callback) override;
+
+  virtual void GetProperties();
+  virtual void Register(const ResultCallback &callback);
+
+ protected:
+  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.
+  void UpdateServiceOLP() override;
+
+  // Post-payment activation handlers.
+  virtual void UpdatePendingActivationState();
+
+  // Returns the operator-specific form of |mdn|, which is passed to the online
+  // payment portal of a cellular operator.
+  std::string GetMdnForOLP(const MobileOperatorInfo *operator_info) const;
+
+ private:
+  struct ModemModes {
+    ModemModes()
+        : allowed_modes(MM_MODEM_MODE_NONE),
+          preferred_mode(MM_MODEM_MODE_NONE) {}
+
+    ModemModes(uint32_t allowed, MMModemMode preferred)
+        : allowed_modes(allowed),
+          preferred_mode(preferred) {}
+
+    uint32_t allowed_modes;        // Bits based on MMModemMode.
+    MMModemMode preferred_mode;  // A single MMModemMode bit.
+  };
+
+  // Constants used in scan results.  Make available to unit tests.
+  // TODO(jglasgow): Generate from modem manager into ModemManager-names.h.
+  // See http://crbug.com/212909.
+  static const char kStatusProperty[];
+  static const char kOperatorLongProperty[];
+  static const char kOperatorShortProperty[];
+  static const char kOperatorCodeProperty[];
+  static const char kOperatorAccessTechnologyProperty[];
+
+  // Plugin strings via ModemManager.
+  static const char kAltairLTEMMPlugin[];
+  static const char kNovatelLTEMMPlugin[];
+
+  static const int64_t kActivationRegistrationTimeoutMilliseconds;
+  static const int64_t kEnterPinTimeoutMilliseconds;
+  static const int64_t kRegistrationDroppedUpdateTimeoutMilliseconds;
+  static const int kSetPowerStateTimeoutMilliseconds;
+
+
+  // Root path. The SIM path is reported by ModemManager to be the root path
+  // when no SIM is present.
+  static const char kRootPath[];
+
+  friend class CellularTest;
+  friend class CellularCapabilityTest;
+  friend class CellularCapabilityUniversalTest;
+  friend class CellularCapabilityUniversalCDMATest;
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, PropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, AllowRoaming);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              ActivationWaitForRegisterTimeout);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, Connect);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, ConnectApns);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, DisconnectNoProxy);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              DisconnectWithDeferredCallback);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, ExtractPcoValue);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, GetMdnForOLP);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              GetNetworkTechnologyStringOnE362);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              GetOutOfCreditsDetectionType);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, GetTypeString);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, IsMdnValid);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, IsRegistered);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, IsServiceActivationRequired);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, IsValidSimPath);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, NormalizeMdn);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, OnLockRetriesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, OnLockTypeChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              OnModemCurrentCapabilitiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, OnSimLockPropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, PropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, Reset);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, Scan);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, ScanFailure);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, SimLockStatusChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, SimLockStatusToProperty);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, SimPathChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, SimPropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StartModem);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StartModemFailure);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StartModemInWrongState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              StartModemWithDeferredEnableFailure);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StopModem);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StopModemAltair);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              StopModemAltairDeleteBearerFailure);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StopModemAltairNotConnected);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, StopModemConnected);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, TerminationAction);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              TerminationActionRemovedByStopModem);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateActiveBearer);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdatePendingActivationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateRegistrationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateRegistrationStateModemNotConnected);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdateServiceActivationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateServiceOLP);
+  FRIEND_TEST(CellularCapabilityUniversalTimerTest, CompleteActivation);
+  FRIEND_TEST(CellularTest, EnableTrafficMonitor);
+  FRIEND_TEST(CellularTest,
+              HandleNewRegistrationStateForServiceRequiringActivation);
+  FRIEND_TEST(CellularTest, ModemStateChangeLostRegistration);
+  FRIEND_TEST(CellularTest, OnPPPDied);
+
+  // SimLockStatus represents the fields in the Cellular.SIMLockStatus
+  // DBUS property of the shill device.
+  struct SimLockStatus {
+   public:
+    SimLockStatus() : enabled(false),
+                      lock_type(MM_MODEM_LOCK_UNKNOWN),
+                      retries_left(0) {}
+
+    bool enabled;
+    MMModemLock lock_type;
+    uint32_t retries_left;
+  };
+
+  // SubscriptionState represents the provisioned state of SIM. It is used
+  // currently by activation logic for LTE to determine if activation process is
+  // complete.
+  enum SubscriptionState {
+    kSubscriptionStateUnknown = 0,
+    kSubscriptionStateUnprovisioned = 1,
+    kSubscriptionStateProvisioned = 2,
+    kSubscriptionStateOutOfData = 3
+  };
+
+  // Methods used in starting a modem
+  void EnableModem(bool deferralbe,
+                   Error *error,
+                   const ResultCallback& callback);
+  void EnableModemCompleted(bool deferrable,
+                            const ResultCallback &callback,
+                            const Error &error);
+
+  // Methods used in stopping a modem
+  void Stop_DeleteActiveBearer(const ResultCallback &callback);
+  void Stop_DeleteActiveBearerCompleted(const ResultCallback &callback,
+                                        const Error &error);
+  void Stop_Disable(const ResultCallback &callback);
+  void Stop_DisableCompleted(const ResultCallback &callback,
+                             const Error &error);
+  void Stop_PowerDown(const ResultCallback &callback);
+  void Stop_PowerDownCompleted(const ResultCallback &callback,
+                               const Error &error);
+
+  // Updates |active_bearer_| to match the currently active bearer.
+  void UpdateActiveBearer();
+
+  Stringmap ParseScanResult(const ScanResult &result);
+
+  KeyValueStore SimLockStatusToProperty(Error *error);
+
+  void SetupApnTryList();
+  void FillConnectPropertyMap(DBusPropertiesMap *properties);
+
+  void HelpRegisterConstDerivedKeyValueStore(
+      const std::string &name,
+      KeyValueStore(CellularCapabilityUniversal::*get)(Error *error));
+
+  // Returns true if a connect error should be retried.  This function
+  // abstracts modem specific behavior for modems which do a lousy job
+  // of returning specific errors on connect failures.
+  bool RetriableConnectError(const Error &error) const;
+
+  // Signal callbacks
+  void OnNetworkModeSignal(uint32_t mode);
+  void OnModemStateChangedSignal(int32_t old_state,
+                                 int32_t new_state,
+                                 uint32_t reason);
+
+  // Property Change notification handlers
+  void OnModemPropertiesChanged(
+      const DBusPropertiesMap &properties,
+      const std::vector<std::string> &invalidated_properties);
+
+  void OnSignalQualityChanged(uint32_t quality);
+
+  void OnSupportedCapabilitesChanged(
+      const std::vector<uint32_t> &supported_capabilities);
+  void OnModemCurrentCapabilitiesChanged(uint32_t current_capabilities);
+  void OnMdnChanged(const std::string &mdn);
+  void OnModemRevisionChanged(const std::string &revision);
+  void OnModemStateChanged(Cellular::ModemState state);
+  void OnAccessTechnologiesChanged(uint32_t access_technologies);
+  void OnSupportedModesChanged(const std::vector<ModemModes> &supported_modes);
+  void OnCurrentModesChanged(const ModemModes &current_modes);
+  void OnBearersChanged(const RpcIdentifiers &bearers);
+  void OnLockRetriesChanged(const LockRetryData &lock_retries);
+  void OnLockTypeChanged(MMModemLock unlock_required);
+  void OnSimLockStatusChanged();
+
+  // Returns false if the MDN is empty or if the MDN consists of all 0s.
+  bool IsMdnValid() const;
+
+  // 3GPP property change handlers
+  virtual void OnModem3GPPPropertiesChanged(
+      const DBusPropertiesMap &properties,
+      const std::vector<std::string> &invalidated_properties);
+  void On3GPPRegistrationChanged(MMModem3gppRegistrationState state,
+                                 const std::string &operator_code,
+                                 const std::string &operator_name);
+  void Handle3GPPRegistrationChange(
+      MMModem3gppRegistrationState updated_state,
+      std::string updated_operator_code,
+      std::string updated_operator_name);
+  void On3GPPSubscriptionStateChanged(MMModem3gppSubscriptionState state);
+  void OnFacilityLocksChanged(uint32_t 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);
+  void OnSpnChanged(const std::string &spn);
+  void OnSimIdentifierChanged(const std::string &id);
+  void OnOperatorIdChanged(const std::string &operator_id);
+  void OnOperatorNameChanged(const std::string &operator_name);
+
+  // Method callbacks
+  void OnRegisterReply(const ResultCallback &callback,
+                       const Error &error);
+  void OnResetReply(const ResultCallback &callback, const Error &error);
+  void OnScanReply(const ResultStringmapsCallback &callback,
+                   const ScanResults &results,
+                   const Error &error);
+  void OnConnectReply(const ResultCallback &callback,
+                      const DBus::Path &bearer,
+                      const Error &error);
+
+  // Returns true, if |sim_path| constitutes a valid SIM path. Currently, a
+  // path is accepted to be valid, as long as it is not equal to one of ""
+  // and "/".
+  bool IsValidSimPath(const std::string &sim_path) const;
+
+  // Returns the normalized version of |mdn| by keeping only digits in |mdn|
+  // and removing other non-digit characters.
+  std::string NormalizeMdn(const std::string &mdn) const;
+
+  // Post-payment activation handlers.
+  void ResetAfterActivation();
+  void UpdateServiceActivationState();
+  void OnResetAfterActivationReply(const Error &error);
+
+  static bool IsRegisteredState(MMModem3gppRegistrationState state);
+
+  // Returns the out-of-credits detection algorithm to be used on this modem.
+  OutOfCreditsDetector::OOCType GetOutOfCreditsDetectionType() const;
+
+  // For unit tests.
+  void set_active_bearer(CellularBearer *bearer) {
+    active_bearer_.reset(bearer);  // Takes ownership
+  }
+
+  std::unique_ptr<mm1::ModemModem3gppProxyInterface> modem_3gpp_proxy_;
+  std::unique_ptr<mm1::ModemProxyInterface> modem_proxy_;
+  std::unique_ptr<mm1::ModemSimpleProxyInterface> modem_simple_proxy_;
+  std::unique_ptr<mm1::SimProxyInterface> sim_proxy_;
+  // Used to enrich information about the network operator in |ParseScanResult|.
+  // TODO(pprabhu) Instead instantiate a local |MobileOperatorInfo| instance
+  // once the context has been separated out. (crbug.com/363874)
+  std::unique_ptr<MobileOperatorInfo> mobile_operator_info_;
+
+  base::WeakPtrFactory<CellularCapabilityUniversal> weak_ptr_factory_;
+
+  MMModem3gppRegistrationState registration_state_;
+
+  // Bits based on MMModemCapabilities
+  std::vector<uint32_t> supported_capabilities_;  // Technologies supported
+  uint32_t current_capabilities_;  // Technologies supported without a reload
+  uint32_t access_technologies_;   // Bits based on MMModemAccessTechnology
+  std::vector<ModemModes> supported_modes_;
+  ModemModes current_modes_;
+
+  Stringmap serving_operator_;
+  std::string spn_;
+  std::string desired_network_;
+
+  // Properties.
+  std::deque<Stringmap> apn_try_list_;
+  bool resetting_;
+  SimLockStatus sim_lock_status_;
+  SubscriptionState subscription_state_;
+  std::string sim_path_;
+  std::unique_ptr<CellularBearer> active_bearer_;
+  RpcIdentifiers bearer_paths_;
+  bool reset_done_;
+
+  // If the modem is not in a state to be enabled when StartModem is called,
+  // enabling is deferred using this callback.
+  base::Closure deferred_enable_modem_callback_;
+
+  // Sometimes flaky cellular network causes the 3GPP registration state to
+  // rapidly change from registered --> searching and back. Delay such updates
+  // a little to smooth over temporary registration loss.
+  base::CancelableClosure registration_dropped_update_callback_;
+  int64_t registration_dropped_update_timeout_milliseconds_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityUniversal);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_UNIVERSAL_H_
diff --git a/cellular/cellular_capability_universal_cdma.cc b/cellular/cellular_capability_universal_cdma.cc
new file mode 100644
index 0000000..147c842
--- /dev/null
+++ b/cellular/cellular_capability_universal_cdma.cc
@@ -0,0 +1,534 @@
+// 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/cellular_capability_universal_cdma.h"
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/pending_activation_store.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 Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularCapabilityUniversalCDMA *c) {
+  return c->cellular()->GetRpcIdentifier();
+}
+}
+
+namespace {
+
+const char kPhoneNumber[] = "#777";
+const char kPropertyConnectNumber[] = "number";
+
+}  // namespace
+
+CellularCapabilityUniversalCDMA::CellularCapabilityUniversalCDMA(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory,
+    ModemInfo *modem_info)
+    : CellularCapabilityUniversal(cellular, proxy_factory, modem_info),
+      weak_cdma_ptr_factory_(this),
+      activation_state_(MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED),
+      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(this, 2) << "Cellular capability constructed: Universal CDMA";
+  // TODO(armansito): Update PRL for activation over cellular.
+  // See crbug.com/197330.
+}
+
+CellularCapabilityUniversalCDMA::~CellularCapabilityUniversalCDMA() {}
+
+void CellularCapabilityUniversalCDMA::InitProxies() {
+  SLOG(this, 2) << __func__;
+  modem_cdma_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModemCdmaProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  modem_cdma_proxy_->set_activation_state_callback(
+      Bind(&CellularCapabilityUniversalCDMA::OnActivationStateChangedSignal,
+      weak_cdma_ptr_factory_.GetWeakPtr()));
+  CellularCapabilityUniversal::InitProxies();
+}
+
+void CellularCapabilityUniversalCDMA::ReleaseProxies() {
+  SLOG(this, 2) << __func__;
+  modem_cdma_proxy_.reset();
+  CellularCapabilityUniversal::ReleaseProxies();
+}
+
+void CellularCapabilityUniversalCDMA::Activate(const string &carrier,
+                                               Error *error,
+                                               const ResultCallback &callback) {
+  // Currently activation over the cellular network is not supported using
+  // ModemManager-next. Service activation is currently carried through over
+  // non-cellular networks and only the final step of the OTA activation
+  // procedure ("automatic activation") is performed by this class.
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversalCDMA::CompleteActivation(Error *error) {
+  SLOG(this, 2) << __func__;
+  if (cellular()->state() < Cellular::kStateEnabled) {
+    Error::PopulateAndLog(error, Error::kInvalidArguments,
+                          "Unable to activate in state " +
+                          Cellular::GetStateString(cellular()->state()));
+    return;
+  }
+  ActivateAutomatic();
+}
+
+void CellularCapabilityUniversalCDMA::ActivateAutomatic() {
+  if (!cellular()->serving_operator_info()->IsMobileNetworkOperatorKnown() ||
+      cellular()->serving_operator_info()->activation_code().empty()) {
+    SLOG(this, 2) << "OTA activation cannot be run in the presence of no "
+                  << "activation code.";
+    return;
+  }
+
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierMEID, cellular()->meid());
+  if (state == PendingActivationStore::kStatePending) {
+    SLOG(this, 2) << "There's already a pending activation. Ignoring.";
+    return;
+  }
+  if (state == PendingActivationStore::kStateActivated) {
+    SLOG(this, 2) << "A call to OTA activation has already completed "
+                  << "successfully. Ignoring.";
+    return;
+  }
+
+  // Mark as pending activation, so that shill can recover if anything fails
+  // during OTA activation.
+  modem_info()->pending_activation_store()->SetActivationState(
+      PendingActivationStore::kIdentifierMEID,
+      cellular()->meid(),
+      PendingActivationStore::kStatePending);
+
+  // Initiate OTA activation.
+  ResultCallback activation_callback =
+    Bind(&CellularCapabilityUniversalCDMA::OnActivateReply,
+         weak_cdma_ptr_factory_.GetWeakPtr(),
+         ResultCallback());
+
+  Error error;
+  modem_cdma_proxy_->Activate(
+      cellular()->serving_operator_info()->activation_code(),
+      &error,
+      activation_callback,
+      kTimeoutActivate);
+}
+
+void CellularCapabilityUniversalCDMA::UpdatePendingActivationState() {
+  SLOG(this, 2) << __func__;
+  if (IsActivated()) {
+    SLOG(this, 3) << "CDMA service activated. Clear store.";
+    modem_info()->pending_activation_store()->RemoveEntry(
+        PendingActivationStore::kIdentifierMEID, cellular()->meid());
+    return;
+  }
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierMEID, cellular()->meid());
+  if (IsActivating() && state != PendingActivationStore::kStateFailureRetry) {
+    SLOG(this, 3) << "OTA activation in progress. Nothing to do.";
+    return;
+  }
+  switch (state) {
+    case PendingActivationStore::kStateFailureRetry:
+      SLOG(this, 3) << "OTA activation failed. Scheduling a retry.";
+      cellular()->dispatcher()->PostTask(
+          Bind(&CellularCapabilityUniversalCDMA::ActivateAutomatic,
+               weak_cdma_ptr_factory_.GetWeakPtr()));
+      break;
+    case PendingActivationStore::kStateActivated:
+      SLOG(this, 3) << "OTA Activation has completed successfully. "
+                    << "Waiting for activation state update to finalize.";
+      break;
+    default:
+      break;
+  }
+}
+
+bool CellularCapabilityUniversalCDMA::IsServiceActivationRequired() const {
+  // If there is no online payment portal information, it's safer to assume
+  // the service does not require activation.
+  if (!cellular()->serving_operator_info()->IsMobileNetworkOperatorKnown() ||
+      cellular()->serving_operator_info()->olp_list().empty()) {
+    return false;
+  }
+
+  // We could also use the MDN to determine whether or not the service is
+  // activated, however, the CDMA ActivatonState property is a more absolute
+  // and fine-grained indicator of activation status.
+  return (activation_state_ == MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED);
+}
+
+bool CellularCapabilityUniversalCDMA::IsActivated() const {
+  return (activation_state_ == MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED);
+}
+
+void CellularCapabilityUniversalCDMA::OnServiceCreated() {
+  SLOG(this, 2) << __func__;
+  cellular()->service()->SetActivationType(
+      CellularService::kActivationTypeOTASP);
+  UpdateServiceActivationStateProperty();
+  HandleNewActivationStatus(MM_CDMA_ACTIVATION_ERROR_NONE);
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversalCDMA::UpdateServiceActivationStateProperty() {
+  string activation_state;
+  if (IsActivating())
+      activation_state = kActivationStateActivating;
+  else if (IsServiceActivationRequired())
+      activation_state = kActivationStateNotActivated;
+  else
+      activation_state = kActivationStateActivated;
+  cellular()->service()->SetActivationState(activation_state);
+}
+
+void CellularCapabilityUniversalCDMA::UpdateServiceOLP() {
+  SLOG(this, 2) << __func__;
+
+  // In this case, the Home Provider is trivial. All information comes from the
+  // Serving Operator.
+  if (!cellular()->serving_operator_info()->IsMobileNetworkOperatorKnown()) {
+    return;
+  }
+
+  const vector<MobileOperatorInfo::OnlinePortal> &olp_list =
+      cellular()->serving_operator_info()->olp_list();
+  if (olp_list.empty()) {
+    return;
+  }
+
+  if (olp_list.size() > 1) {
+    SLOG(this, 1) << "Found multiple online portals. Choosing the first.";
+  }
+  string post_data = olp_list[0].post_data;
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${esn}", cellular()->esn());
+  ReplaceSubstringsAfterOffset(
+      &post_data, 0, "${mdn}",
+      GetMdnForOLP(cellular()->serving_operator_info()));
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${meid}", cellular()->meid());
+  ReplaceSubstringsAfterOffset(&post_data, 0, "${oem}", "GOG2");
+  cellular()->service()->SetOLP(olp_list[0].url, olp_list[0].method, post_data);
+}
+
+void CellularCapabilityUniversalCDMA::GetProperties() {
+  SLOG(this, 2) << __func__;
+  CellularCapabilityUniversal::GetProperties();
+
+  std::unique_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>());
+}
+
+void CellularCapabilityUniversalCDMA::OnActivationStateChangedSignal(
+    uint32_t activation_state,
+    uint32_t activation_error,
+    const DBusPropertiesMap &status_changes) {
+  SLOG(this, 2) << __func__;
+
+  activation_state_ =
+      static_cast<MMModemCdmaActivationState>(activation_state);
+
+  string value;
+  if (DBusProperties::GetString(status_changes, "mdn", &value))
+    cellular()->set_mdn(value);
+  if (DBusProperties::GetString(status_changes, "min", &value))
+    cellular()->set_min(value);
+
+  SLOG(this, 2) << "Activation state: "
+                << GetActivationStateString(activation_state_);
+
+  HandleNewActivationStatus(activation_error);
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversalCDMA::OnActivateReply(
+    const ResultCallback &callback,
+    const Error &error) {
+  SLOG(this, 2) << __func__;
+  if (error.IsSuccess()) {
+    LOG(INFO) << "Activation completed successfully.";
+    modem_info()->pending_activation_store()->SetActivationState(
+        PendingActivationStore::kIdentifierMEID,
+        cellular()->meid(),
+        PendingActivationStore::kStateActivated);
+  } else {
+    LOG(ERROR) << "Activation failed with error: " << error;
+    modem_info()->pending_activation_store()->SetActivationState(
+        PendingActivationStore::kIdentifierMEID,
+        cellular()->meid(),
+        PendingActivationStore::kStateFailureRetry);
+  }
+  UpdatePendingActivationState();
+
+  // CellularCapabilityUniversalCDMA::ActivateAutomatic passes a dummy
+  // ResultCallback when it calls Activate on the proxy object, in which case
+  // |callback.is_null()| will return true.
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversalCDMA::HandleNewActivationStatus(
+    uint32_t error) {
+  SLOG(this, 2) << __func__ << "(" << error << ")";
+  if (!cellular()->service().get()) {
+    LOG(ERROR) << "In " << __func__ << "(): service is null.";
+    return;
+  }
+  SLOG(this, 2) << "Activation State: " << activation_state_;
+  cellular()->service()->SetActivationState(
+      GetActivationStateString(activation_state_));
+  cellular()->service()->set_error(GetActivationErrorString(error));
+  UpdateServiceOLP();
+}
+
+// static
+string CellularCapabilityUniversalCDMA::GetActivationStateString(
+    uint32_t state) {
+  switch (state) {
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED:
+      return kActivationStateActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING:
+      return kActivationStateActivating;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED:
+      return kActivationStateNotActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
+      return kActivationStatePartiallyActivated;
+    default:
+      return kActivationStateUnknown;
+  }
+}
+
+// static
+string CellularCapabilityUniversalCDMA::GetActivationErrorString(
+    uint32_t error) {
+  switch (error) {
+    case MM_CDMA_ACTIVATION_ERROR_WRONG_RADIO_INTERFACE:
+      return kErrorNeedEvdo;
+    case MM_CDMA_ACTIVATION_ERROR_ROAMING:
+      return kErrorNeedHomeNetwork;
+    case MM_CDMA_ACTIVATION_ERROR_COULD_NOT_CONNECT:
+    case MM_CDMA_ACTIVATION_ERROR_SECURITY_AUTHENTICATION_FAILED:
+    case MM_CDMA_ACTIVATION_ERROR_PROVISIONING_FAILED:
+      return kErrorOtaspFailed;
+    case MM_CDMA_ACTIVATION_ERROR_NONE:
+      return "";
+    case MM_CDMA_ACTIVATION_ERROR_NO_SIGNAL:
+    default:
+      return kErrorActivationFailed;
+  }
+}
+
+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::IsActivating() const {
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierMEID, cellular()->meid());
+  return (state == PendingActivationStore::kStatePending) ||
+      (state == PendingActivationStore::kStateFailureRetry) ||
+      (activation_state_ == MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING);
+}
+
+bool CellularCapabilityUniversalCDMA::IsRegistered() const {
+  return (cdma_1x_registration_state_ !=
+              MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
+          cdma_evdo_registration_state_ !=
+              MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+}
+
+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::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  (*properties)[kPropertyConnectNumber].writer().append_string(
+      kPhoneNumber);
+}
+
+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 ResultStringmapsCallback &callback) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversalCDMA::OnSimPathChanged(
+    const string &sim_path) {
+  // TODO(armansito): Remove once 3GPP is implemented in its own class.
+}
+
+string CellularCapabilityUniversalCDMA::GetRoamingStateString() const {
+  uint32_t 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 kRoamingStateHome;
+    case MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING:
+      return kRoamingStateRoaming;
+    default:
+      NOTREACHED();
+  }
+  return kRoamingStateUnknown;
+}
+
+void CellularCapabilityUniversalCDMA::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  SLOG(this, 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(this, 2) << __func__;
+  string str_value;
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEMCDMA_PROPERTY_MEID,
+                                &str_value))
+    cellular()->set_meid(str_value);
+  if (DBusProperties::GetString(properties,
+                                MM_MODEM_MODEMCDMA_PROPERTY_ESN,
+                                &str_value))
+    cellular()->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 (DBusProperties::GetUint32(
+      properties,
+      MM_MODEM_MODEMCDMA_PROPERTY_ACTIVATIONSTATE,
+      &uint_value)) {
+    activation_state_ = static_cast<MMModemCdmaActivationState>(uint_value);
+    HandleNewActivationStatus(MM_CDMA_ACTIVATION_ERROR_NONE);
+  }
+  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(this, 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;
+  cellular()->serving_operator_info()->UpdateSID(UintToString(sid));
+  cellular()->serving_operator_info()->UpdateNID(UintToString(nid));
+  cellular()->HandleNewRegistrationState();
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_universal_cdma.h b/cellular/cellular_capability_universal_cdma.h
new file mode 100644
index 0000000..8300a75
--- /dev/null
+++ b/cellular/cellular_capability_universal_cdma.h
@@ -0,0 +1,133 @@
+// 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_CELLULAR_CAPABILITY_UNIVERSAL_CDMA_H_
+#define SHILL_CELLULAR_CELLULAR_CAPABILITY_UNIVERSAL_CDMA_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_capability_universal.h"
+#include "shill/cellular/mm1_modem_modemcdma_proxy_interface.h"
+
+namespace shill {
+
+class CellularCapabilityUniversalCDMA : public CellularCapabilityUniversal {
+ public:
+  CellularCapabilityUniversalCDMA(Cellular *cellular,
+                                  ProxyFactory *proxy_factory,
+                                  ModemInfo *modem_info);
+  ~CellularCapabilityUniversalCDMA() override;
+
+  // Returns true if the service is activated.
+  bool IsActivated() const;
+
+  // Inherited from CellularCapability.
+  void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties) override;
+  bool IsServiceActivationRequired() const override;
+  bool IsActivating() const override;
+  void Activate(const std::string &carrier,
+                Error *error,
+                const ResultCallback &callback) override;
+  void CompleteActivation(Error *error) override;
+  bool IsRegistered() const override;
+  void SetUnregistered(bool searching) override;
+  void OnServiceCreated() override;
+  std::string GetRoamingStateString() const override;
+  void SetupConnectProperties(DBusPropertiesMap *properties) override;
+
+  // TODO(armansito): Remove once 3GPP is implemented in its own class
+  void Register(const ResultCallback &callback) override;
+  void RegisterOnNetwork(const std::string &network_id, Error *error,
+                         const ResultCallback &callback) override;
+  void RequirePIN(const std::string &pin, bool require, Error *error,
+                  const ResultCallback &callback) override;
+  void EnterPIN(const std::string &pin, Error *error,
+                const ResultCallback &callback) override;
+  void UnblockPIN(const std::string &unblock_code,
+                  const std::string &pin, Error *error,
+                  const ResultCallback &callback) override;
+  void ChangePIN(const std::string &old_pin, const std::string &new_pin,
+                 Error *error, const ResultCallback &callback) override;
+  void Scan(Error *error,
+            const ResultStringmapsCallback &callback) override;
+  void OnSimPathChanged(const std::string &sim_path) override;
+
+  void GetProperties() override;
+
+ protected:
+  // Inherited from CellularCapabilityUniversal.
+  void InitProxies() override;
+  void ReleaseProxies() override;
+  void UpdateServiceOLP() override;
+
+  // Post-payment activation handlers.
+  void UpdatePendingActivationState() override;
+
+ private:
+  friend class CellularCapabilityUniversalCDMATest;
+  FRIEND_TEST(CellularCapabilityUniversalCDMADispatcherTest,
+              UpdatePendingActivationState);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, ActivateAutomatic);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, IsActivating);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, IsRegistered);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest,
+              IsServiceActivationRequired);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest,
+              OnCDMARegistrationChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, PropertiesChanged);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest, UpdateServiceOLP);
+  FRIEND_TEST(CellularCapabilityUniversalCDMAMainTest,
+              UpdateServiceActivationStateProperty);
+
+  // 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);
+
+  // CDMA activation handlers
+  void ActivateAutomatic();
+  void OnActivationStateChangedSignal(uint32_t activation_state,
+                                      uint32_t activation_error,
+                                      const DBusPropertiesMap &status_changes);
+  void OnActivateReply(const ResultCallback &callback,
+                       const Error &error);
+  void HandleNewActivationStatus(uint32_t error);
+
+  void UpdateServiceActivationStateProperty();
+
+  static std::string GetActivationStateString(uint32_t state);
+  static std::string GetActivationErrorString(uint32_t error);
+
+  std::unique_ptr<mm1::ModemModemCdmaProxyInterface> modem_cdma_proxy_;
+  // TODO(armansito): Should probably call this |weak_ptr_factory_| after
+  // 3gpp refactor
+  base::WeakPtrFactory<CellularCapabilityUniversalCDMA> weak_cdma_ptr_factory_;
+
+  // CDMA ActivationState property.
+  MMModemCdmaActivationState activation_state_;
+
+  MMModemCdmaRegistrationState cdma_1x_registration_state_;
+  MMModemCdmaRegistrationState cdma_evdo_registration_state_;
+
+  uint32_t nid_;
+  uint32_t sid_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularCapabilityUniversalCDMA);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_CAPABILITY_UNIVERSAL_CDMA_H_
diff --git a/cellular/cellular_capability_universal_cdma_unittest.cc b/cellular/cellular_capability_universal_cdma_unittest.cc
new file mode 100644
index 0000000..02ae2db
--- /dev/null
+++ b/cellular/cellular_capability_universal_cdma_unittest.cc
@@ -0,0 +1,633 @@
+// 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/cellular_capability_universal_cdma.h"
+
+#include <string>
+#include <vector>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_cellular_service.h"
+#include "shill/cellular/mock_mm1_modem_modem3gpp_proxy.h"
+#include "shill/cellular/mock_mm1_modem_modemcdma_proxy.h"
+#include "shill/cellular/mock_mm1_modem_proxy.h"
+#include "shill/cellular/mock_mm1_modem_simple_proxy.h"
+#include "shill/cellular/mock_mm1_sim_proxy.h"
+#include "shill/cellular/mock_mobile_operator_info.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.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_pending_activation_store.h"
+#include "shill/nice_mock_control.h"
+#include "shill/proxy_factory.h"
+
+using base::StringPrintf;
+using base::UintToString;
+using std::string;
+using std::unique_ptr;
+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:
+  explicit CellularCapabilityUniversalCDMATest(EventDispatcher *dispatcher)
+      : dispatcher_(dispatcher),
+        capability_(nullptr),
+        device_adaptor_(nullptr),
+        modem_info_(nullptr, dispatcher, nullptr, nullptr, nullptr),
+        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_)),
+        mock_home_provider_info_(nullptr),
+        mock_serving_operator_info_(nullptr) {}
+
+  virtual ~CellularCapabilityUniversalCDMATest() {
+    cellular_->service_ = nullptr;
+    capability_ = nullptr;
+    device_adaptor_ = nullptr;
+  }
+
+  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_ = nullptr;
+  }
+
+  void SetService() {
+    cellular_->service_ = new CellularService(&modem_info_, cellular_);
+  }
+
+  void ClearService() {
+    cellular_->service_ = nullptr;
+  }
+
+  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());
+  }
+
+  void SetMockMobileOperatorInfoObjects() {
+    CHECK(!mock_home_provider_info_);
+    CHECK(!mock_serving_operator_info_);
+    mock_home_provider_info_ =
+        new MockMobileOperatorInfo(dispatcher_, "HomeProvider");
+    mock_serving_operator_info_ =
+        new MockMobileOperatorInfo(dispatcher_, "ServingOperator");
+    cellular_->set_home_provider_info(mock_home_provider_info_);
+    cellular_->set_serving_operator_info(mock_serving_operator_info_);
+  }
+
+ 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::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_;
+  };
+
+  EventDispatcher *dispatcher_;
+  CellularCapabilityUniversalCDMA *capability_;
+  NiceMock<DeviceMockAdaptor> *device_adaptor_;
+  MockModemInfo modem_info_;
+  MockGLib glib_;
+  // TODO(armansito): Remove |modem_3gpp_proxy_| after refactor.
+  unique_ptr<mm1::MockModemModem3gppProxy> modem_3gpp_proxy_;
+  unique_ptr<mm1::MockModemModemCdmaProxy> modem_cdma_proxy_;
+  unique_ptr<mm1::MockModemProxy> modem_proxy_;
+  unique_ptr<mm1::MockModemSimpleProxy> modem_simple_proxy_;
+  unique_ptr<mm1::MockSimProxy> sim_proxy_;
+  unique_ptr<MockDBusPropertiesProxy> properties_proxy_;
+  TestProxyFactory proxy_factory_;
+  CellularRefPtr cellular_;
+  MockCellularService *service_;
+
+  // Set when required and passed to |cellular_|. Owned by |cellular_|.
+  MockMobileOperatorInfo *mock_home_provider_info_;
+  MockMobileOperatorInfo *mock_serving_operator_info_;
+};
+
+// static
+const char CellularCapabilityUniversalCDMATest::kEsn[] = "0000";
+// static
+const char CellularCapabilityUniversalCDMATest::kMachineAddress[] =
+    "TestMachineAddress";
+// static
+const char CellularCapabilityUniversalCDMATest::kMeid[] = "11111111111111";
+
+class CellularCapabilityUniversalCDMAMainTest
+    : public CellularCapabilityUniversalCDMATest {
+ public:
+  CellularCapabilityUniversalCDMAMainTest()
+      : CellularCapabilityUniversalCDMATest(&dispatcher_) {}
+
+ private:
+  EventDispatcher dispatcher_;
+};
+
+class CellularCapabilityUniversalCDMADispatcherTest
+    : public CellularCapabilityUniversalCDMATest {
+ public:
+  CellularCapabilityUniversalCDMADispatcherTest()
+      : CellularCapabilityUniversalCDMATest(nullptr) {}
+};
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, 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(cellular_->meid().empty());
+  EXPECT_TRUE(cellular_->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(cellular_->meid().empty());
+  EXPECT_TRUE(cellular_->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, cellular_->meid());
+  EXPECT_EQ(kEsn, cellular_->esn());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, 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_);
+
+  const unsigned kSid = 2;
+  const unsigned kNid = 1;
+  SetMockMobileOperatorInfoObjects();
+  EXPECT_CALL(*mock_serving_operator_info_, UpdateSID(UintToString(kSid)));
+  EXPECT_CALL(*mock_serving_operator_info_, UpdateNID(UintToString(kNid)));
+  capability_->OnCDMARegistrationChanged(
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+      kSid,
+      kNid);
+  EXPECT_EQ(kSid, capability_->sid_);
+  EXPECT_EQ(kNid, 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());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, UpdateServiceOLP) {
+  const MobileOperatorInfo::OnlinePortal kOlp {
+      "http://testurl",
+      "POST",
+      "esn=${esn}&mdn=${mdn}&meid=${meid}"};
+  const vector<MobileOperatorInfo::OnlinePortal> kOlpList {kOlp};
+  const string kUuidVzw = "c83d6597-dc91-4d48-a3a7-d86b80123751";
+  const string kUuidFoo = "foo";
+
+  SetMockMobileOperatorInfoObjects();
+  cellular_->set_esn("0");
+  cellular_->set_mdn("10123456789");
+  cellular_->set_meid("4");
+
+
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, olp_list())
+      .WillRepeatedly(ReturnRef(kOlpList));
+  EXPECT_CALL(*mock_serving_operator_info_, uuid())
+      .WillOnce(ReturnRef(kUuidVzw));
+  SetService();
+  capability_->UpdateServiceOLP();
+  // Copy to simplify assertions below.
+  Stringmap vzw_olp = cellular_->service()->olp();
+  EXPECT_EQ("http://testurl", vzw_olp[kPaymentPortalURL]);
+  EXPECT_EQ("POST", vzw_olp[kPaymentPortalMethod]);
+  EXPECT_EQ("esn=0&mdn=0123456789&meid=4",
+            vzw_olp[kPaymentPortalPostData]);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, olp_list())
+      .WillRepeatedly(ReturnRef(kOlpList));
+  EXPECT_CALL(*mock_serving_operator_info_, uuid())
+      .WillOnce(ReturnRef(kUuidFoo));
+  capability_->UpdateServiceOLP();
+  // Copy to simplify assertions below.
+  Stringmap olp = cellular_->service()->olp();
+  EXPECT_EQ("http://testurl", olp[kPaymentPortalURL]);
+  EXPECT_EQ("POST", olp[kPaymentPortalMethod]);
+  EXPECT_EQ("esn=0&mdn=10123456789&meid=4",
+            olp[kPaymentPortalPostData]);
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, ActivateAutomatic) {
+  const string activation_code {"1234"};
+  SetMockMobileOperatorInfoObjects();
+
+  mm1::MockModemModemCdmaProxy *cdma_proxy = modem_cdma_proxy_.get();
+  SetUp();
+  capability_->InitProxies();
+
+  // Cases when activation fails because |activation_code| is not available.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*cdma_proxy, Activate(_, _, _, _)).Times(0);
+  capability_->ActivateAutomatic();
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  Mock::VerifyAndClearExpectations(modem_cdma_proxy_.get());
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*cdma_proxy, Activate(_, _, _, _)).Times(0);
+  capability_->ActivateAutomatic();
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  Mock::VerifyAndClearExpectations(modem_cdma_proxy_.get());
+
+  // These expectations hold for all subsequent tests.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, activation_code())
+      .WillRepeatedly(ReturnRef(activation_code));
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierMEID, _))
+      .WillOnce(Return(PendingActivationStore::kStatePending))
+      .WillOnce(Return(PendingActivationStore::kStateActivated));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              SetActivationState(_, _, _))
+      .Times(0);
+  EXPECT_CALL(*cdma_proxy, Activate(_, _, _, _)).Times(0);
+  capability_->ActivateAutomatic();
+  capability_->ActivateAutomatic();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_cdma_proxy_.get());
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierMEID, _))
+      .WillOnce(Return(PendingActivationStore::kStateUnknown))
+      .WillOnce(Return(PendingActivationStore::kStateFailureRetry));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              SetActivationState(_, _, PendingActivationStore::kStatePending))
+      .Times(2);
+  EXPECT_CALL(*cdma_proxy, Activate(_, _, _, _)).Times(2);
+  capability_->ActivateAutomatic();
+  capability_->ActivateAutomatic();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_cdma_proxy_.get());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, IsServiceActivationRequired) {
+  const vector<MobileOperatorInfo::OnlinePortal> empty_list;
+  const vector<MobileOperatorInfo::OnlinePortal> olp_list {
+    {"some@url", "some_method", "some_post_data"}
+  };
+  SetMockMobileOperatorInfoObjects();
+
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, olp_list())
+      .WillRepeatedly(ReturnRef(empty_list));
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+
+  // These expectations hold for all subsequent tests.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, olp_list())
+      .WillRepeatedly(ReturnRef(olp_list));
+
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_TRUE(capability_->IsServiceActivationRequired());
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED;
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest,
+       UpdateServiceActivationStateProperty) {
+  const vector<MobileOperatorInfo::OnlinePortal> olp_list {
+    {"some@url", "some_method", "some_post_data"}
+  };
+  SetMockMobileOperatorInfoObjects();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, olp_list())
+      .WillRepeatedly(ReturnRef(olp_list));
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .WillOnce(Return(PendingActivationStore::kStatePending))
+      .WillRepeatedly(Return(PendingActivationStore::kStateUnknown));
+
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivating))
+      .Times(1);
+  capability_->UpdateServiceActivationStateProperty();
+  Mock::VerifyAndClearExpectations(service_);
+
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateNotActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationStateProperty();
+  Mock::VerifyAndClearExpectations(service_);
+
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivating))
+      .Times(1);
+  capability_->UpdateServiceActivationStateProperty();
+  Mock::VerifyAndClearExpectations(service_);
+
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED;
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationStateProperty();
+  Mock::VerifyAndClearExpectations(service_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, IsActivating) {
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .WillOnce(Return(PendingActivationStore::kStatePending))
+      .WillOnce(Return(PendingActivationStore::kStatePending))
+      .WillOnce(Return(PendingActivationStore::kStateFailureRetry))
+      .WillRepeatedly(Return(PendingActivationStore::kStateUnknown));
+
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_TRUE(capability_->IsActivating());
+  EXPECT_TRUE(capability_->IsActivating());
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  EXPECT_TRUE(capability_->IsActivating());
+  EXPECT_TRUE(capability_->IsActivating());
+  capability_->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_FALSE(capability_->IsActivating());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, IsRegistered) {
+  capability_->cdma_1x_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  EXPECT_FALSE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_1x_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_1x_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_1x_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->cdma_evdo_registration_state_ =
+      MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+  EXPECT_TRUE(capability_->IsRegistered());
+}
+
+TEST_F(CellularCapabilityUniversalCDMAMainTest, SetupConnectProperties) {
+  DBusPropertiesMap map;
+  capability_->SetupConnectProperties(&map);
+  EXPECT_EQ(1, map.size());
+  EXPECT_STREQ("#777", map["number"].reader().get_string());
+}
+
+TEST_F(CellularCapabilityUniversalCDMADispatcherTest,
+       UpdatePendingActivationState) {
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED;
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(), RemoveEntry(_, _))
+      .Times(1);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .Times(0);
+  EXPECT_CALL(*modem_info_.mock_dispatcher(), PostTask(_)).Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_dispatcher());
+
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(), RemoveEntry(_, _))
+      .Times(0);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .Times(2)
+      .WillRepeatedly(Return(PendingActivationStore::kStateUnknown));
+  EXPECT_CALL(*modem_info_.mock_dispatcher(), PostTask(_)).Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_dispatcher());
+
+  capability_->activation_state_ = MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(), RemoveEntry(_, _))
+      .Times(0);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .Times(2)
+      .WillRepeatedly(Return(PendingActivationStore::kStatePending));
+  EXPECT_CALL(*modem_info_.mock_dispatcher(), PostTask(_)).Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_dispatcher());
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(), RemoveEntry(_, _))
+      .Times(0);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .Times(2)
+      .WillRepeatedly(Return(PendingActivationStore::kStateFailureRetry));
+  EXPECT_CALL(*modem_info_.mock_dispatcher(), PostTask(_)).Times(1);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_dispatcher());
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(), RemoveEntry(_, _))
+      .Times(0);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _))
+      .Times(4)
+      .WillOnce(Return(PendingActivationStore::kStateActivated))
+      .WillOnce(Return(PendingActivationStore::kStateActivated))
+      .WillOnce(Return(PendingActivationStore::kStateUnknown))
+      .WillOnce(Return(PendingActivationStore::kStateUnknown));
+  EXPECT_CALL(*modem_info_.mock_dispatcher(), PostTask(_)).Times(0);
+  capability_->UpdatePendingActivationState();
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_dispatcher());
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_capability_universal_unittest.cc b/cellular/cellular_capability_universal_unittest.cc
new file mode 100644
index 0000000..4566799
--- /dev/null
+++ b/cellular/cellular_capability_universal_unittest.cc
@@ -0,0 +1,2025 @@
+// 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/cellular_capability_universal.h"
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_cellular_service.h"
+#include "shill/cellular/mock_mm1_modem_modem3gpp_proxy.h"
+#include "shill/cellular/mock_mm1_modem_modemcdma_proxy.h"
+#include "shill/cellular/mock_mm1_modem_proxy.h"
+#include "shill/cellular/mock_mm1_modem_simple_proxy.h"
+#include "shill/cellular/mock_mm1_sim_proxy.h"
+#include "shill/cellular/mock_mobile_operator_info.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/dbus_adaptor.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_event_dispatcher.h"
+#include "shill/mock_pending_activation_store.h"
+#include "shill/mock_profile.h"
+#include "shill/net/mock_rtnl_handler.h"
+#include "shill/proxy_factory.h"
+#include "shill/testing.h"
+
+using base::Bind;
+using base::StringPrintf;
+using base::Unretained;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::_;
+
+namespace shill {
+
+MATCHER_P(HasApn, expected_apn, "") {
+  string apn;
+  return (DBusProperties::GetString(arg,
+                                    CellularCapabilityUniversal::kConnectApn,
+                                    &apn) &&
+          apn == expected_apn);
+}
+
+class CellularCapabilityUniversalTest : public testing::TestWithParam<string> {
+ public:
+  explicit CellularCapabilityUniversalTest(EventDispatcher *dispatcher)
+      : dispatcher_(dispatcher),
+        modem_info_(nullptr, dispatcher, nullptr, nullptr, nullptr),
+        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),
+        capability_(nullptr),
+        device_adaptor_(nullptr),
+        cellular_(new Cellular(&modem_info_,
+                               "",
+                               "00:01:02:03:04:05",
+                               0,
+                               Cellular::kTypeUniversal,
+                               "",
+                               "",
+                               "",
+                               &proxy_factory_)),
+        service_(new MockCellularService(&modem_info_, cellular_)),
+        mock_home_provider_info_(nullptr),
+        mock_serving_operator_info_(nullptr) {
+    modem_info_.metrics()->RegisterDevice(cellular_->interface_index(),
+                                          Technology::kCellular);
+  }
+
+  virtual ~CellularCapabilityUniversalTest() {
+    cellular_->service_ = nullptr;
+    capability_ = nullptr;
+    device_adaptor_ = nullptr;
+  }
+
+  virtual void SetUp() {
+    capability_ = dynamic_cast<CellularCapabilityUniversal *>(
+        cellular_->capability_.get());
+    device_adaptor_ =
+        dynamic_cast<DeviceMockAdaptor *>(cellular_->adaptor());
+    cellular_->service_ = service_;
+
+    // kStateUnknown leads to minimal extra work in maintaining
+    // activation state.
+    ON_CALL(*modem_info_.mock_pending_activation_store(),
+            GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+        .WillByDefault(Return(PendingActivationStore::kStateUnknown));
+
+    SetMockMobileOperatorInfoObjects();
+  }
+
+  virtual void TearDown() {
+    capability_->proxy_factory_ = nullptr;
+  }
+
+  void CreateService() {
+    // The following constants are never directly accessed by the tests.
+    const char kStorageIdentifier[] = "default_test_storage_id";
+    const char kFriendlyServiceName[] = "default_test_service_name";
+    const char kOperatorCode[] = "10010";
+    const char kOperatorName[] = "default_test_operator_name";
+    const char kOperatorCountry[] = "us";
+
+    // Simulate all the side-effects of Cellular::CreateService
+    auto service = new CellularService(&modem_info_, cellular_);
+    service->SetStorageIdentifier(kStorageIdentifier);
+    service->SetFriendlyName(kFriendlyServiceName);
+
+    Stringmap serving_operator;
+    serving_operator[kOperatorCodeKey] = kOperatorCode;
+    serving_operator[kOperatorNameKey] = kOperatorName;
+    serving_operator[kOperatorCountryKey] = kOperatorCountry;
+    service->set_serving_operator(serving_operator);
+    cellular_->set_home_provider(serving_operator);
+    cellular_->service_ = service;
+  }
+
+  void ClearService() {
+    cellular_->service_ = nullptr;
+  }
+
+  void ExpectModemAndModem3gppProperties() {
+    // Set up mock modem properties.
+    DBusPropertiesMap modem_properties;
+    string operator_name = "TestOperator";
+    string operator_code = "001400";
+
+    modem_properties[MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES].
+        writer().append_uint32(kAccessTechnologies);
+
+    DBus::Variant variant;
+    DBus::MessageIter writer = variant.writer();
+    DBus::Struct<uint32_t, bool> signal_signal;
+    signal_signal._1 = 90;
+    signal_signal._2 = true;
+    writer << signal_signal;
+    modem_properties[MM_MODEM_PROPERTY_SIGNALQUALITY] = variant;
+
+    // Set up mock modem 3gpp properties.
+    DBusPropertiesMap modem3gpp_properties;
+    modem3gpp_properties[MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS].
+        writer().append_uint32(0);
+    modem3gpp_properties[MM_MODEM_MODEM3GPP_PROPERTY_IMEI].
+        writer().append_string(kImei);
+
+    EXPECT_CALL(*properties_proxy_,
+                GetAll(MM_DBUS_INTERFACE_MODEM))
+        .WillOnce(Return(modem_properties));
+    EXPECT_CALL(*properties_proxy_,
+                GetAll(MM_DBUS_INTERFACE_MODEM_MODEM3GPP))
+        .WillOnce(Return(modem3gpp_properties));
+  }
+
+  void InvokeEnable(bool enable, Error *error,
+                    const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeEnableFail(bool enable, Error *error,
+                        const ResultCallback &callback, int timeout) {
+    callback.Run(Error(Error::kOperationFailed));
+  }
+  void InvokeEnableInWrongState(bool enable, Error *error,
+                                const ResultCallback &callback, int timeout) {
+    callback.Run(Error(Error::kWrongState));
+  }
+  void InvokeRegister(const string &operator_id, Error *error,
+                      const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeSetPowerState(const uint32_t &power_state,
+                           Error *error,
+                           const ResultCallback &callback,
+                           int timeout) {
+    callback.Run(Error());
+  }
+  void Set3gppProxy() {
+    capability_->modem_3gpp_proxy_.reset(modem_3gpp_proxy_.release());
+  }
+
+  void SetSimpleProxy() {
+    capability_->modem_simple_proxy_.reset(modem_simple_proxy_.release());
+  }
+
+  void SetMockMobileOperatorInfoObjects() {
+    CHECK(!mock_home_provider_info_);
+    CHECK(!mock_serving_operator_info_);
+    mock_home_provider_info_ =
+        new MockMobileOperatorInfo(dispatcher_, "HomeProvider");
+    mock_serving_operator_info_ =
+        new MockMobileOperatorInfo(dispatcher_, "ServingOperator");
+    cellular_->set_home_provider_info(mock_home_provider_info_);
+    cellular_->set_serving_operator_info(mock_serving_operator_info_);
+  }
+
+  void ReleaseCapabilityProxies() {
+    capability_->ReleaseProxies();
+  }
+
+  void SetRegistrationDroppedUpdateTimeout(int64_t timeout_milliseconds) {
+    capability_->registration_dropped_update_timeout_milliseconds_ =
+        timeout_milliseconds;
+  }
+
+  MOCK_METHOD1(TestCallback, void(const Error &error));
+
+  MOCK_METHOD0(DummyCallback, void(void));
+
+  void SetMockRegistrationDroppedUpdateCallback() {
+    capability_->registration_dropped_update_callback_.Reset(
+        Bind(&CellularCapabilityUniversalTest::DummyCallback,
+             Unretained(this)));
+  }
+
+ protected:
+  static const char kActiveBearerPathPrefix[];
+  static const char kImei[];
+  static const char kInactiveBearerPathPrefix[];
+  static const char kSimPath[];
+  static const uint32_t kAccessTechnologies;
+  static const char kTestMobileProviderDBPath[];
+
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(CellularCapabilityUniversalTest *test) :
+        test_(test) {
+      active_bearer_properties_[MM_BEARER_PROPERTY_CONNECTED].writer()
+          .append_bool(true);
+      active_bearer_properties_[MM_BEARER_PROPERTY_INTERFACE].writer()
+          .append_string("/dev/fake");
+
+      DBusPropertiesMap ip4config;
+      ip4config["method"].writer().append_uint32(MM_BEARER_IP_METHOD_DHCP);
+      DBus::MessageIter writer =
+          active_bearer_properties_[MM_BEARER_PROPERTY_IP4CONFIG].writer();
+      writer << ip4config;
+
+      inactive_bearer_properties_[MM_BEARER_PROPERTY_CONNECTED].writer()
+          .append_bool(false);
+    }
+
+    DBusPropertiesMap *mutable_active_bearer_properties() {
+      return &active_bearer_properties_;
+    }
+
+    DBusPropertiesMap *mutable_inactive_bearer_properties() {
+      return &inactive_bearer_properties_;
+    }
+
+    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*/) {
+      mm1::MockSimProxy *sim_proxy = test_->sim_proxy_.release();
+      test_->sim_proxy_.reset(new mm1::MockSimProxy());
+      return sim_proxy;
+    }
+
+    virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
+        const std::string &path,
+        const std::string &/*service*/) {
+      MockDBusPropertiesProxy *properties_proxy =
+          test_->properties_proxy_.release();
+      if (path.find(kActiveBearerPathPrefix) != std::string::npos) {
+        EXPECT_CALL(*properties_proxy, GetAll(MM_DBUS_INTERFACE_BEARER))
+            .Times(AnyNumber())
+            .WillRepeatedly(Return(active_bearer_properties_));
+      } else {
+        EXPECT_CALL(*properties_proxy, GetAll(MM_DBUS_INTERFACE_BEARER))
+            .Times(AnyNumber())
+            .WillRepeatedly(Return(inactive_bearer_properties_));
+      }
+      test_->properties_proxy_.reset(new MockDBusPropertiesProxy());
+      return properties_proxy;
+    }
+
+   private:
+    CellularCapabilityUniversalTest *test_;
+    DBusPropertiesMap active_bearer_properties_;
+    DBusPropertiesMap inactive_bearer_properties_;
+  };
+
+  EventDispatcher *dispatcher_;
+  MockModemInfo modem_info_;
+  unique_ptr<mm1::MockModemModem3gppProxy> modem_3gpp_proxy_;
+  unique_ptr<mm1::MockModemModemCdmaProxy> modem_cdma_proxy_;
+  unique_ptr<mm1::MockModemProxy> modem_proxy_;
+  unique_ptr<mm1::MockModemSimpleProxy> modem_simple_proxy_;
+  unique_ptr<mm1::MockSimProxy> sim_proxy_;
+  unique_ptr<MockDBusPropertiesProxy> properties_proxy_;
+  TestProxyFactory proxy_factory_;
+  CellularCapabilityUniversal *capability_;  // Owned by |cellular_|.
+  DeviceMockAdaptor *device_adaptor_;  // Owned by |cellular_|.
+  CellularRefPtr cellular_;
+  MockCellularService *service_;  // owned by cellular_
+  DBusPathCallback connect_callback_;  // saved for testing connect operations
+
+  // Set when required and passed to |cellular_|. Owned by |cellular_|.
+  MockMobileOperatorInfo *mock_home_provider_info_;
+  MockMobileOperatorInfo *mock_serving_operator_info_;
+};
+
+// Most of our tests involve using a real EventDispatcher object.
+class CellularCapabilityUniversalMainTest
+    : public CellularCapabilityUniversalTest {
+ public:
+  CellularCapabilityUniversalMainTest() :
+      CellularCapabilityUniversalTest(&dispatcher_) {}
+
+ protected:
+  EventDispatcher dispatcher_;
+};
+
+// Tests that involve timers will (or may) use a mock of the event dispatcher
+// instead of a real one.
+class CellularCapabilityUniversalTimerTest
+    : public CellularCapabilityUniversalTest {
+ public:
+  CellularCapabilityUniversalTimerTest()
+      : CellularCapabilityUniversalTest(&mock_dispatcher_) {}
+
+ protected:
+  ::testing::StrictMock<MockEventDispatcher> mock_dispatcher_;
+};
+
+const char CellularCapabilityUniversalTest::kActiveBearerPathPrefix[] =
+    "/bearer/active";
+const char CellularCapabilityUniversalTest::kImei[] = "999911110000";
+const char CellularCapabilityUniversalTest::kInactiveBearerPathPrefix[] =
+    "/bearer/inactive";
+const char CellularCapabilityUniversalTest::kSimPath[] = "/foo/sim";
+const uint32_t CellularCapabilityUniversalTest::kAccessTechnologies =
+    MM_MODEM_ACCESS_TECHNOLOGY_LTE |
+    MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
+const char CellularCapabilityUniversalTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
+
+TEST_F(CellularCapabilityUniversalMainTest, StartModem) {
+  ExpectModemAndModem3gppProperties();
+
+  EXPECT_CALL(*modem_proxy_,
+              Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(
+           this, &CellularCapabilityUniversalTest::InvokeEnable));
+
+  Error error;
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StartModem(&error, callback);
+
+  EXPECT_TRUE(error.IsOngoing());
+  EXPECT_EQ(kImei, cellular_->imei());
+  EXPECT_EQ(kAccessTechnologies, capability_->access_technologies_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, StartModemFailure) {
+  EXPECT_CALL(*modem_proxy_,
+              Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(
+           this, &CellularCapabilityUniversalTest::InvokeEnableFail));
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_MODEM)).Times(0);
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_MODEM_MODEM3GPP))
+      .Times(0);
+
+  Error error;
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StartModem(&error, callback);
+  EXPECT_TRUE(error.IsOngoing());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, StartModemInWrongState) {
+  ExpectModemAndModem3gppProperties();
+
+  EXPECT_CALL(*modem_proxy_,
+              Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(
+           this, &CellularCapabilityUniversalTest::InvokeEnableInWrongState))
+      .WillOnce(Invoke(
+           this, &CellularCapabilityUniversalTest::InvokeEnable));
+
+  Error error;
+  EXPECT_CALL(*this, TestCallback(_)).Times(0);
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StartModem(&error, callback);
+  EXPECT_TRUE(error.IsOngoing());
+
+  // Verify that the modem has not been enabled.
+  EXPECT_TRUE(cellular_->imei().empty());
+  EXPECT_EQ(0, capability_->access_technologies_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Change the state to kModemStateEnabling and verify that it still has not
+  // been enabled.
+  capability_->OnModemStateChanged(Cellular::kModemStateEnabling);
+  EXPECT_TRUE(cellular_->imei().empty());
+  EXPECT_EQ(0, capability_->access_technologies_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Change the state to kModemStateDisabling and verify that it still has not
+  // been enabled.
+  EXPECT_CALL(*this, TestCallback(_)).Times(0);
+  capability_->OnModemStateChanged(Cellular::kModemStateDisabling);
+  EXPECT_TRUE(cellular_->imei().empty());
+  EXPECT_EQ(0, capability_->access_technologies_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Change the state of the modem to disabled and verify that it gets enabled.
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  capability_->OnModemStateChanged(Cellular::kModemStateDisabled);
+  EXPECT_EQ(kImei, cellular_->imei());
+  EXPECT_EQ(kAccessTechnologies, capability_->access_technologies_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest,
+       StartModemWithDeferredEnableFailure) {
+  EXPECT_CALL(*modem_proxy_,
+              Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .Times(2)
+      .WillRepeatedly(Invoke(
+           this, &CellularCapabilityUniversalTest::InvokeEnableInWrongState));
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_MODEM)).Times(0);
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_MODEM_MODEM3GPP))
+      .Times(0);
+
+  Error error;
+  EXPECT_CALL(*this, TestCallback(_)).Times(0);
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StartModem(&error, callback);
+  EXPECT_TRUE(error.IsOngoing());
+  Mock::VerifyAndClearExpectations(this);
+
+  // Change the state of the modem to disabled but fail the deferred enable
+  // operation with the WrongState error in order to verify that the deferred
+  // enable operation does not trigger another deferred enable operation.
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  capability_->OnModemStateChanged(Cellular::kModemStateDisabled);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, StopModem) {
+  // Save pointers to proxies before they are lost by the call to InitProxies
+  mm1::MockModemProxy *modem_proxy = modem_proxy_.get();
+  EXPECT_CALL(*modem_proxy, set_state_changed_callback(_));
+  capability_->InitProxies();
+
+  Error error;
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StopModem(&error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+
+  ResultCallback disable_callback;
+  EXPECT_CALL(*modem_proxy,
+              Enable(false, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(SaveArg<2>(&disable_callback));
+  dispatcher_.DispatchPendingEvents();
+
+  ResultCallback set_power_state_callback;
+  EXPECT_CALL(
+      *modem_proxy,
+      SetPowerState(
+          MM_MODEM_POWER_STATE_LOW, _, _,
+          CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+      .WillOnce(SaveArg<2>(&set_power_state_callback));
+  disable_callback.Run(Error(Error::kSuccess));
+
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kSuccess));
+  Mock::VerifyAndClearExpectations(this);
+
+  // TestCallback should get called with success even if the power state
+  // callback gets called with an error
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kOperationFailed));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, StopModemAltair) {
+  // Save pointers to proxies before they are lost by the call to InitProxies
+  mm1::MockModemProxy *modem_proxy = modem_proxy_.get();
+  EXPECT_CALL(*modem_proxy, set_state_changed_callback(_));
+  capability_->InitProxies();
+
+  const char kBearerDBusPath[] = "/bearer/dbus/path";
+  capability_->set_active_bearer(
+      new CellularBearer(&proxy_factory_,
+                         kBearerDBusPath,
+                         cellular_->dbus_service()));  // Passes ownership.
+
+  cellular_->set_mm_plugin(CellularCapabilityUniversal::kAltairLTEMMPlugin);
+
+  Error error;
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StopModem(&error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+
+  ResultCallback delete_bearer_callback;
+  EXPECT_CALL(*modem_proxy,
+              DeleteBearer(::DBus::Path(kBearerDBusPath), _, _,
+                           CellularCapability::kTimeoutDefault))
+      .WillOnce(SaveArg<2>(&delete_bearer_callback));
+  dispatcher_.DispatchPendingEvents();
+
+  ResultCallback disable_callback;
+  EXPECT_CALL(*modem_proxy,
+              Enable(false, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(SaveArg<2>(&disable_callback));
+  delete_bearer_callback.Run(Error(Error::kSuccess));
+
+  ResultCallback set_power_state_callback;
+  EXPECT_CALL(
+      *modem_proxy,
+      SetPowerState(
+          MM_MODEM_POWER_STATE_LOW, _, _,
+          CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+      .WillOnce(SaveArg<2>(&set_power_state_callback));
+  disable_callback.Run(Error(Error::kSuccess));
+
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kSuccess));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest,
+       StopModemAltairDeleteBearerFailure) {
+  // Save pointers to proxies before they are lost by the call to InitProxies
+  mm1::MockModemProxy *modem_proxy = modem_proxy_.get();
+  EXPECT_CALL(*modem_proxy, set_state_changed_callback(_));
+  capability_->InitProxies();
+
+  const char kBearerDBusPath[] = "/bearer/dbus/path";
+  capability_->set_active_bearer(
+      new CellularBearer(&proxy_factory_,
+                         kBearerDBusPath,
+                         cellular_->dbus_service()));  // Passes ownership.
+
+  cellular_->set_mm_plugin(CellularCapabilityUniversal::kAltairLTEMMPlugin);
+
+  Error error;
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StopModem(&error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+
+  ResultCallback delete_bearer_callback;
+  EXPECT_CALL(*modem_proxy,
+              DeleteBearer(::DBus::Path(kBearerDBusPath), _, _,
+                           CellularCapability::kTimeoutDefault))
+      .WillOnce(SaveArg<2>(&delete_bearer_callback));
+  dispatcher_.DispatchPendingEvents();
+
+  ResultCallback disable_callback;
+  EXPECT_CALL(*modem_proxy,
+              Enable(false, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(SaveArg<2>(&disable_callback));
+  delete_bearer_callback.Run(Error(Error::kOperationFailed));
+
+  ResultCallback set_power_state_callback;
+  EXPECT_CALL(
+      *modem_proxy,
+      SetPowerState(
+          MM_MODEM_POWER_STATE_LOW, _, _,
+          CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+      .WillOnce(SaveArg<2>(&set_power_state_callback));
+  disable_callback.Run(Error(Error::kSuccess));
+
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kSuccess));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, StopModemAltairNotConnected) {
+  // Save pointers to proxies before they are lost by the call to InitProxies
+  mm1::MockModemProxy *modem_proxy = modem_proxy_.get();
+  EXPECT_CALL(*modem_proxy, set_state_changed_callback(_));
+  capability_->InitProxies();
+  capability_->set_active_bearer(nullptr);
+  cellular_->set_mm_plugin(CellularCapabilityUniversal::kAltairLTEMMPlugin);
+
+  Error error;
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  capability_->StopModem(&error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+
+  ResultCallback disable_callback;
+  EXPECT_CALL(*modem_proxy,
+              Enable(false, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(SaveArg<2>(&disable_callback));
+  dispatcher_.DispatchPendingEvents();
+
+  ResultCallback set_power_state_callback;
+  EXPECT_CALL(
+      *modem_proxy,
+      SetPowerState(
+          MM_MODEM_POWER_STATE_LOW, _, _,
+          CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+      .WillOnce(SaveArg<2>(&set_power_state_callback));
+  disable_callback.Run(Error(Error::kSuccess));
+
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kSuccess));
+  Mock::VerifyAndClearExpectations(this);
+
+  // TestCallback should get called with success even if the power state
+  // callback gets called with an error
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  set_power_state_callback.Run(Error(Error::kOperationFailed));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, TerminationAction) {
+  ExpectModemAndModem3gppProperties();
+
+  {
+    InSequence seq;
+
+    EXPECT_CALL(*modem_proxy_,
+                Enable(true, _, _, CellularCapability::kTimeoutEnable))
+        .WillOnce(Invoke(this, &CellularCapabilityUniversalTest::InvokeEnable));
+    EXPECT_CALL(*modem_proxy_,
+                Enable(false, _, _, CellularCapability::kTimeoutEnable))
+        .WillOnce(Invoke(this, &CellularCapabilityUniversalTest::InvokeEnable));
+    EXPECT_CALL(
+        *modem_proxy_,
+        SetPowerState(
+            MM_MODEM_POWER_STATE_LOW, _, _,
+            CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+        .WillOnce(Invoke(
+             this, &CellularCapabilityUniversalTest::InvokeSetPowerState));
+  }
+  EXPECT_CALL(*this, TestCallback(IsSuccess())).Times(2);
+
+  EXPECT_EQ(Cellular::kStateDisabled, cellular_->state());
+  EXPECT_EQ(Cellular::kModemStateUnknown, cellular_->modem_state());
+  EXPECT_TRUE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // Here we mimic the modem state change from ModemManager. When the modem is
+  // enabled, a termination action should be added.
+  cellular_->OnModemStateChanged(Cellular::kModemStateEnabled);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateEnabled, cellular_->state());
+  EXPECT_EQ(Cellular::kModemStateEnabled, cellular_->modem_state());
+  EXPECT_FALSE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // Running the termination action should disable the modem.
+  modem_info_.manager()->RunTerminationActions(Bind(
+      &CellularCapabilityUniversalMainTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+  // Here we mimic the modem state change from ModemManager. When the modem is
+  // disabled, the termination action should be removed.
+  cellular_->OnModemStateChanged(Cellular::kModemStateDisabled);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateDisabled, cellular_->state());
+  EXPECT_EQ(Cellular::kModemStateDisabled, cellular_->modem_state());
+  EXPECT_TRUE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // No termination action should be called here.
+  modem_info_.manager()->RunTerminationActions(Bind(
+      &CellularCapabilityUniversalMainTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityUniversalMainTest,
+       TerminationActionRemovedByStopModem) {
+  ExpectModemAndModem3gppProperties();
+
+  {
+    InSequence seq;
+
+    EXPECT_CALL(*modem_proxy_,
+                Enable(true, _, _, CellularCapability::kTimeoutEnable))
+        .WillOnce(Invoke(this, &CellularCapabilityUniversalTest::InvokeEnable));
+    EXPECT_CALL(*modem_proxy_,
+                Enable(false, _, _, CellularCapability::kTimeoutEnable))
+        .WillOnce(Invoke(this, &CellularCapabilityUniversalTest::InvokeEnable));
+    EXPECT_CALL(
+        *modem_proxy_,
+        SetPowerState(
+            MM_MODEM_POWER_STATE_LOW, _, _,
+            CellularCapabilityUniversal::kSetPowerStateTimeoutMilliseconds))
+        .WillOnce(Invoke(
+             this, &CellularCapabilityUniversalTest::InvokeSetPowerState));
+  }
+  EXPECT_CALL(*this, TestCallback(IsSuccess())).Times(1);
+
+  EXPECT_EQ(Cellular::kStateDisabled, cellular_->state());
+  EXPECT_EQ(Cellular::kModemStateUnknown, cellular_->modem_state());
+  EXPECT_TRUE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // Here we mimic the modem state change from ModemManager. When the modem is
+  // enabled, a termination action should be added.
+  cellular_->OnModemStateChanged(Cellular::kModemStateEnabled);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateEnabled, cellular_->state());
+  EXPECT_EQ(Cellular::kModemStateEnabled, cellular_->modem_state());
+  EXPECT_FALSE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // Verify that the termination action is removed when the modem is disabled
+  // not due to a suspend request.
+  cellular_->SetEnabled(false);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateDisabled, cellular_->state());
+  EXPECT_TRUE(modem_info_.manager()->termination_actions_.IsEmpty());
+
+  // No termination action should be called here.
+  modem_info_.manager()->RunTerminationActions(Bind(
+      &CellularCapabilityUniversalMainTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, DisconnectModemNoBearer) {
+  Error error;
+  ResultCallback disconnect_callback;
+  EXPECT_CALL(*modem_simple_proxy_,
+              Disconnect(_, _, _, CellularCapability::kTimeoutDisconnect))
+      .Times(0);
+  capability_->Disconnect(&error, disconnect_callback);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, DisconnectNoProxy) {
+  Error error;
+  ResultCallback disconnect_callback;
+  EXPECT_CALL(*modem_simple_proxy_,
+              Disconnect(_, _, _, CellularCapability::kTimeoutDisconnect))
+      .Times(0);
+  ReleaseCapabilityProxies();
+  capability_->Disconnect(&error, disconnect_callback);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, SimLockStatusChanged) {
+  // Set up mock SIM properties
+  const char kImsi[] = "310100000001";
+  const char kSimIdentifier[] = "9999888";
+  const char kOperatorIdentifier[] = "310240";
+  const char kOperatorName[] = "Custom SPN";
+  DBusPropertiesMap sim_properties;
+  sim_properties[MM_SIM_PROPERTY_IMSI].writer().append_string(kImsi);
+  sim_properties[MM_SIM_PROPERTY_SIMIDENTIFIER].writer()
+      .append_string(kSimIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORIDENTIFIER].writer()
+      .append_string(kOperatorIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORNAME].writer()
+      .append_string(kOperatorName);
+
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .WillOnce(Return(sim_properties));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(1);
+
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(cellular_->sim_present());
+  EXPECT_NE(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+
+  cellular_->set_imsi("");
+  cellular_->set_sim_identifier("");
+  capability_->spn_ = "";
+
+  // SIM is locked.
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PIN;
+  capability_->OnSimLockStatusChanged();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+
+  // SIM is unlocked.
+  properties_proxy_.reset(new MockDBusPropertiesProxy());
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .WillOnce(Return(sim_properties));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(1);
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_NONE;
+  capability_->OnSimLockStatusChanged();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  EXPECT_EQ(kImsi, cellular_->imsi());
+  EXPECT_EQ(kSimIdentifier, cellular_->sim_identifier());
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  // SIM is missing and SIM path is "/".
+  capability_->OnSimPathChanged(CellularCapabilityUniversal::kRootPath);
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ(CellularCapabilityUniversal::kRootPath, capability_->sim_path_);
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _)).Times(0);
+  capability_->OnSimLockStatusChanged();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+
+  // SIM is missing and SIM path is empty.
+  capability_->OnSimPathChanged("");
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ("", capability_->sim_path_);
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(_, _)).Times(0);
+  capability_->OnSimLockStatusChanged();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, PropertiesChanged) {
+  // Set up mock modem properties
+  DBusPropertiesMap modem_properties;
+  modem_properties[MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES].
+      writer().append_uint32(kAccessTechnologies);
+  modem_properties[MM_MODEM_PROPERTY_SIM].
+      writer().append_path(kSimPath);
+
+  // Set up mock modem 3gpp properties
+  DBusPropertiesMap modem3gpp_properties;
+  modem3gpp_properties[MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS].
+      writer().append_uint32(0);
+  modem3gpp_properties[MM_MODEM_MODEM3GPP_PROPERTY_IMEI].
+      writer().append_string(kImei);
+
+  // Set up mock modem sim properties
+  DBusPropertiesMap sim_properties;
+
+  EXPECT_CALL(*properties_proxy_,
+              GetAll(MM_DBUS_INTERFACE_SIM))
+      .WillOnce(Return(sim_properties));
+
+  EXPECT_EQ("", cellular_->imei());
+  EXPECT_EQ(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN,
+            capability_->access_technologies_);
+  EXPECT_FALSE(capability_->sim_proxy_.get());
+  EXPECT_CALL(*device_adaptor_, EmitStringChanged(
+      kTechnologyFamilyProperty, kTechnologyFamilyGsm));
+  EXPECT_CALL(*device_adaptor_, EmitStringChanged(kImeiProperty, kImei));
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_properties, vector<string>());
+  EXPECT_EQ(kAccessTechnologies, capability_->access_technologies_);
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_TRUE(capability_->sim_proxy_.get());
+
+  // Changing properties on wrong interface will not have an effect
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem3gpp_properties,
+                                       vector<string>());
+  EXPECT_EQ("", cellular_->imei());
+
+  // Changing properties on the right interface gets reflected in the
+  // capabilities object
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM_MODEM3GPP,
+                                       modem3gpp_properties,
+                                       vector<string>());
+  EXPECT_EQ(kImei, cellular_->imei());
+  Mock::VerifyAndClearExpectations(device_adaptor_);
+
+  // Expect to see changes when the family changes
+  modem_properties.clear();
+  modem_properties[MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES].
+      writer().append_uint32(MM_MODEM_ACCESS_TECHNOLOGY_1XRTT);
+  EXPECT_CALL(*device_adaptor_, EmitStringChanged(
+      kTechnologyFamilyProperty, kTechnologyFamilyCdma)).
+      Times(1);
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_properties,
+                                       vector<string>());
+  Mock::VerifyAndClearExpectations(device_adaptor_);
+
+  // Back to LTE
+  modem_properties.clear();
+  modem_properties[MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES].
+      writer().append_uint32(MM_MODEM_ACCESS_TECHNOLOGY_LTE);
+  EXPECT_CALL(*device_adaptor_, EmitStringChanged(
+      kTechnologyFamilyProperty, kTechnologyFamilyGsm)).
+      Times(1);
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_properties,
+                                       vector<string>());
+  Mock::VerifyAndClearExpectations(device_adaptor_);
+
+  // LTE & CDMA - the device adaptor should not be called!
+  modem_properties.clear();
+  modem_properties[MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES].
+      writer().append_uint32(MM_MODEM_ACCESS_TECHNOLOGY_LTE |
+                             MM_MODEM_ACCESS_TECHNOLOGY_1XRTT);
+  EXPECT_CALL(*device_adaptor_, EmitStringChanged(_, _)).Times(0);
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_properties,
+                                       vector<string>());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, UpdateRegistrationState) {
+  capability_->InitProxies();
+
+  CreateService();
+  cellular_->set_imsi("310240123456789");
+  cellular_->set_modem_state(Cellular::kModemStateConnected);
+  SetRegistrationDroppedUpdateTimeout(0);
+
+  const Stringmap &home_provider_map = cellular_->home_provider();
+  ASSERT_NE(home_provider_map.end(), home_provider_map.find(kOperatorNameKey));
+  string home_provider = home_provider_map.find(kOperatorNameKey)->second;
+  string ota_name = cellular_->service_->friendly_name();
+
+  // Home --> Roaming should be effective immediately.
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING,
+            capability_->registration_state_);
+
+  // Idle --> Roaming should be effective immediately.
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_IDLE,
+      home_provider,
+      ota_name);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_IDLE,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING,
+            capability_->registration_state_);
+
+  // Idle --> Searching should be effective immediately.
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_IDLE,
+      home_provider,
+      ota_name);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_IDLE,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+            capability_->registration_state_);
+
+  // Home --> Searching --> Home should never see Searching.
+  EXPECT_CALL(*(modem_info_.mock_metrics()),
+      Notify3GPPRegistrationDelayedDropPosted());
+  EXPECT_CALL(*(modem_info_.mock_metrics()),
+      Notify3GPPRegistrationDelayedDropCanceled());
+
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_metrics());
+
+  // Home --> Searching --> wait till dispatch should see Searching
+  EXPECT_CALL(*(modem_info_.mock_metrics()),
+      Notify3GPPRegistrationDelayedDropPosted());
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+            capability_->registration_state_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_metrics());
+
+  // Home --> Searching --> Searching --> wait till dispatch should see
+  // Searching *and* the first callback should be cancelled.
+  EXPECT_CALL(*this, DummyCallback()).Times(0);
+  EXPECT_CALL(*(modem_info_.mock_metrics()),
+      Notify3GPPRegistrationDelayedDropPosted());
+
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  SetMockRegistrationDroppedUpdateCallback();
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+            capability_->registration_state_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, IsRegistered) {
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
+  EXPECT_FALSE(capability_->IsRegistered());
+
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability_->IsRegistered());
+
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
+  EXPECT_FALSE(capability_->IsRegistered());
+
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_DENIED;
+  EXPECT_FALSE(capability_->IsRegistered());
+
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+  EXPECT_FALSE(capability_->IsRegistered());
+
+  capability_->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING;
+  EXPECT_TRUE(capability_->IsRegistered());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest,
+       UpdateRegistrationStateModemNotConnected) {
+  capability_->InitProxies();
+  CreateService();
+
+  cellular_->set_imsi("310240123456789");
+  cellular_->set_modem_state(Cellular::kModemStateRegistered);
+  SetRegistrationDroppedUpdateTimeout(0);
+
+  const Stringmap &home_provider_map = cellular_->home_provider();
+  ASSERT_NE(home_provider_map.end(), home_provider_map.find(kOperatorNameKey));
+  string home_provider = home_provider_map.find(kOperatorNameKey)->second;
+  string ota_name = cellular_->service_->friendly_name();
+
+  // Home --> Searching should be effective immediately.
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_HOME,
+            capability_->registration_state_);
+  capability_->On3GPPRegistrationChanged(
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+      home_provider,
+      ota_name);
+  EXPECT_EQ(MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING,
+            capability_->registration_state_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, IsValidSimPath) {
+  // Invalid paths
+  EXPECT_FALSE(capability_->IsValidSimPath(""));
+  EXPECT_FALSE(capability_->IsValidSimPath("/"));
+
+  // A valid path
+  EXPECT_TRUE(capability_->IsValidSimPath(
+      "/org/freedesktop/ModemManager1/SIM/0"));
+
+  // Note that any string that is not one of the above invalid paths is
+  // currently regarded as valid, since the ModemManager spec doesn't impose
+  // a strict format on the path. The validity of this is subject to change.
+  EXPECT_TRUE(capability_->IsValidSimPath("path"));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, NormalizeMdn) {
+  EXPECT_EQ("", capability_->NormalizeMdn(""));
+  EXPECT_EQ("12345678901", capability_->NormalizeMdn("12345678901"));
+  EXPECT_EQ("12345678901", capability_->NormalizeMdn("+1 234 567 8901"));
+  EXPECT_EQ("12345678901", capability_->NormalizeMdn("+1-234-567-8901"));
+  EXPECT_EQ("12345678901", capability_->NormalizeMdn("+1 (234) 567-8901"));
+  EXPECT_EQ("12345678901", capability_->NormalizeMdn("1 234  567 8901 "));
+  EXPECT_EQ("2345678901", capability_->NormalizeMdn("(234) 567-8901"));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, SimPathChanged) {
+  // Set up mock modem SIM properties
+  const char kImsi[] = "310100000001";
+  const char kSimIdentifier[] = "9999888";
+  const char kOperatorIdentifier[] = "310240";
+  const char kOperatorName[] = "Custom SPN";
+  DBusPropertiesMap sim_properties;
+  sim_properties[MM_SIM_PROPERTY_IMSI].writer().append_string(kImsi);
+  sim_properties[MM_SIM_PROPERTY_SIMIDENTIFIER].writer()
+      .append_string(kSimIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORIDENTIFIER].writer()
+      .append_string(kOperatorIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORNAME].writer()
+      .append_string(kOperatorName);
+
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .Times(1).WillOnce(Return(sim_properties));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(1);
+
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ("", capability_->sim_path_);
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(cellular_->sim_present());
+  EXPECT_NE(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_EQ(kImsi, cellular_->imsi());
+  EXPECT_EQ(kSimIdentifier, cellular_->sim_identifier());
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  // Changing to the same SIM path should be a no-op.
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(cellular_->sim_present());
+  EXPECT_NE(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_EQ(kImsi, cellular_->imsi());
+  EXPECT_EQ(kSimIdentifier, cellular_->sim_identifier());
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  capability_->OnSimPathChanged("");
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(properties_proxy_.get());
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ("", capability_->sim_path_);
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .Times(1).WillOnce(Return(sim_properties));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(1);
+
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(cellular_->sim_present());
+  EXPECT_NE(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_EQ(kImsi, cellular_->imsi());
+  EXPECT_EQ(kSimIdentifier, cellular_->sim_identifier());
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  capability_->OnSimPathChanged("/");
+  EXPECT_FALSE(cellular_->sim_present());
+  EXPECT_EQ(nullptr, capability_->sim_proxy_);;
+  EXPECT_EQ("/", capability_->sim_path_);
+  EXPECT_EQ("", cellular_->imsi());
+  EXPECT_EQ("", cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, SimPropertiesChanged) {
+  // Set up mock modem properties
+  DBusPropertiesMap modem_properties;
+  modem_properties[MM_MODEM_PROPERTY_SIM].writer().append_path(kSimPath);
+
+  // Set up mock modem sim properties
+  const char kImsi[] = "310100000001";
+  DBusPropertiesMap sim_properties;
+  sim_properties[MM_SIM_PROPERTY_IMSI].writer().append_string(kImsi);
+
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .WillOnce(Return(sim_properties));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(0);
+
+  EXPECT_FALSE(capability_->sim_proxy_.get());
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_MODEM,
+                                       modem_properties, vector<string>());
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_TRUE(capability_->sim_proxy_.get());
+  EXPECT_EQ(kImsi, cellular_->imsi());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // Updating the SIM
+  DBusPropertiesMap new_properties;
+  const char kNewImsi[] = "310240123456789";
+  const char kSimIdentifier[] = "9999888";
+  const char kOperatorIdentifier[] = "310240";
+  const char kOperatorName[] = "Custom SPN";
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(2);
+  EXPECT_CALL(*mock_home_provider_info_, UpdateIMSI(kNewImsi)).Times(2);
+  new_properties[MM_SIM_PROPERTY_IMSI].writer().append_string(kNewImsi);
+  new_properties[MM_SIM_PROPERTY_SIMIDENTIFIER].writer().
+      append_string(kSimIdentifier);
+  new_properties[MM_SIM_PROPERTY_OPERATORIDENTIFIER].writer().
+      append_string(kOperatorIdentifier);
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_SIM,
+                                       new_properties,
+                                       vector<string>());
+  EXPECT_EQ(kNewImsi, cellular_->imsi());
+  EXPECT_EQ(kSimIdentifier, cellular_->sim_identifier());
+  EXPECT_EQ("", capability_->spn_);
+
+  new_properties[MM_SIM_PROPERTY_OPERATORNAME].writer().
+      append_string(kOperatorName);
+  capability_->OnDBusPropertiesChanged(MM_DBUS_INTERFACE_SIM,
+                                       new_properties,
+                                       vector<string>());
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+}
+
+MATCHER_P(SizeIs, value, "") {
+  return static_cast<size_t>(value) == arg.size();
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, Reset) {
+  // Save pointers to proxies before they are lost by the call to InitProxies
+  mm1::MockModemProxy *modem_proxy = modem_proxy_.get();
+  EXPECT_CALL(*modem_proxy, set_state_changed_callback(_));
+  capability_->InitProxies();
+
+  Error error;
+  ResultCallback reset_callback;
+
+  EXPECT_CALL(*modem_proxy, Reset(_, _, CellularCapability::kTimeoutReset))
+      .WillOnce(SaveArg<1>(&reset_callback));
+
+  capability_->Reset(&error, ResultCallback());
+  EXPECT_TRUE(capability_->resetting_);
+  reset_callback.Run(error);
+  EXPECT_FALSE(capability_->resetting_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, UpdateActiveBearer) {
+  // Common resources.
+  const size_t kPathCount = 3;
+  DBus::Path active_paths[kPathCount], inactive_paths[kPathCount];
+  for (size_t i = 0; i < kPathCount; ++i) {
+    active_paths[i] = base::StringPrintf("%s/%zu", kActiveBearerPathPrefix, i);
+    inactive_paths[i] =
+        base::StringPrintf("%s/%zu", kInactiveBearerPathPrefix, i);
+  }
+
+  EXPECT_EQ(nullptr, capability_->GetActiveBearer());;
+
+  // Check that |active_bearer_| is set correctly when an active bearer is
+  // returned.
+  capability_->OnBearersChanged({inactive_paths[0],
+                                 inactive_paths[1],
+                                 active_paths[2],
+                                 inactive_paths[1],
+                                 inactive_paths[2]});
+  capability_->UpdateActiveBearer();
+  ASSERT_NE(nullptr, capability_->GetActiveBearer());;
+  EXPECT_EQ(active_paths[2], capability_->GetActiveBearer()->dbus_path());
+
+  // Check that |active_bearer_| is nullptr if no active bearers are returned.
+  capability_->OnBearersChanged({inactive_paths[0],
+                                 inactive_paths[1],
+                                 inactive_paths[2],
+                                 inactive_paths[1]});
+  capability_->UpdateActiveBearer();
+  EXPECT_EQ(nullptr, capability_->GetActiveBearer());;
+
+  // Check that returning multiple bearers causes death.
+  capability_->OnBearersChanged({active_paths[0],
+                                 inactive_paths[1],
+                                 inactive_paths[2],
+                                 active_paths[1],
+                                 inactive_paths[1]});
+  EXPECT_DEATH(capability_->UpdateActiveBearer(),
+               "Found more than one active bearer.");
+
+  capability_->OnBearersChanged({});
+  capability_->UpdateActiveBearer();
+  EXPECT_EQ(nullptr, capability_->GetActiveBearer());;
+}
+
+// Validates expected behavior of Connect function
+TEST_F(CellularCapabilityUniversalMainTest, Connect) {
+  mm1::MockModemSimpleProxy *modem_simple_proxy = modem_simple_proxy_.get();
+  SetSimpleProxy();
+  Error error;
+  DBusPropertiesMap properties;
+  capability_->apn_try_list_.clear();
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  DBus::Path bearer("/foo");
+
+  // Test connect failures
+  EXPECT_CALL(*modem_simple_proxy, Connect(_, _, _, _))
+      .WillRepeatedly(SaveArg<2>(&connect_callback_));
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  EXPECT_CALL(*service_, ClearLastGoodApn());
+  connect_callback_.Run(bearer, Error(Error::kOperationFailed));
+  Mock::VerifyAndClearExpectations(this);
+
+  // Test connect success
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  connect_callback_.Run(bearer, Error(Error::kSuccess));
+  Mock::VerifyAndClearExpectations(this);
+
+  // Test connect failures without a service.  Make sure that shill
+  // does not crash if the connect failed and there is no
+  // CellularService object.  This can happen if the modem is enabled
+  // and then quickly disabled.
+  cellular_->service_ = nullptr;
+  EXPECT_FALSE(capability_->cellular()->service());
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  connect_callback_.Run(bearer, Error(Error::kOperationFailed));
+}
+
+// Validates Connect iterates over APNs
+TEST_F(CellularCapabilityUniversalMainTest, ConnectApns) {
+  mm1::MockModemSimpleProxy *modem_simple_proxy = modem_simple_proxy_.get();
+  SetSimpleProxy();
+  Error error;
+  DBusPropertiesMap properties;
+  capability_->apn_try_list_.clear();
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  DBus::Path bearer("/bearer0");
+
+  const char apn_name_foo[] = "foo";
+  const char apn_name_bar[] = "bar";
+  EXPECT_CALL(*modem_simple_proxy, Connect(HasApn(apn_name_foo), _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  Stringmap apn1;
+  apn1[kApnProperty] = apn_name_foo;
+  capability_->apn_try_list_.push_back(apn1);
+  Stringmap apn2;
+  apn2[kApnProperty] = apn_name_bar;
+  capability_->apn_try_list_.push_back(apn2);
+  capability_->FillConnectPropertyMap(&properties);
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  Mock::VerifyAndClearExpectations(modem_simple_proxy);
+
+  EXPECT_CALL(*modem_simple_proxy, Connect(HasApn(apn_name_bar), _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  EXPECT_CALL(*service_, ClearLastGoodApn());
+  connect_callback_.Run(bearer, Error(Error::kInvalidApn));
+
+  EXPECT_CALL(*service_, SetLastGoodApn(apn2));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  connect_callback_.Run(bearer, Error(Error::kSuccess));
+}
+
+// Validates GetTypeString and AccessTechnologyToTechnologyFamily
+TEST_F(CellularCapabilityUniversalMainTest, GetTypeString) {
+  const int gsm_technologies[] = {
+    MM_MODEM_ACCESS_TECHNOLOGY_LTE,
+    MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS,
+    MM_MODEM_ACCESS_TECHNOLOGY_HSPA,
+    MM_MODEM_ACCESS_TECHNOLOGY_HSUPA,
+    MM_MODEM_ACCESS_TECHNOLOGY_HSDPA,
+    MM_MODEM_ACCESS_TECHNOLOGY_UMTS,
+    MM_MODEM_ACCESS_TECHNOLOGY_EDGE,
+    MM_MODEM_ACCESS_TECHNOLOGY_GPRS,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM,
+    MM_MODEM_ACCESS_TECHNOLOGY_LTE | MM_MODEM_ACCESS_TECHNOLOGY_EVDO0,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM | MM_MODEM_ACCESS_TECHNOLOGY_EVDO0,
+    MM_MODEM_ACCESS_TECHNOLOGY_LTE | MM_MODEM_ACCESS_TECHNOLOGY_EVDOA,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM | MM_MODEM_ACCESS_TECHNOLOGY_EVDOA,
+    MM_MODEM_ACCESS_TECHNOLOGY_LTE | MM_MODEM_ACCESS_TECHNOLOGY_EVDOB,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM | MM_MODEM_ACCESS_TECHNOLOGY_EVDOB,
+    MM_MODEM_ACCESS_TECHNOLOGY_GSM | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT,
+  };
+  for (size_t i = 0; i < arraysize(gsm_technologies); ++i) {
+    capability_->access_technologies_ = gsm_technologies[i];
+    ASSERT_EQ(capability_->GetTypeString(), kTechnologyFamilyGsm);
+  }
+  const int cdma_technologies[] = {
+    MM_MODEM_ACCESS_TECHNOLOGY_EVDO0,
+    MM_MODEM_ACCESS_TECHNOLOGY_EVDOA,
+    MM_MODEM_ACCESS_TECHNOLOGY_EVDOA | MM_MODEM_ACCESS_TECHNOLOGY_EVDO0,
+    MM_MODEM_ACCESS_TECHNOLOGY_EVDOB,
+    MM_MODEM_ACCESS_TECHNOLOGY_EVDOB | MM_MODEM_ACCESS_TECHNOLOGY_EVDO0,
+    MM_MODEM_ACCESS_TECHNOLOGY_1XRTT,
+  };
+  for (size_t i = 0; i < arraysize(cdma_technologies); ++i) {
+    capability_->access_technologies_ = cdma_technologies[i];
+    ASSERT_EQ(capability_->GetTypeString(), kTechnologyFamilyCdma);
+  }
+  capability_->access_technologies_ = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+  ASSERT_EQ(capability_->GetTypeString(), "");
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, AllowRoaming) {
+  EXPECT_FALSE(cellular_->allow_roaming_);
+  EXPECT_FALSE(cellular_->provider_requires_roaming());
+  EXPECT_FALSE(capability_->AllowRoaming());
+  cellular_->set_provider_requires_roaming(true);
+  EXPECT_TRUE(capability_->AllowRoaming());
+  cellular_->set_provider_requires_roaming(false);
+  cellular_->allow_roaming_ = true;
+  EXPECT_TRUE(capability_->AllowRoaming());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, GetMdnForOLP) {
+  const string kVzwUUID = "c83d6597-dc91-4d48-a3a7-d86b80123751";
+  const string kFooUUID = "foo";
+  MockMobileOperatorInfo mock_operator_info(&dispatcher_,
+                                            "MobileOperatorInfo");
+
+  mock_operator_info.SetEmptyDefaultsForProperties();
+  EXPECT_CALL(mock_operator_info, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(mock_operator_info, uuid()).WillRepeatedly(ReturnRef(kVzwUUID));
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnknown;
+
+  cellular_->set_mdn("");
+  EXPECT_EQ("0000000000", capability_->GetMdnForOLP(&mock_operator_info));
+  cellular_->set_mdn("0123456789");
+  EXPECT_EQ("0123456789", capability_->GetMdnForOLP(&mock_operator_info));
+  cellular_->set_mdn("10123456789");
+  EXPECT_EQ("0123456789", capability_->GetMdnForOLP(&mock_operator_info));
+
+  cellular_->set_mdn("1021232333");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnprovisioned;
+  EXPECT_EQ("0000000000", capability_->GetMdnForOLP(&mock_operator_info));
+  Mock::VerifyAndClearExpectations(&mock_operator_info);
+
+  mock_operator_info.SetEmptyDefaultsForProperties();
+  EXPECT_CALL(mock_operator_info, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(mock_operator_info, uuid()).WillRepeatedly(ReturnRef(kFooUUID));
+
+  cellular_->set_mdn("");
+  EXPECT_EQ("", capability_->GetMdnForOLP(&mock_operator_info));
+  cellular_->set_mdn("0123456789");
+  EXPECT_EQ("0123456789", capability_->GetMdnForOLP(&mock_operator_info));
+  cellular_->set_mdn("10123456789");
+  EXPECT_EQ("10123456789", capability_->GetMdnForOLP(&mock_operator_info));
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, UpdateServiceOLP) {
+  const MobileOperatorInfo::OnlinePortal kOlp {
+      "http://testurl",
+      "POST",
+      "imei=${imei}&imsi=${imsi}&mdn=${mdn}&min=${min}&iccid=${iccid}"};
+  const vector<MobileOperatorInfo::OnlinePortal> kOlpList {kOlp};
+  const string kUuidVzw = "c83d6597-dc91-4d48-a3a7-d86b80123751";
+  const string kUuidFoo = "foo";
+
+  cellular_->set_imei("1");
+  cellular_->set_imsi("2");
+  cellular_->set_mdn("10123456789");
+  cellular_->set_min("5");
+  cellular_->set_sim_identifier("6");
+
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, olp_list())
+      .WillRepeatedly(ReturnRef(kOlpList));
+  EXPECT_CALL(*mock_home_provider_info_, uuid())
+      .WillOnce(ReturnRef(kUuidVzw));
+  CreateService();
+  capability_->UpdateServiceOLP();
+  // Copy to simplify assertions below.
+  Stringmap vzw_olp = cellular_->service()->olp();
+  EXPECT_EQ("http://testurl", vzw_olp[kPaymentPortalURL]);
+  EXPECT_EQ("POST", vzw_olp[kPaymentPortalMethod]);
+  EXPECT_EQ("imei=1&imsi=2&mdn=0123456789&min=5&iccid=6",
+            vzw_olp[kPaymentPortalPostData]);
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, olp_list())
+      .WillRepeatedly(ReturnRef(kOlpList));
+  EXPECT_CALL(*mock_home_provider_info_, uuid())
+      .WillOnce(ReturnRef(kUuidFoo));
+  capability_->UpdateServiceOLP();
+  // Copy to simplify assertions below.
+  Stringmap olp = cellular_->service()->olp();
+  EXPECT_EQ("http://testurl", olp[kPaymentPortalURL]);
+  EXPECT_EQ("POST", olp[kPaymentPortalMethod]);
+  EXPECT_EQ("imei=1&imsi=2&mdn=10123456789&min=5&iccid=6",
+            olp[kPaymentPortalPostData]);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, IsMdnValid) {
+  cellular_->set_mdn("");
+  EXPECT_FALSE(capability_->IsMdnValid());
+  cellular_->set_mdn("0000000");
+  EXPECT_FALSE(capability_->IsMdnValid());
+  cellular_->set_mdn("0000001");
+  EXPECT_TRUE(capability_->IsMdnValid());
+  cellular_->set_mdn("1231223");
+  EXPECT_TRUE(capability_->IsMdnValid());
+}
+
+TEST_F(CellularCapabilityUniversalTimerTest, CompleteActivation) {
+  const char kIccid[] = "1234567";
+
+  cellular_->set_sim_identifier(kIccid);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              SetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid,
+                                 PendingActivationStore::kStatePending))
+      .Times(1);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .WillOnce(Return(PendingActivationStore::kStatePending));
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivating))
+      .Times(1);
+  EXPECT_CALL(*modem_proxy_.get(), Reset(_, _, _)).Times(1);
+  Error error;
+  capability_->InitProxies();
+  capability_->CompleteActivation(&error);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  Mock::VerifyAndClearExpectations(service_);
+  Mock::VerifyAndClearExpectations(&mock_dispatcher_);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, UpdateServiceActivationState) {
+  const char kIccid[] = "1234567";
+  const vector<MobileOperatorInfo::OnlinePortal> olp_list {
+    {"some@url", "some_method", "some_post_data"}
+  };
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnprovisioned;
+  cellular_->set_sim_identifier("");
+  cellular_->set_mdn("0000000000");
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, olp_list())
+      .WillRepeatedly(ReturnRef(olp_list));
+
+  service_->SetAutoConnect(false);
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateNotActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  EXPECT_FALSE(service_->auto_connect());
+
+  cellular_->set_mdn("1231231122");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnknown;
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  EXPECT_TRUE(service_->auto_connect());
+
+  // Make sure we don't overwrite auto-connect if a service is already
+  // activated before calling UpdateServiceActivationState().
+  service_->SetAutoConnect(false);
+  EXPECT_FALSE(service_->auto_connect());
+  const string activation_state = kActivationStateActivated;
+  EXPECT_CALL(*service_, activation_state())
+      .WillOnce(ReturnRef(activation_state));
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  EXPECT_FALSE(service_->auto_connect());
+
+  service_->SetAutoConnect(false);
+  cellular_->set_mdn("0000000000");
+  cellular_->set_sim_identifier(kIccid);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .Times(1)
+      .WillRepeatedly(Return(PendingActivationStore::kStatePending));
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivating))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  EXPECT_FALSE(service_->auto_connect());
+
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .Times(2)
+      .WillRepeatedly(Return(PendingActivationStore::kStateActivated));
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+  EXPECT_TRUE(service_->auto_connect());
+
+  // SubscriptionStateUnprovisioned overrides valid MDN.
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnprovisioned;
+  cellular_->set_mdn("1231231122");
+  cellular_->set_sim_identifier("");
+  service_->SetAutoConnect(false);
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateNotActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  EXPECT_FALSE(service_->auto_connect());
+
+  // SubscriptionStateProvisioned overrides invalid MDN.
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateProvisioned;
+  cellular_->set_mdn("0000000000");
+  cellular_->set_sim_identifier("");
+  service_->SetAutoConnect(false);
+  EXPECT_CALL(*service_, SetActivationState(kActivationStateActivated))
+      .Times(1);
+  capability_->UpdateServiceActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+  EXPECT_TRUE(service_->auto_connect());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, UpdatePendingActivationState) {
+  const char kIccid[] = "1234567";
+
+  capability_->InitProxies();
+  capability_->registration_state_ =
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
+
+  // No MDN, no ICCID.
+  cellular_->set_mdn("0000000");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnknown;
+  cellular_->set_sim_identifier("");
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // Valid MDN, but subsciption_state_ Unprovisioned
+  cellular_->set_mdn("1234567");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnprovisioned;
+  cellular_->set_sim_identifier("");
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID, _))
+      .Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // ICCID known.
+  cellular_->set_sim_identifier(kIccid);
+
+  // After the modem has reset.
+  capability_->reset_done_ = true;
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .Times(1).WillOnce(Return(PendingActivationStore::kStatePending));
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              SetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid,
+                                 PendingActivationStore::kStateActivated))
+      .Times(1);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // Not registered.
+  capability_->registration_state_ =
+      MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .Times(2).WillRepeatedly(Return(PendingActivationStore::kStateActivated));
+  EXPECT_CALL(*service_, AutoConnect()).Times(0);
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(service_);
+
+  // Service, registered.
+  capability_->registration_state_ =
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
+  EXPECT_CALL(*service_, AutoConnect()).Times(1);
+  capability_->UpdatePendingActivationState();
+
+  cellular_->service_->activation_state_ = kActivationStateNotActivated;
+
+  Mock::VerifyAndClearExpectations(service_);
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // Device is connected.
+  cellular_->state_ = Cellular::kStateConnected;
+  capability_->UpdatePendingActivationState();
+
+  // Device is linked.
+  cellular_->state_ = Cellular::kStateLinked;
+  capability_->UpdatePendingActivationState();
+
+  // Got valid MDN, subscription_state_ is kSubscriptionStateUnknown
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              RemoveEntry(PendingActivationStore::kIdentifierICCID, kIccid));
+  cellular_->state_ = Cellular::kStateRegistered;
+  cellular_->set_mdn("1020304");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnknown;
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+
+  // Got invalid MDN, subscription_state_ is kSubscriptionStateProvisioned
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              RemoveEntry(PendingActivationStore::kIdentifierICCID, kIccid));
+  cellular_->state_ = Cellular::kStateRegistered;
+  cellular_->set_mdn("0000000");
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateProvisioned;
+  capability_->UpdatePendingActivationState();
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, IsServiceActivationRequired) {
+  const vector<MobileOperatorInfo::OnlinePortal> empty_list;
+  const vector<MobileOperatorInfo::OnlinePortal> olp_list {
+    {"some@url", "some_method", "some_post_data"}
+  };
+
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateProvisioned;
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnprovisioned;
+  EXPECT_TRUE(capability_->IsServiceActivationRequired());
+
+  capability_->subscription_state_ =
+      CellularCapabilityUniversal::kSubscriptionStateUnknown;
+  cellular_->set_mdn("0000000000");
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, olp_list())
+      .WillRepeatedly(ReturnRef(empty_list));
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+
+  // Set expectations for all subsequent cases.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, olp_list())
+      .WillRepeatedly(ReturnRef(olp_list));
+
+  cellular_->set_mdn("");
+  EXPECT_TRUE(capability_->IsServiceActivationRequired());
+  cellular_->set_mdn("1234567890");
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  cellular_->set_mdn("0000000000");
+  EXPECT_TRUE(capability_->IsServiceActivationRequired());
+
+  const char kIccid[] = "1234567890";
+  cellular_->set_sim_identifier(kIccid);
+  EXPECT_CALL(*modem_info_.mock_pending_activation_store(),
+              GetActivationState(PendingActivationStore::kIdentifierICCID,
+                                 kIccid))
+      .WillOnce(Return(PendingActivationStore::kStateActivated))
+      .WillOnce(Return(PendingActivationStore::kStatePending))
+      .WillOnce(Return(PendingActivationStore::kStateUnknown));
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  EXPECT_FALSE(capability_->IsServiceActivationRequired());
+  EXPECT_TRUE(capability_->IsServiceActivationRequired());
+  Mock::VerifyAndClearExpectations(modem_info_.mock_pending_activation_store());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, OnModemCurrentCapabilitiesChanged) {
+  EXPECT_FALSE(cellular_->scanning_supported());
+  capability_->OnModemCurrentCapabilitiesChanged(MM_MODEM_CAPABILITY_LTE);
+  EXPECT_FALSE(cellular_->scanning_supported());
+  capability_->OnModemCurrentCapabilitiesChanged(MM_MODEM_CAPABILITY_CDMA_EVDO);
+  EXPECT_FALSE(cellular_->scanning_supported());
+  capability_->OnModemCurrentCapabilitiesChanged(MM_MODEM_CAPABILITY_GSM_UMTS);
+  EXPECT_TRUE(cellular_->scanning_supported());
+  capability_->OnModemCurrentCapabilitiesChanged(
+      MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO);
+  EXPECT_TRUE(cellular_->scanning_supported());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, GetNetworkTechnologyStringOnE362) {
+  cellular_->set_model_id("");;
+  capability_->access_technologies_ = 0;
+  EXPECT_TRUE(capability_->GetNetworkTechnologyString().empty());
+
+  cellular_->set_mm_plugin(CellularCapabilityUniversal::kNovatelLTEMMPlugin);
+  EXPECT_EQ(kNetworkTechnologyLte, capability_->GetNetworkTechnologyString());
+
+  capability_->access_technologies_ = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+  EXPECT_EQ(kNetworkTechnologyLte, capability_->GetNetworkTechnologyString());
+
+  cellular_->set_mm_plugin("");
+  EXPECT_EQ(kNetworkTechnologyGprs, capability_->GetNetworkTechnologyString());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, GetOutOfCreditsDetectionType) {
+  cellular_->set_model_id("");;
+  EXPECT_EQ(OutOfCreditsDetector::OOCTypeNone,
+            capability_->GetOutOfCreditsDetectionType());
+  cellular_->set_mm_plugin(CellularCapabilityUniversal::kAltairLTEMMPlugin);
+  EXPECT_EQ(OutOfCreditsDetector::OOCTypeSubscriptionState,
+            capability_->GetOutOfCreditsDetectionType());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, SimLockStatusToProperty) {
+  Error error;
+  KeyValueStore store = capability_->SimLockStatusToProperty(&error);
+  EXPECT_FALSE(store.GetBool(kSIMLockEnabledProperty));
+  EXPECT_TRUE(store.GetString(kSIMLockTypeProperty).empty());
+  EXPECT_EQ(0, store.GetUint(kSIMLockRetriesLeftProperty));
+
+  capability_->sim_lock_status_.enabled = true;
+  capability_->sim_lock_status_.retries_left = 3;
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PIN;
+  store = capability_->SimLockStatusToProperty(&error);
+  EXPECT_TRUE(store.GetBool(kSIMLockEnabledProperty));
+  EXPECT_EQ("sim-pin", store.GetString(kSIMLockTypeProperty));
+  EXPECT_EQ(3, store.GetUint(kSIMLockRetriesLeftProperty));
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PUK;
+  store = capability_->SimLockStatusToProperty(&error);
+  EXPECT_EQ("sim-puk", store.GetString(kSIMLockTypeProperty));
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PIN2;
+  store = capability_->SimLockStatusToProperty(&error);
+  EXPECT_TRUE(store.GetString(kSIMLockTypeProperty).empty());
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PUK2;
+  store = capability_->SimLockStatusToProperty(&error);
+  EXPECT_TRUE(store.GetString(kSIMLockTypeProperty).empty());
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, OnLockRetriesChanged) {
+  CellularCapabilityUniversal::LockRetryData data;
+  const uint32_t kDefaultRetries = 999;
+
+  capability_->OnLockRetriesChanged(data);
+  EXPECT_EQ(kDefaultRetries, capability_->sim_lock_status_.retries_left);
+
+  data[MM_MODEM_LOCK_SIM_PIN] = 3;
+  data[MM_MODEM_LOCK_SIM_PUK] = 10;
+  capability_->OnLockRetriesChanged(data);
+  EXPECT_EQ(3, capability_->sim_lock_status_.retries_left);
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PUK;
+  capability_->OnLockRetriesChanged(data);
+  EXPECT_EQ(10, capability_->sim_lock_status_.retries_left);
+
+  capability_->sim_lock_status_.lock_type = MM_MODEM_LOCK_SIM_PIN;
+  capability_->OnLockRetriesChanged(data);
+  EXPECT_EQ(3, capability_->sim_lock_status_.retries_left);
+
+  data.clear();
+  capability_->OnLockRetriesChanged(data);
+  EXPECT_EQ(kDefaultRetries, capability_->sim_lock_status_.retries_left);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, OnLockTypeChanged) {
+  EXPECT_EQ(MM_MODEM_LOCK_UNKNOWN, capability_->sim_lock_status_.lock_type);
+
+  capability_->OnLockTypeChanged(MM_MODEM_LOCK_NONE);
+  EXPECT_EQ(MM_MODEM_LOCK_NONE, capability_->sim_lock_status_.lock_type);
+  EXPECT_FALSE(capability_->sim_lock_status_.enabled);
+
+  capability_->OnLockTypeChanged(MM_MODEM_LOCK_SIM_PIN);
+  EXPECT_EQ(MM_MODEM_LOCK_SIM_PIN, capability_->sim_lock_status_.lock_type);
+  EXPECT_TRUE(capability_->sim_lock_status_.enabled);
+
+  capability_->sim_lock_status_.enabled = false;
+  capability_->OnLockTypeChanged(MM_MODEM_LOCK_SIM_PUK);
+  EXPECT_EQ(MM_MODEM_LOCK_SIM_PUK, capability_->sim_lock_status_.lock_type);
+  EXPECT_TRUE(capability_->sim_lock_status_.enabled);
+}
+
+TEST_F(CellularCapabilityUniversalMainTest, OnSimLockPropertiesChanged) {
+  EXPECT_EQ(MM_MODEM_LOCK_UNKNOWN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(0, capability_->sim_lock_status_.retries_left);
+
+  DBusPropertiesMap changed;
+  vector<string> invalidated;
+
+  capability_->OnModemPropertiesChanged(changed, invalidated);
+  EXPECT_EQ(MM_MODEM_LOCK_UNKNOWN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(0, capability_->sim_lock_status_.retries_left);
+
+  ::DBus::Variant variant;
+  ::DBus::MessageIter writer = variant.writer();
+
+  // Unlock retries changed, but the SIM wasn't locked.
+  CellularCapabilityUniversal::LockRetryData retry_data;
+  retry_data[MM_MODEM_LOCK_SIM_PIN] = 3;
+  writer << retry_data;
+  changed[MM_MODEM_PROPERTY_UNLOCKRETRIES] = variant;
+
+  capability_->OnModemPropertiesChanged(changed, invalidated);
+  EXPECT_EQ(MM_MODEM_LOCK_UNKNOWN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(3, capability_->sim_lock_status_.retries_left);
+
+  // Unlock retries changed and the SIM got locked.
+  variant.clear();
+  writer = variant.writer();
+  writer << static_cast<uint32_t>(MM_MODEM_LOCK_SIM_PIN);
+  changed[MM_MODEM_PROPERTY_UNLOCKREQUIRED] = variant;
+  capability_->OnModemPropertiesChanged(changed, invalidated);
+  EXPECT_EQ(MM_MODEM_LOCK_SIM_PIN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(3, capability_->sim_lock_status_.retries_left);
+
+  // Only unlock retries changed.
+  changed.erase(MM_MODEM_PROPERTY_UNLOCKREQUIRED);
+  retry_data[MM_MODEM_LOCK_SIM_PIN] = 2;
+  variant.clear();
+  writer = variant.writer();
+  writer << retry_data;
+  changed[MM_MODEM_PROPERTY_UNLOCKRETRIES] = variant;
+  capability_->OnModemPropertiesChanged(changed, invalidated);
+  EXPECT_EQ(MM_MODEM_LOCK_SIM_PIN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(2, capability_->sim_lock_status_.retries_left);
+
+  // Unlock retries changed with a value that doesn't match the current
+  // lock type. Default to whatever count is available.
+  retry_data.clear();
+  retry_data[MM_MODEM_LOCK_SIM_PIN2] = 2;
+  variant.clear();
+  writer = variant.writer();
+  writer << retry_data;
+  changed[MM_MODEM_PROPERTY_UNLOCKRETRIES] = variant;
+  capability_->OnModemPropertiesChanged(changed, invalidated);
+  EXPECT_EQ(MM_MODEM_LOCK_SIM_PIN, capability_->sim_lock_status_.lock_type);
+  EXPECT_EQ(2, capability_->sim_lock_status_.retries_left);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_error.cc b/cellular/cellular_error.cc
new file mode 100644
index 0000000..ffa44b2
--- /dev/null
+++ b/cellular/cellular_error.cc
@@ -0,0 +1,59 @@
+// 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/cellular_error.h"
+
+#include <string>
+
+#include <mm/mm-modem.h>
+
+using std::string;
+
+#define MM_MODEM_ERROR(error)  MM_MODEM_INTERFACE "." error
+#define MM_MOBILE_ERROR(error) MM_MODEM_GSM_INTERFACE "." error
+
+namespace shill {
+
+static const char *kErrorIncorrectPassword =
+    MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_INCORRECTPASSWORD);
+static const char *kErrorSimPinRequired =
+    MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_SIMPINREQUIRED);
+static const char *kErrorSimPukRequired =
+    MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_SIMPUKREQUIRED);
+static const char *kErrorGprsNotSubscribed =
+    MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_GPRSNOTSUBSCRIBED);
+
+// static
+void CellularError::FromDBusError(const DBus::Error &dbus_error,
+                                  Error *error) {
+  if (!error)
+    return;
+
+  if (!dbus_error.is_set()) {
+    error->Reset();
+    return;
+  }
+
+  string name(dbus_error.name());
+  const char *msg = dbus_error.message();
+  Error::Type type;
+
+  if (name == kErrorIncorrectPassword)
+    type = Error::kIncorrectPin;
+  else if (name == kErrorSimPinRequired)
+    type = Error::kPinRequired;
+  else if (name == kErrorSimPukRequired)
+    type = Error::kPinBlocked;
+  else if (name == kErrorGprsNotSubscribed)
+    type = Error::kInvalidApn;
+  else
+    type = Error::kOperationFailed;
+
+  if (msg)
+    return error->Populate(type, msg);
+  else
+    return error->Populate(type);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_error.h b/cellular/cellular_error.h
new file mode 100644
index 0000000..12a7554
--- /dev/null
+++ b/cellular/cellular_error.h
@@ -0,0 +1,26 @@
+// 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_CELLULAR_ERROR_H_
+#define SHILL_CELLULAR_CELLULAR_ERROR_H_
+
+#include <dbus-c++/error.h>
+
+#include "shill/error.h"
+
+namespace shill {
+
+class CellularError {
+ public:
+  static void FromDBusError(const DBus::Error &dbus_error, Error *error);
+
+  static void FromMM1DBusError(const DBus::Error &dbus_error, Error *error);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CellularError);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_ERROR_H_
diff --git a/cellular/cellular_error_mm1.cc b/cellular/cellular_error_mm1.cc
new file mode 100644
index 0000000..ef8faf8
--- /dev/null
+++ b/cellular/cellular_error_mm1.cc
@@ -0,0 +1,76 @@
+// 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/cellular_error.h"
+
+#include <string>
+
+#include <ModemManager/ModemManager.h>
+
+// TODO(armansito): Once we refactor the code to handle the ModemManager D-Bus
+// bindings in a dedicated class, this code should move there.
+// (See crbug.com/246425)
+
+using std::string;
+
+namespace shill {
+
+namespace {
+
+const char *kErrorGprsMissingOrUnknownApn =
+    MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX ".GprsMissingOrUnknownApn";
+
+const char *kErrorGprsServiceOptionNotSubscribed =
+    MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX ".GprsServiceOptionNotSubscribed";
+
+const char *kErrorIncorrectPassword =
+    MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX ".IncorrectPassword";
+
+const char *kErrorSimPin =
+    MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX ".SimPin";
+
+const char *kErrorSimPuk =
+    MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX ".SimPuk";
+
+const char* kErrorWrongState = MM_CORE_ERROR_DBUS_PREFIX ".WrongState";
+
+}  // namespace
+
+// static
+void CellularError::FromMM1DBusError(const DBus::Error &dbus_error,
+                                     Error *error) {
+  if (!error)
+    return;
+
+  if (!dbus_error.is_set()) {
+    error->Reset();
+    return;
+  }
+
+  string name(dbus_error.name());
+  const char *msg = dbus_error.message();
+  Error::Type type;
+
+  if (name == kErrorIncorrectPassword)
+    type = Error::kIncorrectPin;
+  else if (name == kErrorSimPin)
+    type = Error::kPinRequired;
+  else if (name == kErrorSimPuk)
+    type = Error::kPinBlocked;
+  else if (name == kErrorGprsMissingOrUnknownApn)
+    type = Error::kInvalidApn;
+  else if (name == kErrorGprsServiceOptionNotSubscribed)
+    type = Error::kInvalidApn;
+  else if (name == kErrorWrongState)
+    type = Error::kWrongState;
+  else
+    type = Error::kOperationFailed;
+
+  if (msg)
+    return error->Populate(type, msg);
+  else
+    return error->Populate(type);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_error_unittest.cc b/cellular/cellular_error_unittest.cc
new file mode 100644
index 0000000..ab15362
--- /dev/null
+++ b/cellular/cellular_error_unittest.cc
@@ -0,0 +1,136 @@
+// 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/cellular_error.h"
+
+#include <dbus-c++/error.h>
+#include <gtest/gtest.h>
+
+namespace shill {
+
+class CellularErrorTest : public testing::Test {
+};
+
+namespace {
+
+const char kErrorIncorrectPasswordMM[] =
+    "org.freedesktop.ModemManager.Modem.Gsm.IncorrectPassword";
+
+const char kErrorSimPinRequiredMM[] =
+    "org.freedesktop.ModemManager.Modem.Gsm.SimPinRequired";
+
+const char kErrorSimPukRequiredMM[] =
+    "org.freedesktop.ModemManager.Modem.Gsm.SimPukRequired";
+
+const char kErrorGprsNotSubscribedMM[] =
+    "org.freedesktop.ModemManager.Modem.Gsm.GprsNotSubscribed";
+
+const char kErrorIncorrectPasswordMM1[] =
+    "org.freedesktop.ModemManager1.Error.MobileEquipment.IncorrectPassword";
+
+const char kErrorSimPinMM1[] =
+    "org.freedesktop.ModemManager1.Error.MobileEquipment.SimPin";
+
+const char kErrorSimPukMM1[] =
+    "org.freedesktop.ModemManager1.Error.MobileEquipment.SimPuk";
+
+const char kErrorGprsNotSubscribedMM1[] =
+    "org.freedesktop.ModemManager1.Error.MobileEquipment."
+    "GprsServiceOptionNotSubscribed";
+
+const char kErrorWrongStateMM1[] =
+    "org.freedesktop.ModemManager1.Error.Core.WrongState";
+
+
+const char kErrorMessage[] = "Some error message.";
+
+}  // namespace
+
+TEST_F(CellularErrorTest, FromDBusError) {
+  Error shill_error;
+
+  CellularError::FromDBusError(DBus::Error(), nullptr);
+  EXPECT_TRUE(shill_error.IsSuccess());
+
+  CellularError::FromDBusError(DBus::Error(), &shill_error);
+  EXPECT_TRUE(shill_error.IsSuccess());
+
+  CellularError::FromDBusError(
+      DBus::Error(kErrorIncorrectPasswordMM, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kIncorrectPin, shill_error.type());
+
+  CellularError::FromDBusError(
+      DBus::Error(kErrorSimPinRequiredMM, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kPinRequired, shill_error.type());
+
+  CellularError::FromDBusError(
+      DBus::Error(kErrorSimPukRequiredMM, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kPinBlocked, shill_error.type());
+
+  CellularError::FromDBusError(
+      DBus::Error(kErrorGprsNotSubscribedMM, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kInvalidApn, shill_error.type());
+
+  CellularError::FromDBusError(
+      DBus::Error(kErrorIncorrectPasswordMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kOperationFailed, shill_error.type());
+
+  CellularError::FromDBusError(
+      DBus::Error("Some random error name.", kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kOperationFailed, shill_error.type());
+}
+
+TEST_F(CellularErrorTest, FromMM1DBusError) {
+  Error shill_error;
+
+  CellularError::FromDBusError(DBus::Error(), nullptr);
+  EXPECT_TRUE(shill_error.IsSuccess());
+
+  CellularError::FromMM1DBusError(DBus::Error(), &shill_error);
+  EXPECT_TRUE(shill_error.IsSuccess());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorIncorrectPasswordMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kIncorrectPin, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorSimPinMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kPinRequired, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorSimPukMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kPinBlocked, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorGprsNotSubscribedMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kInvalidApn, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorWrongStateMM1, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kWrongState, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error(kErrorIncorrectPasswordMM, kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kOperationFailed, shill_error.type());
+
+  CellularError::FromMM1DBusError(
+      DBus::Error("Some random error name.", kErrorMessage),
+      &shill_error);
+  EXPECT_EQ(Error::kOperationFailed, shill_error.type());
+}
+
+}  // namespace shill
+
diff --git a/cellular/cellular_service.cc b/cellular/cellular_service.cc
new file mode 100644
index 0000000..32e835b
--- /dev/null
+++ b/cellular/cellular_service.cc
@@ -0,0 +1,456 @@
+// 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/cellular_service.h"
+
+#include <string>
+
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular/cellular.h"
+#include "shill/property_accessor.h"
+#include "shill/store_interface.h"
+
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularService *c) { return c->GetRpcIdentifier(); }
+}
+
+// statics
+const char CellularService::kAutoConnActivating[] = "activating";
+const char CellularService::kAutoConnBadPPPCredentials[] =
+    "bad PPP credentials";
+const char CellularService::kAutoConnDeviceDisabled[] = "device disabled";
+const char CellularService::kAutoConnOutOfCredits[] = "device out of credits";
+const char CellularService::kAutoConnOutOfCreditsDetectionInProgress[] =
+    "device detecting out-of-credits";
+const char CellularService::kStoragePPPUsername[] = "Cellular.PPP.Username";
+const char CellularService::kStoragePPPPassword[] = "Cellular.PPP.Password";
+
+// TODO(petkov): Add these to system_api/dbus/service_constants.h
+namespace {
+const char kCellularPPPUsernameProperty[] = "Cellular.PPP.Username";
+const char kCellularPPPPasswordProperty[] = "Cellular.PPP.Password";
+}  // namespace
+
+namespace {
+const char kStorageAPN[] = "Cellular.APN";
+const char kStorageLastGoodAPN[] = "Cellular.LastGoodAPN";
+}  // namespace
+
+static bool GetNonEmptyField(const Stringmap &stringmap,
+                             const string &fieldname,
+                             string *value) {
+  Stringmap::const_iterator it = stringmap.find(fieldname);
+  if (it != stringmap.end() && !it->second.empty()) {
+    *value = it->second;
+    return true;
+  }
+  return false;
+}
+
+CellularService::CellularService(ModemInfo *modem_info,
+                                 const CellularRefPtr &device)
+    : Service(modem_info->control_interface(), modem_info->dispatcher(),
+              modem_info->metrics(), modem_info->manager(),
+              Technology::kCellular),
+      weak_ptr_factory_(this),
+      activation_type_(kActivationTypeUnknown),
+      cellular_(device),
+      is_auto_connecting_(false) {
+  SetConnectable(true);
+  PropertyStore *store = this->mutable_store();
+  HelpRegisterDerivedString(kActivationTypeProperty,
+                            &CellularService::CalculateActivationType,
+                            nullptr);
+  store->RegisterConstString(kActivationStateProperty, &activation_state_);
+  HelpRegisterDerivedStringmap(kCellularApnProperty,
+                               &CellularService::GetApn,
+                               &CellularService::SetApn);
+  store->RegisterConstStringmap(kCellularLastGoodApnProperty,
+                                &last_good_apn_info_);
+  store->RegisterConstString(kNetworkTechnologyProperty, &network_technology_);
+  HelpRegisterDerivedBool(kOutOfCreditsProperty,
+                          &CellularService::IsOutOfCredits,
+                          nullptr);
+  store->RegisterConstStringmap(kPaymentPortalProperty, &olp_);
+  store->RegisterConstString(kRoamingStateProperty, &roaming_state_);
+  store->RegisterConstStringmap(kServingOperatorProperty, &serving_operator_);
+  store->RegisterConstString(kUsageURLProperty, &usage_url_);
+  store->RegisterString(kCellularPPPUsernameProperty, &ppp_username_);
+  store->RegisterWriteOnlyString(kCellularPPPPasswordProperty, &ppp_password_);
+
+  set_friendly_name(cellular_->CreateDefaultFriendlyServiceName());
+  SetStorageIdentifier(string(kTypeCellular) + "_" +
+                       cellular_->address() + "_" + friendly_name());
+  // Assume we are not performing any out-of-credits detection.
+  // The capability can reinitialize with the appropriate type later.
+  InitOutOfCreditsDetection(OutOfCreditsDetector::OOCTypeNone);
+}
+
+CellularService::~CellularService() { }
+
+bool CellularService::IsAutoConnectable(const char **reason) const {
+  if (!cellular_->running()) {
+    *reason = kAutoConnDeviceDisabled;
+    return false;
+  }
+  if (cellular_->IsActivating()) {
+    *reason = kAutoConnActivating;
+    return false;
+  }
+  if (failure() == kFailurePPPAuth) {
+    *reason = kAutoConnBadPPPCredentials;
+    return false;
+  }
+  if (out_of_credits_detector_->IsDetecting()) {
+    *reason = kAutoConnOutOfCreditsDetectionInProgress;
+    return false;
+  }
+  if (out_of_credits_detector_->out_of_credits()) {
+    *reason = kAutoConnOutOfCredits;
+    return false;
+  }
+  return Service::IsAutoConnectable(reason);
+}
+
+void CellularService::HelpRegisterDerivedString(
+    const string &name,
+    string(CellularService::*get)(Error *error),
+    bool(CellularService::*set)(const string &value, Error *error)) {
+  mutable_store()->RegisterDerivedString(
+      name,
+      StringAccessor(
+          new CustomAccessor<CellularService, string>(this, get, set)));
+}
+
+void CellularService::HelpRegisterDerivedStringmap(
+    const string &name,
+    Stringmap(CellularService::*get)(Error *error),
+    bool(CellularService::*set)(
+        const Stringmap &value, Error *error)) {
+  mutable_store()->RegisterDerivedStringmap(
+      name,
+      StringmapAccessor(
+          new CustomAccessor<CellularService, Stringmap>(this, get, set)));
+}
+
+void CellularService::HelpRegisterDerivedBool(
+    const string &name,
+    bool(CellularService::*get)(Error *error),
+    bool(CellularService::*set)(const bool&, Error *)) {
+  mutable_store()->RegisterDerivedBool(
+    name,
+    BoolAccessor(new CustomAccessor<CellularService, bool>(this, get, set)));
+}
+
+Stringmap *CellularService::GetUserSpecifiedApn() {
+  Stringmap::iterator it = apn_info_.find(kApnProperty);
+  if (it == apn_info_.end() || it->second.empty())
+    return nullptr;
+  return &apn_info_;
+}
+
+Stringmap *CellularService::GetLastGoodApn() {
+  Stringmap::iterator it = last_good_apn_info_.find(kApnProperty);
+  if (it == last_good_apn_info_.end() || it->second.empty())
+    return nullptr;
+  return &last_good_apn_info_;
+}
+
+string CellularService::CalculateActivationType(Error *error) {
+  return GetActivationTypeString();
+}
+
+Stringmap CellularService::GetApn(Error */*error*/) {
+  return apn_info_;
+}
+
+bool CellularService::SetApn(const Stringmap &value, Error *error) {
+  // Only copy in the fields we care about, and validate the contents.
+  // If the "apn" field is missing or empty, the APN is cleared.
+  string str;
+  Stringmap new_apn_info;
+  if (GetNonEmptyField(value, kApnProperty, &str)) {
+    new_apn_info[kApnProperty] = str;
+    if (GetNonEmptyField(value, kApnUsernameProperty, &str))
+      new_apn_info[kApnUsernameProperty] = str;
+    if (GetNonEmptyField(value, kApnPasswordProperty, &str))
+      new_apn_info[kApnPasswordProperty] = str;
+  }
+  if (apn_info_ == new_apn_info) {
+    return false;
+  }
+  apn_info_ = new_apn_info;
+  if (ContainsKey(apn_info_, kApnProperty)) {
+    // Clear the last good APN, otherwise the one the user just
+    // set won't be used, since LastGoodApn comes first in the
+    // search order when trying to connect. Only do this if a
+    // non-empty user APN has been supplied. If the user APN is
+    // being cleared, leave LastGoodApn alone.
+    ClearLastGoodApn();
+  }
+  adaptor()->EmitStringmapChanged(kCellularApnProperty, apn_info_);
+  return true;
+}
+
+void CellularService::SetLastGoodApn(const Stringmap &apn_info) {
+  last_good_apn_info_ = apn_info;
+  adaptor()->EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                  last_good_apn_info_);
+}
+
+void CellularService::ClearLastGoodApn() {
+  last_good_apn_info_.clear();
+  adaptor()->EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                  last_good_apn_info_);
+}
+
+void CellularService::OnAfterResume() {
+  Service::OnAfterResume();
+  resume_start_time_ = base::Time::Now();
+}
+
+void CellularService::InitOutOfCreditsDetection(
+    OutOfCreditsDetector::OOCType ooc_type) {
+  out_of_credits_detector_.reset(
+      OutOfCreditsDetector::CreateDetector(ooc_type,
+                                           dispatcher(),
+                                           manager(),
+                                           metrics(),
+                                           this));
+}
+
+bool CellularService::Load(StoreInterface *storage) {
+  // Load properties common to all Services.
+  if (!Service::Load(storage))
+    return false;
+
+  const string id = GetStorageIdentifier();
+  LoadApn(storage, id, kStorageAPN, &apn_info_);
+  LoadApn(storage, id, kStorageLastGoodAPN, &last_good_apn_info_);
+
+  const string old_username = ppp_username_;
+  const string old_password = ppp_password_;
+  storage->GetString(id, kStoragePPPUsername, &ppp_username_);
+  storage->GetString(id, kStoragePPPPassword, &ppp_password_);
+  if (IsFailed() && failure() == kFailurePPPAuth &&
+      (old_username != ppp_username_ || old_password != ppp_password_)) {
+    SetState(kStateIdle);
+  }
+  return true;
+}
+
+void CellularService::LoadApn(StoreInterface *storage,
+                              const string &storage_group,
+                              const string &keytag,
+                              Stringmap *apn_info) {
+  if (!LoadApnField(storage, storage_group, keytag, kApnProperty, apn_info))
+    return;
+  LoadApnField(storage, storage_group, keytag, kApnUsernameProperty, apn_info);
+  LoadApnField(storage, storage_group, keytag, kApnPasswordProperty, apn_info);
+}
+
+bool CellularService::LoadApnField(StoreInterface *storage,
+                                   const string &storage_group,
+                                   const string &keytag,
+                                   const string &apntag,
+                                   Stringmap *apn_info) {
+  string value;
+  if (storage->GetString(storage_group, keytag + "." + apntag, &value) &&
+      !value.empty()) {
+    (*apn_info)[apntag] = value;
+    return true;
+  }
+  return false;
+}
+
+bool CellularService::Save(StoreInterface *storage) {
+  // Save properties common to all Services.
+  if (!Service::Save(storage))
+    return false;
+
+  const string id = GetStorageIdentifier();
+  SaveApn(storage, id, GetUserSpecifiedApn(), kStorageAPN);
+  SaveApn(storage, id, GetLastGoodApn(), kStorageLastGoodAPN);
+  SaveString(storage, id, kStoragePPPUsername, ppp_username_, false, true);
+  SaveString(storage, id, kStoragePPPPassword, ppp_password_, false, true);
+  return true;
+}
+
+void CellularService::SaveApn(StoreInterface *storage,
+                              const string &storage_group,
+                              const Stringmap *apn_info,
+                              const string &keytag) {
+  SaveApnField(storage, storage_group, apn_info, keytag, kApnProperty);
+  SaveApnField(storage, storage_group, apn_info, keytag, kApnUsernameProperty);
+  SaveApnField(storage, storage_group, apn_info, keytag, kApnPasswordProperty);
+}
+
+void CellularService::SaveApnField(StoreInterface *storage,
+                                   const string &storage_group,
+                                   const Stringmap *apn_info,
+                                   const string &keytag,
+                                   const string &apntag) {
+  const string key = keytag + "." + apntag;
+  string str;
+  if (apn_info && GetNonEmptyField(*apn_info, apntag, &str))
+    storage->SetString(storage_group, key, str);
+  else
+    storage->DeleteKey(storage_group, key);
+}
+
+bool CellularService::IsOutOfCredits(Error */*error*/) {
+  return out_of_credits_detector_->out_of_credits();
+}
+
+void CellularService::set_out_of_credits_detector(
+    OutOfCreditsDetector *detector) {
+  out_of_credits_detector_.reset(detector);
+}
+
+void CellularService::SignalOutOfCreditsChanged(bool state) const {
+  adaptor()->EmitBoolChanged(kOutOfCreditsProperty, state);
+}
+
+void CellularService::AutoConnect() {
+  is_auto_connecting_ = true;
+  Service::AutoConnect();
+  is_auto_connecting_ = false;
+}
+
+void CellularService::Connect(Error *error, const char *reason) {
+  Service::Connect(error, reason);
+  cellular_->Connect(error);
+  if (error->IsFailure())
+    out_of_credits_detector_->ResetDetector();
+}
+
+void CellularService::Disconnect(Error *error, const char *reason) {
+  Service::Disconnect(error, reason);
+  cellular_->Disconnect(error, reason);
+}
+
+void CellularService::ActivateCellularModem(const string &carrier,
+                                            Error *error,
+                                            const ResultCallback &callback) {
+  cellular_->Activate(carrier, error, callback);
+}
+
+void CellularService::CompleteCellularActivation(Error *error) {
+  cellular_->CompleteActivation(error);
+}
+
+void CellularService::SetState(ConnectState new_state) {
+  out_of_credits_detector_->NotifyServiceStateChanged(state(), new_state);
+  Service::SetState(new_state);
+}
+
+void CellularService::SetStorageIdentifier(const string &identifier) {
+  SLOG(this, 3) << __func__ << ": " << identifier;
+  storage_identifier_ = identifier;
+  std::replace_if(storage_identifier_.begin(),
+                  storage_identifier_.end(),
+                  &Service::IllegalChar, '_');
+}
+
+string CellularService::GetStorageIdentifier() const {
+  return storage_identifier_;
+}
+
+string CellularService::GetDeviceRpcId(Error */*error*/) const {
+  return cellular_->GetRpcIdentifier();
+}
+
+void CellularService::SetActivationType(ActivationType type) {
+  if (type == activation_type_) {
+    return;
+  }
+  activation_type_ = type;
+  adaptor()->EmitStringChanged(kActivationTypeProperty,
+                               GetActivationTypeString());
+}
+
+string CellularService::GetActivationTypeString() const {
+  switch (activation_type_) {
+    case kActivationTypeNonCellular:
+      return shill::kActivationTypeNonCellular;
+    case kActivationTypeOMADM:
+      return shill::kActivationTypeOMADM;
+    case kActivationTypeOTA:
+      return shill::kActivationTypeOTA;
+    case kActivationTypeOTASP:
+      return shill::kActivationTypeOTASP;
+    case kActivationTypeUnknown:
+      return "";
+    default:
+      NOTREACHED();
+      return "";  // Make compiler happy.
+  }
+}
+
+void CellularService::SetActivationState(const string &state) {
+  if (state == activation_state_) {
+    return;
+  }
+  activation_state_ = state;
+  adaptor()->EmitStringChanged(kActivationStateProperty, state);
+  SetConnectableFull(state != kActivationStateNotActivated);
+}
+
+void CellularService::SetOLP(const string &url,
+                             const string &method,
+                             const string &post_data) {
+  Stringmap olp;
+  olp[kPaymentPortalURL] = url;
+  olp[kPaymentPortalMethod] = method;
+  olp[kPaymentPortalPostData] = post_data;
+
+  if (olp_ == olp) {
+    return;
+  }
+  olp_ = olp;
+  adaptor()->EmitStringmapChanged(kPaymentPortalProperty, olp);
+}
+
+void CellularService::SetUsageURL(const string &url) {
+  if (url == usage_url_) {
+    return;
+  }
+  usage_url_ = url;
+  adaptor()->EmitStringChanged(kUsageURLProperty, url);
+}
+
+void CellularService::SetNetworkTechnology(const string &technology) {
+  if (technology == network_technology_) {
+    return;
+  }
+  network_technology_ = technology;
+  adaptor()->EmitStringChanged(kNetworkTechnologyProperty,
+                               technology);
+}
+
+void CellularService::SetRoamingState(const string &state) {
+  if (state == roaming_state_) {
+    return;
+  }
+  roaming_state_ = state;
+  adaptor()->EmitStringChanged(kRoamingStateProperty, state);
+}
+
+void CellularService::set_serving_operator(const Stringmap &serving_operator) {
+  if (serving_operator_ == serving_operator)
+    return;
+
+  serving_operator_ = serving_operator;
+  adaptor()->EmitStringmapChanged(kServingOperatorProperty, serving_operator_);
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_service.h b/cellular/cellular_service.h
new file mode 100644
index 0000000..1e2a5a0
--- /dev/null
+++ b/cellular/cellular_service.h
@@ -0,0 +1,233 @@
+// 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_CELLULAR_SERVICE_H_
+#define SHILL_CELLULAR_CELLULAR_SERVICE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/out_of_credits_detector.h"
+#include "shill/refptr_types.h"
+#include "shill/service.h"
+
+namespace shill {
+
+class ControlInterface;
+class Error;
+class EventDispatcher;
+class Manager;
+class OutOfCreditsDetector;
+
+class CellularService : public Service {
+ public:
+  enum ActivationType {
+    kActivationTypeNonCellular,  // For future use
+    kActivationTypeOMADM,  // For future use
+    kActivationTypeOTA,
+    kActivationTypeOTASP,
+    kActivationTypeUnknown
+  };
+
+  CellularService(ModemInfo *modem_info,
+                  const CellularRefPtr &device);
+  ~CellularService() override;
+
+  // Inherited from Service.
+  virtual void AutoConnect();
+  virtual void Connect(Error *error, const char *reason);
+  virtual void Disconnect(Error *error, const char *reason);
+  virtual void ActivateCellularModem(const std::string &carrier,
+                                     Error *error,
+                                     const ResultCallback &callback);
+  virtual void CompleteCellularActivation(Error *error);
+  virtual void SetState(ConnectState new_state);
+
+  virtual std::string GetStorageIdentifier() const;
+  void SetStorageIdentifier(const std::string &identifier);
+
+  const CellularRefPtr &cellular() const { return cellular_; }
+
+  void SetActivationType(ActivationType type);
+  std::string GetActivationTypeString() const;
+
+  virtual void SetActivationState(const std::string &state);
+  virtual const std::string &activation_state() const {
+      return activation_state_;
+  }
+
+  void SetOLP(const std::string &url,
+              const std::string &method,
+              const std::string &post_data);
+  const Stringmap &olp() const { return olp_; }
+
+  void SetUsageURL(const std::string &url);
+  const std::string &usage_url() const { return usage_url_; }
+
+  void set_serving_operator(const Stringmap &serving_operator);
+  const Stringmap &serving_operator() const { return serving_operator_; }
+
+  // Sets network technology to |technology| and broadcasts the property change.
+  void SetNetworkTechnology(const std::string &technology);
+  const std::string &network_technology() const { return network_technology_; }
+
+  // Sets roaming state to |state| and broadcasts the property change.
+  void SetRoamingState(const std::string &state);
+  const std::string &roaming_state() const { return roaming_state_; }
+
+  bool is_auto_connecting() const {
+    return is_auto_connecting_;
+  }
+
+  const std::string &ppp_username() const { return ppp_username_; }
+  const std::string &ppp_password() const { return ppp_password_; }
+
+  virtual const base::Time &resume_start_time() const {
+    return resume_start_time_;
+  }
+
+  OutOfCreditsDetector *out_of_credits_detector() {
+    return out_of_credits_detector_.get();
+  }
+  void SignalOutOfCreditsChanged(bool state) const;
+
+  // Overrides Load and Save from parent Service class.  We will call
+  // the parent method.
+  virtual bool Load(StoreInterface *storage);
+  virtual bool Save(StoreInterface *storage);
+
+  Stringmap *GetUserSpecifiedApn();
+  Stringmap *GetLastGoodApn();
+  virtual void SetLastGoodApn(const Stringmap &apn_info);
+  virtual void ClearLastGoodApn();
+
+  virtual void OnAfterResume();
+
+  // Initialize out-of-credits detection.
+  void InitOutOfCreditsDetection(OutOfCreditsDetector::OOCType ooc_type);
+
+ protected:
+  // Overrides IsAutoConnectable from parent Service class.
+  virtual bool IsAutoConnectable(const char **reason) const;
+
+ private:
+  friend class CellularCapabilityUniversalTest;
+  friend class CellularServiceTest;
+  FRIEND_TEST(CellularCapabilityGSMTest, SetupApnTryList);
+  FRIEND_TEST(CellularCapabilityTest, TryApns);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest,
+              UpdatePendingActivationState);
+  FRIEND_TEST(CellularCapabilityUniversalMainTest, UpdateServiceName);
+  FRIEND_TEST(CellularTest, Connect);
+  FRIEND_TEST(CellularTest, GetLogin);  // ppp_username_, ppp_password_
+  FRIEND_TEST(CellularTest, OnConnectionHealthCheckerResult);
+  FRIEND_TEST(CellularServiceTest, SetApn);
+  FRIEND_TEST(CellularServiceTest, ClearApn);
+  FRIEND_TEST(CellularServiceTest, LastGoodApn);
+  FRIEND_TEST(CellularServiceTest, LoadResetsPPPAuthFailure);
+  FRIEND_TEST(CellularServiceTest, IsAutoConnectable);
+  FRIEND_TEST(CellularServiceTest, OutOfCreditsDetected);
+  FRIEND_TEST(CellularServiceTest,
+              OutOfCreditsDetectionNotSkippedAfterSlowResume);
+  FRIEND_TEST(CellularServiceTest, OutOfCreditsDetectionSkippedAfterResume);
+  FRIEND_TEST(CellularServiceTest,
+              OutOfCreditsDetectionSkippedAlreadyOutOfCredits);
+  FRIEND_TEST(CellularServiceTest,
+              OutOfCreditsDetectionSkippedExplicitDisconnect);
+  FRIEND_TEST(CellularServiceTest, OutOfCreditsNotDetectedConnectionNotDropped);
+  FRIEND_TEST(CellularServiceTest, OutOfCreditsNotDetectedIntermittentNetwork);
+  FRIEND_TEST(CellularServiceTest, OutOfCreditsNotEnforced);
+  FRIEND_TEST(CellularServiceTest, CustomSetterNoopChange);
+
+  static const char kAutoConnActivating[];
+  static const char kAutoConnBadPPPCredentials[];
+  static const char kAutoConnDeviceDisabled[];
+  static const char kAutoConnOutOfCredits[];
+  static const char kAutoConnOutOfCreditsDetectionInProgress[];
+  static const char kStoragePPPUsername[];
+  static const char kStoragePPPPassword[];
+
+  void HelpRegisterDerivedString(
+      const std::string &name,
+      std::string(CellularService::*get)(Error *error),
+      bool(CellularService::*set)(const std::string &value, Error *error));
+  void HelpRegisterDerivedStringmap(
+      const std::string &name,
+      Stringmap(CellularService::*get)(Error *error),
+      bool(CellularService::*set)(const Stringmap &value, Error *error));
+  void HelpRegisterDerivedBool(
+      const std::string &name,
+      bool(CellularService::*get)(Error *error),
+      bool(CellularService::*set)(const bool&, Error *));
+
+  virtual std::string GetDeviceRpcId(Error *error) const;
+
+  std::string CalculateActivationType(Error *error);
+
+  Stringmap GetApn(Error *error);
+  bool SetApn(const Stringmap &value, Error *error);
+  static void SaveApn(StoreInterface *storage,
+                      const std::string &storage_group,
+                      const Stringmap *apn_info,
+                      const std::string &keytag);
+  static void SaveApnField(StoreInterface *storage,
+                           const std::string &storage_group,
+                           const Stringmap *apn_info,
+                           const std::string &keytag,
+                           const std::string &apntag);
+  static void LoadApn(StoreInterface *storage,
+                      const std::string &storage_group,
+                      const std::string &keytag,
+                      Stringmap *apn_info);
+  static bool LoadApnField(StoreInterface *storage,
+                           const std::string &storage_group,
+                           const std::string &keytag,
+                           const std::string &apntag,
+                           Stringmap *apn_info);
+  bool IsOutOfCredits(Error */*error*/);
+
+  // For unit test.
+  void set_out_of_credits_detector(OutOfCreditsDetector *detector);
+
+  base::WeakPtrFactory<CellularService> weak_ptr_factory_;
+
+  // Properties
+  ActivationType activation_type_;
+  std::string activation_state_;
+  Stringmap serving_operator_;
+  std::string network_technology_;
+  std::string roaming_state_;
+  Stringmap olp_;
+  std::string usage_url_;
+  Stringmap apn_info_;
+  Stringmap last_good_apn_info_;
+  std::string ppp_username_;
+  std::string ppp_password_;
+
+  std::string storage_identifier_;
+
+  CellularRefPtr cellular_;
+
+  // Flag indicating that a connect request is an auto-connect request.
+  // Note: Since Connect() is asynchronous, this flag is only set during the
+  // call to Connect().  It does not remain set while the async request is
+  // in flight.
+  bool is_auto_connecting_;
+  // Time when the last resume occurred.
+  base::Time resume_start_time_;
+  // Out-of-credits detector.
+  std::unique_ptr<OutOfCreditsDetector> out_of_credits_detector_;
+
+  DISALLOW_COPY_AND_ASSIGN(CellularService);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_CELLULAR_SERVICE_H_
diff --git a/cellular/cellular_service_unittest.cc b/cellular/cellular_service_unittest.cc
new file mode 100644
index 0000000..da40905
--- /dev/null
+++ b/cellular/cellular_service_unittest.cc
@@ -0,0 +1,525 @@
+// 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/cellular_service.h"
+
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/cellular_capability_cdma.h"
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_out_of_credits_detector.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+#include "shill/mock_profile.h"
+#include "shill/mock_store.h"
+#include "shill/nice_mock_control.h"
+#include "shill/proxy_factory.h"
+#include "shill/service_property_change_test.h"
+
+using std::string;
+using testing::_;
+using testing::InSequence;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+
+namespace shill {
+
+class CellularServiceTest : public testing::Test {
+ public:
+  CellularServiceTest()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        device_(new MockCellular(&modem_info_,
+                                 "usb0",
+                                 kAddress,
+                                 3,
+                                 Cellular::kTypeCDMA,
+                                 "",
+                                 "",
+                                 "",
+                                 ProxyFactory::GetInstance())),
+        service_(new CellularService(&modem_info_, device_)),
+        adaptor_(nullptr) {}
+
+  virtual ~CellularServiceTest() {
+    adaptor_ = nullptr;
+  }
+
+  virtual void SetUp() {
+    adaptor_ =
+        dynamic_cast<ServiceMockAdaptor *>(service_->adaptor());
+    out_of_credits_detector_ =
+        new MockOutOfCreditsDetector(nullptr, nullptr, nullptr, service_);
+    // Passes ownership.
+    service_->set_out_of_credits_detector(out_of_credits_detector_);
+  }
+
+  CellularCapabilityCDMA *GetCapabilityCDMA() {
+    return dynamic_cast<CellularCapabilityCDMA *>(device_->capability_.get());
+  }
+
+ protected:
+  static const char kAddress[];
+
+  string GetFriendlyName() const { return service_->friendly_name(); }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  scoped_refptr<MockCellular> device_;
+  CellularServiceRefPtr service_;
+  ServiceMockAdaptor *adaptor_;  // Owned by |service_|.
+  MockOutOfCreditsDetector *out_of_credits_detector_;  // Owned by |service_|.
+};
+
+const char CellularServiceTest::kAddress[] = "000102030405";
+
+TEST_F(CellularServiceTest, Constructor) {
+  EXPECT_TRUE(service_->connectable());
+}
+
+TEST_F(CellularServiceTest, SetActivationState) {
+  {
+    InSequence call_sequence;
+    EXPECT_CALL(*adaptor_, EmitStringChanged(
+        kActivationStateProperty,
+        kActivationStateNotActivated));
+    EXPECT_CALL(*adaptor_, EmitBoolChanged(
+        kConnectableProperty, false));
+    EXPECT_CALL(*adaptor_, EmitStringChanged(
+        kActivationStateProperty,
+        kActivationStateActivating));
+    EXPECT_CALL(*adaptor_, EmitBoolChanged(
+        kConnectableProperty, true));
+    EXPECT_CALL(*adaptor_, EmitStringChanged(
+        kActivationStateProperty,
+        kActivationStatePartiallyActivated));
+    EXPECT_CALL(*adaptor_, EmitStringChanged(
+        kActivationStateProperty,
+        kActivationStateActivated));
+    EXPECT_CALL(*adaptor_, EmitStringChanged(
+        kActivationStateProperty,
+        kActivationStateNotActivated));
+    EXPECT_CALL(*adaptor_, EmitBoolChanged(
+        kConnectableProperty, false));
+  }
+  EXPECT_CALL(*modem_info_.mock_manager(), HasService(_))
+      .WillRepeatedly(Return(false));
+
+  EXPECT_TRUE(service_->activation_state().empty());
+  EXPECT_TRUE(service_->connectable());
+
+  service_->SetActivationState(kActivationStateNotActivated);
+  EXPECT_EQ(kActivationStateNotActivated, service_->activation_state());
+  EXPECT_FALSE(service_->connectable());
+
+  service_->SetActivationState(kActivationStateActivating);
+  EXPECT_EQ(kActivationStateActivating, service_->activation_state());
+  EXPECT_TRUE(service_->connectable());
+
+  service_->SetActivationState(kActivationStatePartiallyActivated);
+  EXPECT_EQ(kActivationStatePartiallyActivated, service_->activation_state());
+  EXPECT_TRUE(service_->connectable());
+
+  service_->SetActivationState(kActivationStateActivated);
+  EXPECT_EQ(kActivationStateActivated, service_->activation_state());
+  EXPECT_TRUE(service_->connectable());
+
+  service_->SetActivationState(kActivationStateNotActivated);
+  EXPECT_EQ(kActivationStateNotActivated, service_->activation_state());
+  EXPECT_FALSE(service_->connectable());
+}
+
+TEST_F(CellularServiceTest, SetNetworkTechnology) {
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kNetworkTechnologyProperty,
+                                           kNetworkTechnologyUmts));
+  EXPECT_TRUE(service_->network_technology().empty());
+  service_->SetNetworkTechnology(kNetworkTechnologyUmts);
+  EXPECT_EQ(kNetworkTechnologyUmts, service_->network_technology());
+  service_->SetNetworkTechnology(kNetworkTechnologyUmts);
+}
+
+TEST_F(CellularServiceTest, SetRoamingState) {
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kRoamingStateProperty,
+                                           kRoamingStateHome));
+  EXPECT_TRUE(service_->roaming_state().empty());
+  service_->SetRoamingState(kRoamingStateHome);
+  EXPECT_EQ(kRoamingStateHome, service_->roaming_state());
+  service_->SetRoamingState(kRoamingStateHome);
+}
+
+TEST_F(CellularServiceTest, SetStorageIdentifier) {
+  EXPECT_EQ(string(kTypeCellular) + "_" +
+            kAddress + "_" + GetFriendlyName(),
+            service_->GetStorageIdentifier());
+  service_->SetStorageIdentifier("a b c");
+  EXPECT_EQ("a_b_c", service_->GetStorageIdentifier());
+}
+
+TEST_F(CellularServiceTest, SetServingOperator) {
+  static const char kCode[] = "123456";
+  static const char kName[] = "Some Cellular Operator";
+  Stringmap test_operator;
+  service_->set_serving_operator(test_operator);
+  test_operator[kOperatorCodeKey] = kCode;
+  test_operator[kOperatorNameKey] = kName;
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kServingOperatorProperty, _));
+  service_->set_serving_operator(test_operator);
+  const Stringmap &serving_operator = service_->serving_operator();
+  ASSERT_NE(serving_operator.end(), serving_operator.find(kOperatorCodeKey));
+  ASSERT_NE(serving_operator.end(), serving_operator.find(kOperatorNameKey));
+  EXPECT_EQ(kCode, serving_operator.find(kOperatorCodeKey)->second);
+  EXPECT_EQ(kName, serving_operator.find(kOperatorNameKey)->second);
+  Mock::VerifyAndClearExpectations(adaptor_);
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kServingOperatorProperty, _)).Times(0);
+  service_->set_serving_operator(serving_operator);
+}
+
+TEST_F(CellularServiceTest, SetOLP) {
+  const char kMethod[] = "GET";
+  const char kURL[] = "payment.url";
+  const char kPostData[] = "post_man";
+  Stringmap olp;
+
+  service_->SetOLP("", "", "");
+  olp = service_->olp();  // Copy to simplify assertions below.
+  EXPECT_EQ("", olp[kPaymentPortalURL]);
+  EXPECT_EQ("", olp[kPaymentPortalMethod]);
+  EXPECT_EQ("", olp[kPaymentPortalPostData]);
+
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kPaymentPortalProperty, _));
+  service_->SetOLP(kURL, kMethod, kPostData);
+  olp = service_->olp();  // Copy to simplify assertions below.
+  EXPECT_EQ(kURL, olp[kPaymentPortalURL]);
+  EXPECT_EQ(kMethod, olp[kPaymentPortalMethod]);
+  EXPECT_EQ(kPostData, olp[kPaymentPortalPostData]);
+}
+
+TEST_F(CellularServiceTest, SetUsageURL) {
+  static const char kUsageURL[] = "usage.url";
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kUsageURLProperty,
+                                           kUsageURL));
+  EXPECT_TRUE(service_->usage_url().empty());
+  service_->SetUsageURL(kUsageURL);
+  EXPECT_EQ(kUsageURL, service_->usage_url());
+  service_->SetUsageURL(kUsageURL);
+}
+
+TEST_F(CellularServiceTest, SetApn) {
+  static const char kApn[] = "TheAPN";
+  static const char kUsername[] = "commander.data";
+  ProfileRefPtr profile(new NiceMock<MockProfile>(
+      modem_info_.control_interface(), modem_info_.metrics(),
+      modem_info_.manager()));
+  service_->set_profile(profile);
+  Error error;
+  Stringmap testapn;
+  testapn[kApnProperty] = kApn;
+  testapn[kApnUsernameProperty] = kUsername;
+  {
+    InSequence seq;
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                     _));
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularApnProperty, _));
+  }
+  service_->SetApn(testapn, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  Stringmap resultapn = service_->GetApn(&error);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_EQ(2, resultapn.size());
+  Stringmap::const_iterator it = resultapn.find(kApnProperty);
+  EXPECT_TRUE(it != resultapn.end() && it->second == kApn);
+  it = resultapn.find(kApnUsernameProperty);
+  EXPECT_TRUE(it != resultapn.end() && it->second == kUsername);
+  EXPECT_NE(nullptr, service_->GetUserSpecifiedApn());
+}
+
+TEST_F(CellularServiceTest, ClearApn) {
+  static const char kApn[] = "TheAPN";
+  static const char kUsername[] = "commander.data";
+  ProfileRefPtr profile(new NiceMock<MockProfile>(
+      modem_info_.control_interface(), modem_info_.metrics(),
+      modem_info_.manager()));
+  service_->set_profile(profile);
+  Error error;
+  // Set up an APN to make sure that it later gets cleared.
+  Stringmap testapn;
+  testapn[kApnProperty] = kApn;
+  testapn[kApnUsernameProperty] = kUsername;
+  {
+    InSequence seq;
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                     _));
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularApnProperty, _));
+  }
+  service_->SetApn(testapn, &error);
+  Stringmap resultapn = service_->GetApn(&error);
+  ASSERT_TRUE(error.IsSuccess());
+  ASSERT_EQ(2, service_->GetApn(&error).size());
+
+  Stringmap emptyapn;
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                   _)).Times(0);
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kCellularApnProperty, _)).Times(1);
+  service_->SetApn(emptyapn, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  resultapn = service_->GetApn(&error);
+  EXPECT_TRUE(resultapn.empty());
+  EXPECT_EQ(nullptr, service_->GetUserSpecifiedApn());;
+}
+
+TEST_F(CellularServiceTest, LastGoodApn) {
+  static const char kApn[] = "TheAPN";
+  static const char kUsername[] = "commander.data";
+  ProfileRefPtr profile(new NiceMock<MockProfile>(
+      modem_info_.control_interface(), modem_info_.metrics(),
+      modem_info_.manager()));
+  service_->set_profile(profile);
+  Stringmap testapn;
+  testapn[kApnProperty] = kApn;
+  testapn[kApnUsernameProperty] = kUsername;
+  EXPECT_CALL(*adaptor_,
+              EmitStringmapChanged(kCellularLastGoodApnProperty, _));
+  service_->SetLastGoodApn(testapn);
+  Stringmap *resultapn = service_->GetLastGoodApn();
+  EXPECT_NE(nullptr, resultapn);
+  EXPECT_EQ(2, resultapn->size());
+  Stringmap::const_iterator it = resultapn->find(kApnProperty);
+  EXPECT_TRUE(it != resultapn->end() && it->second == kApn);
+  it = resultapn->find(kApnUsernameProperty);
+  EXPECT_TRUE(it != resultapn->end() && it->second == kUsername);
+  // Now set the user-specified APN, and check that LastGoodApn got
+  // cleared.
+  Stringmap userapn;
+  userapn[kApnProperty] = kApn;
+  userapn[kApnUsernameProperty] = kUsername;
+  {
+    InSequence seq;
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularLastGoodApnProperty,
+                                     _));
+    EXPECT_CALL(*adaptor_,
+                EmitStringmapChanged(kCellularApnProperty, _));
+  }
+  Error error;
+  service_->SetApn(userapn, &error);
+  EXPECT_EQ(nullptr, service_->GetLastGoodApn());;
+}
+
+TEST_F(CellularServiceTest, IsAutoConnectable) {
+  const char *reason = nullptr;
+
+  ON_CALL(*out_of_credits_detector_, IsDetecting())
+      .WillByDefault(Return(false));
+
+  // Auto-connect should be suppressed if the device is not running.
+  device_->running_ = false;
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(CellularService::kAutoConnDeviceDisabled, reason);
+
+  device_->running_ = true;
+
+  // If we're waiting on a disconnect before an activation, don't auto-connect.
+  GetCapabilityCDMA()->activation_starting_ = true;
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+
+  // If we're waiting on an activation, also don't auto-connect.
+  GetCapabilityCDMA()->activation_starting_ = false;
+  GetCapabilityCDMA()->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+
+  GetCapabilityCDMA()->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED;
+
+  // Auto-connect should be suppressed if the we're undergoing an
+  // out-of-credits detection.
+  EXPECT_CALL(*out_of_credits_detector_, IsDetecting())
+      .WillOnce(Return(true));
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(CellularService::kAutoConnOutOfCreditsDetectionInProgress,
+               reason);
+  Mock::VerifyAndClearExpectations(out_of_credits_detector_);
+
+  // Auto-connect should be suppressed if we're out of credits.
+  EXPECT_CALL(*out_of_credits_detector_, IsDetecting())
+      .WillOnce(Return(false));
+  EXPECT_CALL(*out_of_credits_detector_, out_of_credits())
+      .WillOnce(Return(true));
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(CellularService::kAutoConnOutOfCredits, reason);
+  Mock::VerifyAndClearExpectations(out_of_credits_detector_);
+
+  EXPECT_CALL(*out_of_credits_detector_, out_of_credits())
+      .WillRepeatedly(Return(false));
+
+  // But other activation states are fine.
+  GetCapabilityCDMA()->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED;
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+  GetCapabilityCDMA()->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED;
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+  GetCapabilityCDMA()->activation_state_ =
+      MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED;
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  // A PPP authentication failure means the Service is not auto-connectable.
+  service_->SetFailure(Service::kFailurePPPAuth);
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+
+  // Reset failure state, to make the Service auto-connectable again.
+  service_->SetState(Service::kStateIdle);
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  // The following test cases are copied from ServiceTest.IsAutoConnectable
+
+  service_->SetConnectable(true);
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  // We should not auto-connect to a Service that a user has
+  // deliberately disconnected.
+  Error error;
+  service_->UserInitiatedDisconnect(&error);
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(Service::kAutoConnExplicitDisconnect, reason);
+
+  // But if the Service is reloaded, it is eligible for auto-connect
+  // again.
+  NiceMock<MockStore> storage;
+  EXPECT_CALL(storage, ContainsGroup(service_->GetStorageIdentifier()))
+      .WillOnce(Return(true));
+  EXPECT_TRUE(service_->Load(&storage));
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  // A non-user initiated Disconnect doesn't change anything.
+  service_->Disconnect(&error, "in test");
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  // A resume also re-enables auto-connect.
+  service_->UserInitiatedDisconnect(&error);
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  service_->OnAfterResume();
+  EXPECT_TRUE(service_->IsAutoConnectable(&reason));
+
+  service_->SetState(Service::kStateConnected);
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(Service::kAutoConnConnected, reason);
+
+  service_->SetState(Service::kStateAssociating);
+  EXPECT_FALSE(service_->IsAutoConnectable(&reason));
+  EXPECT_STREQ(Service::kAutoConnConnecting, reason);
+}
+
+TEST_F(CellularServiceTest, LoadResetsPPPAuthFailure) {
+  NiceMock<MockStore> storage;
+  EXPECT_CALL(storage, ContainsGroup(_)).WillRepeatedly(Return(true));
+  EXPECT_CALL(storage, GetString(_, _, _)).WillRepeatedly(Return(true));
+
+  const string kDefaultUser;
+  const string kDefaultPass;
+  const string kNewUser("new-username");
+  const string kNewPass("new-password");
+  for (const auto change_username : { false, true }) {
+    for (const auto change_password : { false, true }) {
+      service_->ppp_username_ = kDefaultUser;
+      service_->ppp_password_ = kDefaultPass;
+      service_->SetFailure(Service::kFailurePPPAuth);
+      EXPECT_TRUE(service_->IsFailed());
+      EXPECT_EQ(Service::kFailurePPPAuth, service_->failure());
+      if (change_username) {
+        EXPECT_CALL(storage,
+                    GetString(_, CellularService::kStoragePPPUsername, _))
+            .WillOnce(DoAll(SetArgumentPointee<2>(kNewUser), Return(true)))
+            .RetiresOnSaturation();
+      }
+      if (change_password) {
+        EXPECT_CALL(storage,
+                    GetString(_, CellularService::kStoragePPPPassword, _))
+            .WillOnce(DoAll(SetArgumentPointee<2>(kNewPass), Return(true)))
+            .RetiresOnSaturation();
+      }
+      EXPECT_TRUE(service_->Load(&storage));
+      if (change_username || change_password) {
+        EXPECT_NE(Service::kFailurePPPAuth, service_->failure());
+      } else {
+        EXPECT_EQ(Service::kFailurePPPAuth, service_->failure());
+      }
+    }
+  }
+}
+
+// Some of these tests duplicate signals tested above. However, it's
+// convenient to have all the property change notifications documented
+// (and tested) in one place.
+TEST_F(CellularServiceTest, PropertyChanges) {
+  TestCommonPropertyChanges(service_, adaptor_);
+  TestAutoConnectPropertyChange(service_, adaptor_);
+
+  EXPECT_CALL(*adaptor_,
+              EmitStringChanged(kActivationTypeProperty, _));
+  service_->SetActivationType(CellularService::kActivationTypeOTA);
+  Mock::VerifyAndClearExpectations(adaptor_);
+
+  EXPECT_NE(kActivationStateNotActivated, service_->activation_state());
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kActivationStateProperty, _));
+  service_->SetActivationState(kActivationStateNotActivated);
+  Mock::VerifyAndClearExpectations(adaptor_);
+
+  string network_technology = service_->network_technology();
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kNetworkTechnologyProperty, _));
+  service_->SetNetworkTechnology(network_technology + "and some new stuff");
+  Mock::VerifyAndClearExpectations(adaptor_);
+
+  bool out_of_credits = true;
+  EXPECT_CALL(*adaptor_,
+              EmitBoolChanged(kOutOfCreditsProperty, out_of_credits));
+  service_->SignalOutOfCreditsChanged(out_of_credits);
+  Mock::VerifyAndClearExpectations(adaptor_);
+
+  string roaming_state = service_->roaming_state();
+  EXPECT_CALL(*adaptor_, EmitStringChanged(kRoamingStateProperty, _));
+  service_->SetRoamingState(roaming_state + "and some new stuff");
+  Mock::VerifyAndClearExpectations(adaptor_);
+}
+
+// Custom property setters should return false, and make no changes, if
+// the new value is the same as the old value.
+TEST_F(CellularServiceTest, CustomSetterNoopChange) {
+  // Test that we didn't break any setters provided by the base class.
+  TestCustomSetterNoopChange(service_, modem_info_.mock_manager());
+
+  // Test the new setter we added.
+  // First set up our environment...
+  static const char kApn[] = "TheAPN";
+  static const char kUsername[] = "commander.data";
+  Error error;
+  Stringmap testapn;
+  ProfileRefPtr profile(new NiceMock<MockProfile>(nullptr, nullptr, nullptr));
+  service_->set_profile(profile);
+  testapn[kApnProperty] = kApn;
+  testapn[kApnUsernameProperty] = kUsername;
+  // ... then set to a known value ...
+  EXPECT_TRUE(service_->SetApn(testapn, &error));
+  EXPECT_TRUE(error.IsSuccess());
+  // ... then set to same value.
+  EXPECT_FALSE(service_->SetApn(testapn, &error));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+}  // namespace shill
diff --git a/cellular/cellular_unittest.cc b/cellular/cellular_unittest.cc
new file mode 100644
index 0000000..b8cbfaa
--- /dev/null
+++ b/cellular/cellular_unittest.cc
@@ -0,0 +1,2143 @@
+// 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/cellular.h"
+
+#include <sys/socket.h>
+#include <linux/if.h>  // NOLINT - Needs typedefs from sys/socket.h.
+#include <linux/netlink.h>
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular/cellular_bearer.h"
+#include "shill/cellular/cellular_capability_cdma.h"
+#include "shill/cellular/cellular_capability_classic.h"
+#include "shill/cellular/cellular_capability_gsm.h"
+#include "shill/cellular/cellular_capability_universal.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/mock_cellular_service.h"
+#include "shill/cellular/mock_mm1_modem_modem3gpp_proxy.h"
+#include "shill/cellular/mock_mm1_modem_proxy.h"
+#include "shill/cellular/mock_mm1_modem_simple_proxy.h"
+#include "shill/cellular/mock_mobile_operator_info.h"
+#include "shill/cellular/mock_modem_cdma_proxy.h"
+#include "shill/cellular/mock_modem_gsm_card_proxy.h"
+#include "shill/cellular/mock_modem_gsm_network_proxy.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_modem_proxy.h"
+#include "shill/cellular/mock_modem_simple_proxy.h"
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_dhcp_config.h"
+#include "shill/mock_dhcp_provider.h"
+#include "shill/mock_external_task.h"
+#include "shill/mock_ppp_device.h"
+#include "shill/mock_ppp_device_factory.h"
+#include "shill/net/mock_rtnl_handler.h"
+#include "shill/property_store_unittest.h"
+#include "shill/proxy_factory.h"
+#include "shill/rpc_task.h"  // for RpcTaskDelegate
+#include "shill/testing.h"
+
+// mm/mm-modem.h must be included after cellular_capability_universal.h
+// in order to allow MM_MODEM_CDMA_* to be defined properly.
+#include <mm/mm-modem.h>
+
+using base::Bind;
+using base::Unretained;
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::Unused;
+
+namespace shill {
+
+class CellularPropertyTest : public PropertyStoreTest {
+ public:
+  CellularPropertyTest()
+      : modem_info_(control_interface(),
+                    dispatcher(),
+                    metrics(),
+                    manager(),
+                    glib()),
+        device_(new Cellular(&modem_info_,
+                             "usb0",
+                             "00:01:02:03:04:05",
+                             3,
+                             Cellular::kTypeCDMA,
+                             "",
+                             "",
+                             "",
+                             ProxyFactory::GetInstance())) {}
+  virtual ~CellularPropertyTest() {}
+
+ protected:
+  MockModemInfo modem_info_;
+  DeviceRefPtr device_;
+};
+
+TEST_F(CellularPropertyTest, Contains) {
+  EXPECT_TRUE(device_->store().Contains(kNameProperty));
+  EXPECT_FALSE(device_->store().Contains(""));
+}
+
+TEST_F(CellularPropertyTest, SetProperty) {
+  {
+    ::DBus::Error error;
+    ::DBus::Variant allow_roaming;
+    allow_roaming.writer().append_bool(true);
+    EXPECT_TRUE(DBusAdaptor::SetProperty(
+        device_->mutable_store(),
+        kCellularAllowRoamingProperty,
+        allow_roaming,
+        &error));
+  }
+  // Ensure that attempting to write a R/O property returns InvalidArgs error.
+  {
+    ::DBus::Error error;
+    EXPECT_FALSE(DBusAdaptor::SetProperty(device_->mutable_store(),
+                                          kAddressProperty,
+                                          PropertyStoreTest::kStringV,
+                                          &error));
+    ASSERT_TRUE(error.is_set());  // name() may be invalid otherwise
+    EXPECT_EQ(invalid_args(), error.name());
+  }
+  {
+    ::DBus::Error error;
+    EXPECT_FALSE(DBusAdaptor::SetProperty(device_->mutable_store(),
+                                          kCarrierProperty,
+                                          PropertyStoreTest::kStringV,
+                                          &error));
+    ASSERT_TRUE(error.is_set());  // name() may be invalid otherwise
+    EXPECT_EQ(invalid_args(), error.name());
+  }
+}
+
+class CellularTest : public testing::Test {
+ public:
+  CellularTest()
+      : kHomeProviderCode("10001"),
+        kHomeProviderCountry("us"),
+        kHomeProviderName("HomeProviderName"),
+        kServingOperatorCode("10002"),
+        kServingOperatorCountry("ca"),
+        kServingOperatorName("ServingOperatorName"),
+        modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        device_info_(modem_info_.control_interface(), &dispatcher_,
+                     modem_info_.metrics(), modem_info_.manager()),
+        dhcp_config_(new MockDHCPConfig(modem_info_.control_interface(),
+                                        kTestDeviceName)),
+        create_gsm_card_proxy_from_factory_(false),
+        proxy_factory_(this),
+        mock_home_provider_info_(nullptr),
+        mock_serving_operator_info_(nullptr),
+        device_(new Cellular(&modem_info_,
+                             kTestDeviceName,
+                             kTestDeviceAddress,
+                             3,
+                             Cellular::kTypeGSM,
+                             kDBusOwner,
+                             kDBusService,
+                             kDBusPath,
+                             &proxy_factory_)) {
+    PopulateProxies();
+    modem_info_.metrics()->RegisterDevice(device_->interface_index(),
+                                          Technology::kCellular);
+  }
+
+  virtual void SetUp() {
+    static_cast<Device *>(device_)->rtnl_handler_ = &rtnl_handler_;
+    device_->set_dhcp_provider(&dhcp_provider_);
+    EXPECT_CALL(*modem_info_.mock_manager(), device_info())
+        .WillRepeatedly(Return(&device_info_));
+    EXPECT_CALL(*modem_info_.mock_manager(), DeregisterService(_))
+        .Times(AnyNumber());
+  }
+
+  virtual void TearDown() {
+    device_->DestroyIPConfig();
+    device_->state_ = Cellular::kStateDisabled;
+    device_->capability_->ReleaseProxies();
+    device_->set_dhcp_provider(nullptr);
+    // Break cycle between Cellular and CellularService.
+    device_->service_ = nullptr;
+    device_->SelectService(nullptr);
+  }
+
+  void PopulateProxies() {
+    dbus_properties_proxy_.reset(new MockDBusPropertiesProxy());
+    proxy_.reset(new MockModemProxy());
+    simple_proxy_.reset(new MockModemSimpleProxy());
+    cdma_proxy_.reset(new MockModemCDMAProxy());
+    gsm_card_proxy_.reset(new MockModemGSMCardProxy());
+    gsm_network_proxy_.reset(new MockModemGSMNetworkProxy());
+    mm1_modem_3gpp_proxy_.reset(new mm1::MockModemModem3gppProxy());
+    mm1_proxy_.reset(new mm1::MockModemProxy());
+    mm1_simple_proxy_.reset(new mm1::MockModemSimpleProxy());
+  }
+
+  void SetMockMobileOperatorInfoObjects() {
+    mock_home_provider_info_ =
+        new MockMobileOperatorInfo(&dispatcher_, "HomeProvider");
+    // Takes ownership.
+    device_->set_home_provider_info(mock_home_provider_info_);
+
+    mock_serving_operator_info_ =
+        new MockMobileOperatorInfo(&dispatcher_, "ServingOperator");
+    // Takes ownership.
+    device_->set_serving_operator_info(mock_serving_operator_info_);
+  }
+
+  void InvokeEnable(bool enable, Error *error,
+                    const ResultCallback &callback, int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeEnableReturningWrongState(
+      bool enable, Error *error, const ResultCallback &callback, int timeout) {
+    callback.Run(Error(Error::kWrongState));
+  }
+  void InvokeGetSignalQuality(Error *error,
+                              const SignalQualityCallback &callback,
+                              int timeout) {
+    callback.Run(kStrength, Error());
+  }
+  void InvokeGetModemStatus(Error *error,
+                            const DBusPropertyMapCallback &callback,
+                            int timeout) {
+    DBusPropertiesMap props;
+    props["carrier"].writer().append_string(kTestCarrier);
+    props["unknown-property"].writer().append_string("irrelevant-value");
+    callback.Run(props, Error());
+  }
+  void InvokeGetModemInfo(Error *error, const ModemInfoCallback &callback,
+                            int timeout) {
+    static const char kManufacturer[] = "Company";
+    static const char kModelID[] = "Gobi 2000";
+    static const char kHWRev[] = "A00B1234";
+    ModemHardwareInfo info;
+    info._1 = kManufacturer;
+    info._2 = kModelID;
+    info._3 = kHWRev;
+    callback.Run(info, Error());
+  }
+  void InvokeGetRegistrationState1X(Error *error,
+                                    const RegistrationStateCallback &callback,
+                                    int timeout) {
+    callback.Run(MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+                 MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+                 Error());
+  }
+  void InvokeGetIMEI(Error *error, const GSMIdentifierCallback &callback,
+                     int timeout) {
+    callback.Run(kIMEI, Error());
+  }
+  void InvokeGetIMSI(Error *error, const GSMIdentifierCallback &callback,
+                     int timeout) {
+    callback.Run(kIMSI, Error());
+  }
+  void InvokeGetMSISDN(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout) {
+    callback.Run(kMSISDN, Error());
+  }
+  void InvokeGetSPN(Error *error, const GSMIdentifierCallback &callback,
+                    int timeout) {
+    callback.Run(kTestCarrierSPN, Error());
+  }
+  void InvokeGetRegistrationInfo(Error *error,
+                                 const RegistrationInfoCallback &callback,
+                                 int timeout) {
+    static const char kNetworkID[] = "22803";
+    callback.Run(MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING,
+                 kNetworkID, kTestCarrier, Error());
+  }
+  void InvokeRegister(const string &network_id,
+                      Error *error,
+                      const ResultCallback &callback,
+                      int timeout) {
+    callback.Run(Error());
+  }
+  void InvokeGetRegistrationState(Error *error,
+                                  const RegistrationStateCallback &callback,
+                                  int timeout) {
+    callback.Run(MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED,
+                 MM_MODEM_CDMA_REGISTRATION_STATE_HOME,
+                 Error());
+  }
+  void InvokeGetRegistrationStateUnregistered(
+      Error *error,
+      const RegistrationStateCallback &callback,
+      int timeout) {
+    callback.Run(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+                 MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+                 Error());
+  }
+  void InvokeConnect(DBusPropertiesMap props, Error *error,
+                     const ResultCallback &callback, int timeout) {
+    EXPECT_EQ(Service::kStateAssociating, device_->service_->state());
+    callback.Run(Error());
+  }
+  void InvokeConnectFail(DBusPropertiesMap props, Error *error,
+                         const ResultCallback &callback, int timeout) {
+    EXPECT_EQ(Service::kStateAssociating, device_->service_->state());
+    callback.Run(Error(Error::kNotOnHomeNetwork));
+  }
+  void InvokeConnectFailNoService(DBusPropertiesMap props, Error *error,
+                                  const ResultCallback &callback, int timeout) {
+    device_->service_ = nullptr;
+    callback.Run(Error(Error::kNotOnHomeNetwork));
+  }
+  void InvokeConnectSuccessNoService(DBusPropertiesMap props, Error *error,
+                                     const ResultCallback &callback,
+                                     int timeout) {
+    device_->service_ = nullptr;
+    callback.Run(Error());
+  }
+  void InvokeDisconnect(Error *error, const ResultCallback &callback,
+                        int timeout) {
+    if (!callback.is_null())
+      callback.Run(Error());
+  }
+  void InvokeDisconnectFail(Error *error, const ResultCallback &callback,
+                            int timeout) {
+    error->Populate(Error::kOperationFailed);
+    if (!callback.is_null())
+      callback.Run(*error);
+  }
+  void InvokeDisconnectMM1(const ::DBus::Path &bearer, Error *error,
+                           const ResultCallback &callback, int timeout) {
+    if (!callback.is_null())
+      callback.Run(Error());
+  }
+  void InvokeSetPowerState(const uint32_t &power_state,
+                           Error *error,
+                           const ResultCallback &callback,
+                           int timeout) {
+    callback.Run(Error());
+  }
+  void ExpectCdmaStartModem(string network_technology) {
+    if (!device_->IsUnderlyingDeviceEnabled())
+      EXPECT_CALL(*proxy_,
+                  Enable(true, _, _, CellularCapability::kTimeoutEnable))
+          .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+    EXPECT_CALL(*simple_proxy_,
+                GetModemStatus(_, _, CellularCapability::kTimeoutDefault))
+        .WillOnce(Invoke(this, &CellularTest::InvokeGetModemStatus));
+    EXPECT_CALL(*proxy_,
+                GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
+        .WillOnce(Invoke(this, &CellularTest::InvokeGetModemInfo));
+    if (network_technology == kNetworkTechnology1Xrtt)
+      EXPECT_CALL(*cdma_proxy_, GetRegistrationState(nullptr, _, _))
+          .WillOnce(Invoke(this, &CellularTest::InvokeGetRegistrationState1X));
+    else
+      EXPECT_CALL(*cdma_proxy_, GetRegistrationState(nullptr, _, _))
+          .WillOnce(Invoke(this, &CellularTest::InvokeGetRegistrationState));
+    EXPECT_CALL(*cdma_proxy_, GetSignalQuality(nullptr, _, _))
+        .Times(2)
+        .WillRepeatedly(Invoke(this, &CellularTest::InvokeGetSignalQuality));
+    EXPECT_CALL(*this, TestCallback(IsSuccess()));
+    EXPECT_CALL(*modem_info_.mock_manager(), RegisterService(_));
+  }
+
+  void ExpectDisconnectCapabilityUniversal() {
+    SetCellularType(Cellular::kTypeUniversal);
+    device_->state_ = Cellular::kStateConnected;
+    EXPECT_CALL(*mm1_simple_proxy_, Disconnect(_, _, _, _))
+        .WillOnce(Invoke(this, &CellularTest::InvokeDisconnectMM1));
+    GetCapabilityUniversal()->modem_simple_proxy_.reset(
+        mm1_simple_proxy_.release());
+  }
+
+  void VerifyDisconnect() {
+    EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
+  }
+
+  void StartPPP(int pid) {
+    MockGLib &mock_glib(*dynamic_cast<MockGLib *>(modem_info_.glib()));
+    EXPECT_CALL(mock_glib, ChildWatchAdd(pid, _, _));
+    EXPECT_CALL(mock_glib, SpawnAsync(_, _, _, _, _, _, _, _))
+        .WillOnce(DoAll(SetArgumentPointee<6>(pid), Return(true)));
+    device_->StartPPP("fake_serial_device");
+    EXPECT_FALSE(device_->ipconfig());  // No DHCP client.
+    EXPECT_FALSE(device_->selected_service());
+    EXPECT_FALSE(device_->is_ppp_authenticating_);
+    EXPECT_NE(nullptr, device_->ppp_task_);
+    Mock::VerifyAndClearExpectations(&mock_glib);
+  }
+
+  void FakeUpConnectedPPP() {
+    const char kInterfaceName[] = "fake-ppp-device";
+    const int kInterfaceIndex = -1;
+    auto mock_ppp_device = make_scoped_refptr(
+        new MockPPPDevice(modem_info_.control_interface(), nullptr, nullptr,
+                          nullptr, kInterfaceName, kInterfaceIndex));
+    device_->ppp_device_ = mock_ppp_device;
+    device_->state_ = Cellular::kStateConnected;
+  }
+
+  void ExpectPPPStopped() {
+    auto mock_ppp_device =
+        dynamic_cast<MockPPPDevice *>(device_->ppp_device_.get());
+    EXPECT_CALL(*mock_ppp_device, DropConnection());
+  }
+
+  void VerifyPPPStopped() {
+    EXPECT_EQ(nullptr, device_->ppp_task_);
+    EXPECT_FALSE(device_->ppp_device_);
+  }
+
+  void SetCommonOnAfterResumeExpectations() {
+    EXPECT_CALL(*dbus_properties_proxy_, GetAll(_))
+        .WillRepeatedly(Return(DBusPropertiesMap()));
+    EXPECT_CALL(*mm1_proxy_, set_state_changed_callback(_)).Times(AnyNumber());
+    EXPECT_CALL(*modem_info_.mock_metrics(), NotifyDeviceScanStarted(_))
+        .Times(AnyNumber());
+    EXPECT_CALL(*modem_info_.mock_manager(), UpdateEnabledTechnologies())
+        .Times(AnyNumber());
+    EXPECT_CALL(*dynamic_cast<DeviceMockAdaptor *>(device_->adaptor()),
+                EmitBoolChanged(_, _)).Times(AnyNumber());
+  }
+
+  mm1::MockModemProxy *SetupOnAfterResume() {
+    SetCellularType(Cellular::kTypeUniversal);
+    SetCommonOnAfterResumeExpectations();
+    return mm1_proxy_.get();  // Before the capability snags it.
+  }
+
+  void VerifyOperatorMap(const Stringmap &operator_map,
+                         const string &code,
+                         const string &name,
+                         const string &country) {
+    Stringmap::const_iterator it;
+    Stringmap::const_iterator endit = operator_map.end();
+
+    it = operator_map.find(kOperatorCodeKey);
+    if (code == "") {
+      EXPECT_EQ(endit, it);
+    } else {
+      ASSERT_NE(endit, it);
+      EXPECT_EQ(code, it->second);
+    }
+    it = operator_map.find(kOperatorNameKey);
+    if (name == "") {
+      EXPECT_EQ(endit, it);
+    } else {
+      ASSERT_NE(endit, it);
+      EXPECT_EQ(name, it->second);
+    }
+    it = operator_map.find(kOperatorCountryKey);
+    if (country == "") {
+      EXPECT_EQ(endit, it);
+    } else {
+      ASSERT_NE(endit, it);
+      EXPECT_EQ(country, it->second);
+    }
+  }
+
+  MOCK_METHOD1(TestCallback, void(const Error &error));
+
+ protected:
+  static const char kTestDeviceName[];
+  static const char kTestDeviceAddress[];
+  static const char kDBusOwner[];
+  static const char kDBusService[];
+  static const char kDBusPath[];
+  static const char kTestCarrier[];
+  static const char kTestCarrierSPN[];
+  static const char kMEID[];
+  static const char kIMEI[];
+  static const char kIMSI[];
+  static const char kMSISDN[];
+  static const char kTestMobileProviderDBPath[];
+  static const Stringmaps kTestNetworksGSM;
+  static const Stringmaps kTestNetworksCellular;
+  static const int kStrength;
+
+  // Must be std::string so that we can safely ReturnRef.
+  const string kHomeProviderCode;
+  const string kHomeProviderCountry;
+  const string kHomeProviderName;
+  const string kServingOperatorCode;
+  const string kServingOperatorCountry;
+  const string kServingOperatorName;
+
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(CellularTest *test) : test_(test) {}
+
+    virtual DBusPropertiesProxyInterface *CreateDBusPropertiesProxy(
+        const std::string &path,
+        const std::string &service) {
+      CHECK(test_->dbus_properties_proxy_);
+      return test_->dbus_properties_proxy_.release();
+    }
+
+    virtual ModemProxyInterface *CreateModemProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      CHECK(test_->proxy_);
+      return test_->proxy_.release();
+    }
+
+    virtual ModemSimpleProxyInterface *CreateModemSimpleProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      CHECK(test_->simple_proxy_);
+      return test_->simple_proxy_.release();
+    }
+
+    virtual ModemCDMAProxyInterface *CreateModemCDMAProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      CHECK(test_->cdma_proxy_);
+      return test_->cdma_proxy_.release();
+    }
+
+    virtual ModemGSMCardProxyInterface *CreateModemGSMCardProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      // TODO(benchan): This code conditionally returns a nullptr to avoid
+      // CellularCapabilityGSM::InitProperties (and thus
+      // CellularCapabilityGSM::GetIMSI) from being called during the
+      // construction. Remove this workaround after refactoring the tests.
+      CHECK(!test_->create_gsm_card_proxy_from_factory_ ||
+            test_->gsm_card_proxy_);
+      return test_->create_gsm_card_proxy_from_factory_ ?
+          test_->gsm_card_proxy_.release() : nullptr;
+    }
+
+    virtual ModemGSMNetworkProxyInterface *CreateModemGSMNetworkProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      CHECK(test_->gsm_network_proxy_);
+      return test_->gsm_network_proxy_.release();
+    }
+
+    virtual mm1::ModemModem3gppProxyInterface *CreateMM1ModemModem3gppProxy(
+      const std::string &path,
+      const std::string &service) {
+      CHECK(test_->mm1_modem_3gpp_proxy_);
+      return test_->mm1_modem_3gpp_proxy_.release();
+    }
+
+    virtual mm1::ModemProxyInterface *CreateMM1ModemProxy(
+      const std::string &path,
+      const std::string &service) {
+      CHECK(test_->mm1_proxy_);
+      return test_->mm1_proxy_.release();
+    }
+
+    virtual mm1::ModemSimpleProxyInterface *CreateMM1ModemSimpleProxy(
+        const string &/*path*/,
+        const string &/*service*/) {
+      CHECK(test_->mm1_simple_proxy_);
+      return test_->mm1_simple_proxy_.release();
+    }
+
+   private:
+    CellularTest *test_;
+  };
+  void StartRTNLHandler();
+  void StopRTNLHandler();
+
+  void AllowCreateGSMCardProxyFromFactory() {
+    create_gsm_card_proxy_from_factory_ = true;
+  }
+
+  void SetCellularType(Cellular::Type type) {
+    device_->InitCapability(type);
+  }
+
+  CellularCapabilityClassic *GetCapabilityClassic() {
+    return dynamic_cast<CellularCapabilityClassic *>(
+        device_->capability_.get());
+  }
+
+  CellularCapabilityCDMA *GetCapabilityCDMA() {
+    return dynamic_cast<CellularCapabilityCDMA *>(device_->capability_.get());
+  }
+
+  CellularCapabilityGSM *GetCapabilityGSM() {
+    return dynamic_cast<CellularCapabilityGSM *>(device_->capability_.get());
+  }
+
+  CellularCapabilityUniversal *GetCapabilityUniversal() {
+    return dynamic_cast<CellularCapabilityUniversal *>(
+        device_->capability_.get());
+  }
+
+  // Different tests simulate a cellular service being set using a real /mock
+  // service.
+  CellularService *SetService() {
+    device_->service_ = new CellularService(&modem_info_, device_);
+    return device_->service_;
+  }
+  MockCellularService *SetMockService() {
+    device_->service_ = new MockCellularService(&modem_info_, device_);
+    return static_cast<MockCellularService *>(device_->service_.get());
+  }
+
+  void set_enabled_persistent(bool new_value) {
+    device_->enabled_persistent_ = new_value;
+  }
+
+  void SetCapabilityUniversalActiveBearer(unique_ptr<CellularBearer> bearer) {
+    SetCellularType(Cellular::kTypeUniversal);
+    CellularCapabilityUniversal *capability = GetCapabilityUniversal();
+    capability->active_bearer_ = std::move(bearer);
+  }
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  MockDeviceInfo device_info_;
+  NiceMock<MockRTNLHandler> rtnl_handler_;
+
+  MockDHCPProvider dhcp_provider_;
+  scoped_refptr<MockDHCPConfig> dhcp_config_;
+
+  bool create_gsm_card_proxy_from_factory_;
+  unique_ptr<MockDBusPropertiesProxy> dbus_properties_proxy_;
+  unique_ptr<MockModemProxy> proxy_;
+  unique_ptr<MockModemSimpleProxy> simple_proxy_;
+  unique_ptr<MockModemCDMAProxy> cdma_proxy_;
+  unique_ptr<MockModemGSMCardProxy> gsm_card_proxy_;
+  unique_ptr<MockModemGSMNetworkProxy> gsm_network_proxy_;
+  unique_ptr<mm1::MockModemModem3gppProxy> mm1_modem_3gpp_proxy_;
+  unique_ptr<mm1::MockModemProxy> mm1_proxy_;
+  unique_ptr<mm1::MockModemSimpleProxy> mm1_simple_proxy_;
+  TestProxyFactory proxy_factory_;
+  MockMobileOperatorInfo *mock_home_provider_info_;
+  MockMobileOperatorInfo *mock_serving_operator_info_;
+  CellularRefPtr device_;
+};
+
+const char CellularTest::kTestDeviceName[] = "usb0";
+const char CellularTest::kTestDeviceAddress[] = "00:01:02:03:04:05";
+const char CellularTest::kDBusOwner[] = ":1.19";
+const char CellularTest::kDBusService[] = "org.chromium.ModemManager";
+const char CellularTest::kDBusPath[] = "/org/chromium/ModemManager/Gobi/0";
+const char CellularTest::kTestCarrier[] = "The Cellular Carrier";
+const char CellularTest::kTestCarrierSPN[] = "Home Provider";
+const char CellularTest::kMEID[] = "01234567EF8901";
+const char CellularTest::kIMEI[] = "987654321098765";
+const char CellularTest::kIMSI[] = "123456789012345";
+const char CellularTest::kMSISDN[] = "12345678901";
+const char CellularTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
+const Stringmaps CellularTest::kTestNetworksGSM =
+    {{{CellularCapabilityGSM::kNetworkPropertyStatus, "1"},
+      {CellularCapabilityGSM::kNetworkPropertyID, "0000"},
+      {CellularCapabilityGSM::kNetworkPropertyLongName, "some_long_name"},
+      {CellularCapabilityGSM::kNetworkPropertyShortName, "short"}}};
+const Stringmaps CellularTest::kTestNetworksCellular =
+    {{{kStatusProperty, "available"},
+      {kNetworkIdProperty, "0000"},
+      {kLongNameProperty, "some_long_name"},
+      {kShortNameProperty, "short"}}};
+const int CellularTest::kStrength = 90;
+
+TEST_F(CellularTest, GetStateString) {
+  EXPECT_EQ("CellularStateDisabled",
+            Cellular::GetStateString(Cellular::kStateDisabled));
+  EXPECT_EQ("CellularStateEnabled",
+            Cellular::GetStateString(Cellular::kStateEnabled));
+  EXPECT_EQ("CellularStateRegistered",
+            Cellular::GetStateString(Cellular::kStateRegistered));
+  EXPECT_EQ("CellularStateConnected",
+            Cellular::GetStateString(Cellular::kStateConnected));
+  EXPECT_EQ("CellularStateLinked",
+            Cellular::GetStateString(Cellular::kStateLinked));
+}
+
+TEST_F(CellularTest, GetModemStateString) {
+  EXPECT_EQ("CellularModemStateFailed",
+            Cellular::GetModemStateString(Cellular::kModemStateFailed));
+  EXPECT_EQ("CellularModemStateUnknown",
+            Cellular::GetModemStateString(Cellular::kModemStateUnknown));
+  EXPECT_EQ("CellularModemStateInitializing",
+            Cellular::GetModemStateString(Cellular::kModemStateInitializing));
+  EXPECT_EQ("CellularModemStateLocked",
+            Cellular::GetModemStateString(Cellular::kModemStateLocked));
+  EXPECT_EQ("CellularModemStateDisabled",
+            Cellular::GetModemStateString(Cellular::kModemStateDisabled));
+  EXPECT_EQ("CellularModemStateDisabling",
+            Cellular::GetModemStateString(Cellular::kModemStateDisabling));
+  EXPECT_EQ("CellularModemStateEnabling",
+            Cellular::GetModemStateString(Cellular::kModemStateEnabling));
+  EXPECT_EQ("CellularModemStateEnabled",
+            Cellular::GetModemStateString(Cellular::kModemStateEnabled));
+  EXPECT_EQ("CellularModemStateSearching",
+            Cellular::GetModemStateString(Cellular::kModemStateSearching));
+  EXPECT_EQ("CellularModemStateRegistered",
+            Cellular::GetModemStateString(Cellular::kModemStateRegistered));
+  EXPECT_EQ("CellularModemStateDisconnecting",
+            Cellular::GetModemStateString(Cellular::kModemStateDisconnecting));
+  EXPECT_EQ("CellularModemStateConnecting",
+            Cellular::GetModemStateString(Cellular::kModemStateConnecting));
+  EXPECT_EQ("CellularModemStateConnected",
+            Cellular::GetModemStateString(Cellular::kModemStateConnected));
+}
+
+TEST_F(CellularTest, StartCDMARegister) {
+  SetCellularType(Cellular::kTypeCDMA);
+  ExpectCdmaStartModem(kNetworkTechnology1Xrtt);
+  EXPECT_CALL(*cdma_proxy_, MEID()).WillOnce(Return(kMEID));
+  Error error;
+  device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(kMEID, device_->meid());
+  EXPECT_EQ(kTestCarrier, device_->carrier());
+  EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
+  ASSERT_TRUE(device_->service_.get());
+  EXPECT_EQ(kNetworkTechnology1Xrtt, device_->service_->network_technology());
+  EXPECT_EQ(kStrength, device_->service_->strength());
+  EXPECT_EQ(kRoamingStateHome, device_->service_->roaming_state());
+}
+
+TEST_F(CellularTest, StartGSMRegister) {
+  SetMockMobileOperatorInfoObjects();
+  EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  EXPECT_CALL(*gsm_card_proxy_,
+              GetIMEI(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetIMEI));
+  EXPECT_CALL(*gsm_card_proxy_,
+              GetIMSI(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetIMSI));
+  EXPECT_CALL(*gsm_card_proxy_,
+              GetSPN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetSPN));
+  EXPECT_CALL(*gsm_card_proxy_,
+              GetMSISDN(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetMSISDN));
+  EXPECT_CALL(*gsm_network_proxy_, AccessTechnology())
+      .WillOnce(Return(MM_MODEM_GSM_ACCESS_TECH_EDGE));
+  EXPECT_CALL(*gsm_card_proxy_, EnabledFacilityLocks())
+      .WillOnce(Return(MM_MODEM_GSM_FACILITY_SIM));
+  EXPECT_CALL(*proxy_, GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetModemInfo));
+  EXPECT_CALL(*gsm_network_proxy_,
+              GetRegistrationInfo(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetRegistrationInfo));
+  EXPECT_CALL(*gsm_network_proxy_, GetSignalQuality(nullptr, _, _))
+      .Times(2)
+      .WillRepeatedly(Invoke(this,
+                             &CellularTest::InvokeGetSignalQuality));
+  EXPECT_CALL(*mock_serving_operator_info_, UpdateMCCMNC(_));
+  EXPECT_CALL(*mock_serving_operator_info_, UpdateOperatorName(_));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  EXPECT_CALL(*modem_info_.mock_manager(), RegisterService(_));
+  AllowCreateGSMCardProxyFromFactory();
+
+  Error error;
+  device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(kIMEI, device_->imei());
+  EXPECT_EQ(kIMSI, device_->imsi());
+  EXPECT_EQ(kTestCarrierSPN, GetCapabilityGSM()->spn_);
+  EXPECT_EQ(kMSISDN, device_->mdn());
+  EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
+  ASSERT_TRUE(device_->service_.get());
+  EXPECT_EQ(kNetworkTechnologyEdge, device_->service_->network_technology());
+  EXPECT_TRUE(GetCapabilityGSM()->sim_lock_status_.enabled);
+  EXPECT_EQ(kStrength, device_->service_->strength());
+  EXPECT_EQ(kRoamingStateRoaming, device_->service_->roaming_state());
+}
+
+TEST_F(CellularTest, StartConnected) {
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(Return(true));
+  SetCellularType(Cellular::kTypeCDMA);
+  device_->set_modem_state(Cellular::kModemStateConnected);
+  device_->set_meid(kMEID);
+  ExpectCdmaStartModem(kNetworkTechnologyEvdo);
+  Error error;
+  device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateConnected, device_->state_);
+}
+
+TEST_F(CellularTest, StartLinked) {
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
+  SetCellularType(Cellular::kTypeCDMA);
+  device_->set_modem_state(Cellular::kModemStateConnected);
+  device_->set_meid(kMEID);
+  ExpectCdmaStartModem(kNetworkTechnologyEvdo);
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, _))
+      .WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_, RequestIP()).WillOnce(Return(true));
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateService(_)).Times(3);
+  Error error;
+  device_->Start(&error, Bind(&CellularTest::TestCallback, Unretained(this)));
+  EXPECT_TRUE(error.IsSuccess());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateLinked, device_->state_);
+  EXPECT_EQ(Service::kStateConfiguring, device_->service_->state());
+  device_->SelectService(nullptr);
+}
+
+TEST_F(CellularTest, FriendlyServiceName) {
+  // Test that the name created for the service is sensible under different
+  // scenarios w.r.t. information about the mobile network operator.
+  SetMockMobileOperatorInfoObjects();
+  CHECK(mock_home_provider_info_);
+  CHECK(mock_serving_operator_info_);
+
+  SetCellularType(Cellular::kTypeCDMA);
+  // We are not testing the behaviour of capabilities here.
+  device_->mobile_operator_info_observer_->set_capability(nullptr);
+
+  // (1) Service created, MNO not known => Default name.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  // Compare substrings explicitly using EXPECT_EQ for better error message.
+  size_t prefix_len = strlen(Cellular::kGenericServiceNamePrefix);
+  EXPECT_EQ(Cellular::kGenericServiceNamePrefix,
+            device_->service_->friendly_name().substr(0, prefix_len));
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (2) Service created, then home provider determined => Name provided by
+  //     home provider.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  // Now emulate an event for updated home provider information.
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  EXPECT_EQ(kHomeProviderName, device_->service_->friendly_name());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (3) Service created, then serving operator determined => Name provided by
+  //     serving operator.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  // Now emulate an event for updated serving operator information.
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  EXPECT_EQ(kServingOperatorName, device_->service_->friendly_name());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (4) Service created, then home provider determined, then serving operator
+  // determined => final name is serving operator.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  // Now emulate an event for updated home provider information.
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  // Now emulate an event for updated serving operator information.
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  EXPECT_EQ(kServingOperatorName, device_->service_->friendly_name());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (5) Service created, then serving operator determined, then home provider
+  // determined => final name is serving operator.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  // Now emulate an event for updated serving operator information.
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  // Now emulate an event for updated home provider information.
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  device_->mobile_operator_info_observer_->OnOperatorChanged();
+  EXPECT_EQ(kServingOperatorName, device_->service_->friendly_name());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (6) Serving operator known, home provider known, and then service created
+  //     => Name is serving operator.
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  device_->CreateService();
+  EXPECT_EQ(kServingOperatorName, device_->service_->friendly_name());
+}
+
+TEST_F(CellularTest, HomeProviderServingOperator) {
+  // Test that the the home provider information is correctly updated under
+  // different scenarios w.r.t. information about the mobile network operators.
+  SetMockMobileOperatorInfoObjects();
+  CHECK(mock_home_provider_info_);
+  CHECK(mock_serving_operator_info_);
+  Stringmap home_provider;
+  Stringmap serving_operator;
+
+
+  // (1) Neither home provider nor serving operator known.
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+
+  device_->CreateService();
+
+  home_provider = device_->home_provider();
+  VerifyOperatorMap(home_provider, "", "", "");
+  serving_operator = device_->service_->serving_operator();
+  VerifyOperatorMap(serving_operator, "", "", "");
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (2) serving operator known.
+  // When home provider is not known, serving operator proxies in.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, mccmnc())
+      .WillRepeatedly(ReturnRef(kServingOperatorCode));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  EXPECT_CALL(*mock_serving_operator_info_, country())
+      .WillRepeatedly(ReturnRef(kServingOperatorCountry));
+
+  device_->CreateService();
+
+  home_provider = device_->home_provider();
+  VerifyOperatorMap(home_provider,
+                    kServingOperatorCode,
+                    kServingOperatorName,
+                    kServingOperatorCountry);
+  serving_operator = device_->service_->serving_operator();
+  VerifyOperatorMap(serving_operator,
+                    kServingOperatorCode,
+                    kServingOperatorName,
+                    kServingOperatorCountry);
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (3) home provider known.
+  // When serving operator is not known, home provider proxies in.
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, mccmnc())
+      .WillRepeatedly(ReturnRef(kHomeProviderCode));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  EXPECT_CALL(*mock_home_provider_info_, country())
+      .WillRepeatedly(ReturnRef(kHomeProviderCountry));
+
+  device_->CreateService();
+
+  home_provider = device_->home_provider();
+  VerifyOperatorMap(home_provider,
+                    kHomeProviderCode,
+                    kHomeProviderName,
+                    kHomeProviderCountry);
+  serving_operator = device_->service_->serving_operator();
+  VerifyOperatorMap(serving_operator,
+                    kHomeProviderCode,
+                    kHomeProviderName,
+                    kHomeProviderCountry);
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (4) Serving operator known, home provider known.
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, mccmnc())
+      .WillRepeatedly(ReturnRef(kHomeProviderCode));
+  EXPECT_CALL(*mock_home_provider_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kHomeProviderName));
+  EXPECT_CALL(*mock_home_provider_info_, country())
+      .WillRepeatedly(ReturnRef(kHomeProviderCountry));
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, mccmnc())
+      .WillRepeatedly(ReturnRef(kServingOperatorCode));
+  EXPECT_CALL(*mock_serving_operator_info_, operator_name())
+      .WillRepeatedly(ReturnRef(kServingOperatorName));
+  EXPECT_CALL(*mock_serving_operator_info_, country())
+      .WillRepeatedly(ReturnRef(kServingOperatorCountry));
+
+  device_->CreateService();
+
+  home_provider = device_->home_provider();
+  VerifyOperatorMap(home_provider,
+                    kHomeProviderCode,
+                    kHomeProviderName,
+                    kHomeProviderCountry);
+  serving_operator = device_->service_->serving_operator();
+  VerifyOperatorMap(serving_operator,
+                    kServingOperatorCode,
+                    kServingOperatorName,
+                    kServingOperatorCountry);
+}
+
+static bool IllegalChar(char a) {
+  return !(isalnum(a) || a == '_');
+}
+
+TEST_F(CellularTest, StorageIdentifier) {
+  // Test that the storage identifier name used by the service is sensible under
+  // different scenarios w.r.t. information about the mobile network operator.
+  SetMockMobileOperatorInfoObjects();
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  CHECK(mock_home_provider_info_);
+  CHECK(mock_serving_operator_info_);
+
+  // See cellular_service.cc
+  string prefix = string(kTypeCellular) + "_" +
+                  string(kTestDeviceAddress) + "_";
+  // Service replaces ':' with '_'
+  std::replace_if(prefix.begin(), prefix.end(), &IllegalChar, '_');
+  const string kUuidHomeProvider = "uuidHomeProvider";
+  const string kUuidServingOperator = "uuidServingOperator";
+  const string kSimIdentifier = "12345123451234512345";
+
+  SetCellularType(Cellular::kTypeCDMA);
+  // We are not testing the behaviour of capabilities here.
+  device_->mobile_operator_info_observer_->set_capability(nullptr);
+  ON_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillByDefault(Return(false));
+
+  // (1) Service created, both home provider and serving operator known =>
+  // home provider used.
+  mock_home_provider_info_->SetEmptyDefaultsForProperties();
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_home_provider_info_, uuid())
+      .WillRepeatedly(ReturnRef(kUuidHomeProvider));
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, uuid())
+      .WillRepeatedly(ReturnRef(kUuidServingOperator));
+  device_->CreateService();
+  EXPECT_EQ(prefix + kUuidHomeProvider,
+            device_->service()->GetStorageIdentifier());
+  Mock::VerifyAndClearExpectations(mock_home_provider_info_);
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // Common expectation for following tests:
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+
+  // (2) Service created, no extra information => Default storage_id;
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(false));
+  device_->CreateService();
+  EXPECT_EQ(prefix + device_->service()->friendly_name(),
+            device_->service()->GetStorageIdentifier());
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (3) Service created, serving operator known, uuid known.
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_serving_operator_info_, uuid())
+      .WillRepeatedly(ReturnRef(kUuidServingOperator));
+  device_->CreateService();
+  EXPECT_EQ(prefix + kUuidServingOperator,
+            device_->service()->GetStorageIdentifier());
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+
+  // (4) Service created, serving operator known, uuid not known, iccid known.
+  mock_serving_operator_info_->SetEmptyDefaultsForProperties();
+  EXPECT_CALL(*mock_serving_operator_info_, IsMobileNetworkOperatorKnown())
+      .WillRepeatedly(Return(true));
+  device_->set_sim_identifier(kSimIdentifier);
+  device_->CreateService();
+  EXPECT_EQ(prefix + kSimIdentifier,
+            device_->service()->GetStorageIdentifier());
+  Mock::VerifyAndClearExpectations(mock_serving_operator_info_);
+  device_->DestroyService();
+}
+
+namespace {
+
+MATCHER(ContainsPhoneNumber, "") {
+  return ContainsKey(arg,
+                     CellularCapabilityClassic::kConnectPropertyPhoneNumber);
+}
+
+}  // namespace
+
+TEST_F(CellularTest, Connect) {
+  Error error;
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .Times(2)
+      .WillRepeatedly(Return(true));
+  device_->state_ = Cellular::kStateConnected;
+  device_->Connect(&error);
+  EXPECT_EQ(Error::kAlreadyConnected, error.type());
+  error.Populate(Error::kSuccess);
+
+  device_->state_ = Cellular::kStateLinked;
+  device_->Connect(&error);
+  EXPECT_EQ(Error::kAlreadyConnected, error.type());
+
+  device_->state_ = Cellular::kStateEnabled;
+  device_->Connect(&error);
+  EXPECT_EQ(Error::kNotRegistered, error.type());
+
+  error.Reset();
+  device_->state_ = Cellular::kStateDisabled;
+  device_->Connect(&error);
+  EXPECT_EQ(Error::kNotRegistered, error.type());
+
+  device_->state_ = Cellular::kStateRegistered;
+  SetService();
+
+  device_->allow_roaming_ = false;
+  device_->service_->roaming_state_ = kRoamingStateRoaming;
+  device_->Connect(&error);
+  EXPECT_EQ(Error::kNotOnHomeNetwork, error.type());
+
+  error.Populate(Error::kSuccess);
+  EXPECT_CALL(*simple_proxy_,
+              Connect(ContainsPhoneNumber(), _, _,
+                      CellularCapability::kTimeoutConnect))
+                .Times(2)
+                .WillRepeatedly(Invoke(this, &CellularTest::InvokeConnect));
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
+  device_->service_->roaming_state_ = kRoamingStateHome;
+  device_->state_ = Cellular::kStateRegistered;
+  device_->Connect(&error);
+  EXPECT_TRUE(error.IsSuccess());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateConnected, device_->state_);
+
+  device_->allow_roaming_ = true;
+  device_->service_->roaming_state_ = kRoamingStateRoaming;
+  device_->state_ = Cellular::kStateRegistered;
+  device_->Connect(&error);
+  EXPECT_TRUE(error.IsSuccess());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateConnected, device_->state_);
+}
+
+TEST_F(CellularTest, Disconnect) {
+  Error error;
+  device_->state_ = Cellular::kStateRegistered;
+  device_->Disconnect(&error, "in test");
+  EXPECT_EQ(Error::kNotConnected, error.type());
+  error.Reset();
+
+  device_->state_ = Cellular::kStateConnected;
+  EXPECT_CALL(*proxy_,
+              Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .WillOnce(Invoke(this, &CellularTest::InvokeDisconnect));
+  GetCapabilityClassic()->proxy_.reset(proxy_.release());
+  device_->Disconnect(&error, "in test");
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
+}
+
+TEST_F(CellularTest, DisconnectFailure) {
+  // Test the case where the underlying modem state is set
+  // to disconnecting, but shill thinks it's still connected
+  Error error;
+  device_->state_ = Cellular::kStateConnected;
+  EXPECT_CALL(*proxy_,
+              Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+       .Times(2)
+       .WillRepeatedly(Invoke(this, &CellularTest::InvokeDisconnectFail));
+  GetCapabilityClassic()->proxy_.reset(proxy_.release());
+  device_->modem_state_ = Cellular::kModemStateDisconnecting;
+  device_->Disconnect(&error, "in test");
+  EXPECT_TRUE(error.IsFailure());
+  EXPECT_EQ(Cellular::kStateConnected, device_->state_);
+
+  device_->modem_state_ = Cellular::kModemStateConnected;
+  device_->Disconnect(&error, "in test");
+  EXPECT_TRUE(error.IsFailure());
+  EXPECT_EQ(Cellular::kStateRegistered, device_->state_);
+}
+
+TEST_F(CellularTest, ConnectFailure) {
+  SetCellularType(Cellular::kTypeCDMA);
+  device_->state_ = Cellular::kStateRegistered;
+  SetService();
+  ASSERT_EQ(Service::kStateIdle, device_->service_->state());
+  EXPECT_CALL(*simple_proxy_,
+              Connect(_, _, _, CellularCapability::kTimeoutConnect))
+                .WillOnce(Invoke(this, &CellularTest::InvokeConnectFail));
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
+  Error error;
+  device_->Connect(&error);
+  EXPECT_EQ(Service::kStateFailure, device_->service_->state());
+}
+
+TEST_F(CellularTest, ConnectFailureNoService) {
+  // Make sure we don't crash if the connect failed and there is no
+  // CellularService object.  This can happen if the modem is enabled and
+  // then quick disabled.
+  SetCellularType(Cellular::kTypeCDMA);
+  device_->state_ = Cellular::kStateRegistered;
+  SetService();
+  EXPECT_CALL(
+      *simple_proxy_,
+      Connect(_, _, _, CellularCapability::kTimeoutConnect))
+      .WillOnce(Invoke(this, &CellularTest::InvokeConnectFailNoService));
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateService(_));
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
+  Error error;
+  device_->Connect(&error);
+}
+
+TEST_F(CellularTest, ConnectSuccessNoService) {
+  // Make sure we don't crash if the connect succeeds but the service was
+  // destroyed before the connect request completes.
+  SetCellularType(Cellular::kTypeCDMA);
+  device_->state_ = Cellular::kStateRegistered;
+  SetService();
+  EXPECT_CALL(
+      *simple_proxy_,
+      Connect(_, _, _, CellularCapability::kTimeoutConnect))
+      .WillOnce(Invoke(this, &CellularTest::InvokeConnectSuccessNoService));
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateService(_));
+  GetCapabilityClassic()->simple_proxy_.reset(simple_proxy_.release());
+  Error error;
+  device_->Connect(&error);
+}
+
+TEST_F(CellularTest, LinkEventWontDestroyService) {
+  // If the network interface goes down, Cellular::LinkEvent should
+  // drop the connection but the service object should persist.
+  device_->state_ = Cellular::kStateLinked;
+  CellularService *service = SetService();
+  device_->LinkEvent(0, 0);  // flags doesn't contain IFF_UP
+  EXPECT_EQ(device_->state_, Cellular::kStateConnected);
+  EXPECT_EQ(device_->service_, service);
+}
+
+TEST_F(CellularTest, UseNoArpGateway) {
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, false))
+      .WillOnce(Return(dhcp_config_));
+  device_->AcquireIPConfig();
+}
+
+TEST_F(CellularTest, ModemStateChangeEnable) {
+  EXPECT_CALL(*simple_proxy_,
+              GetModemStatus(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetModemStatus));
+  EXPECT_CALL(*cdma_proxy_, MEID()).WillOnce(Return(kMEID));
+  EXPECT_CALL(*proxy_,
+              GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetModemInfo));
+  EXPECT_CALL(*cdma_proxy_, GetRegistrationState(nullptr, _, _))
+      .WillOnce(Invoke(this,
+                       &CellularTest::InvokeGetRegistrationStateUnregistered));
+  EXPECT_CALL(*cdma_proxy_, GetSignalQuality(nullptr, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeGetSignalQuality));
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateEnabledTechnologies());
+  device_->state_ = Cellular::kStateDisabled;
+  device_->set_modem_state(Cellular::kModemStateDisabled);
+  SetCellularType(Cellular::kTypeCDMA);
+
+  DBusPropertiesMap props;
+  props[CellularCapabilityClassic::kModemPropertyEnabled].writer().
+      append_bool(true);
+  device_->OnDBusPropertiesChanged(MM_MODEM_INTERFACE, props, vector<string>());
+  dispatcher_.DispatchPendingEvents();
+
+  EXPECT_EQ(Cellular::kModemStateEnabled, device_->modem_state());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state());
+  EXPECT_TRUE(device_->enabled());
+}
+
+TEST_F(CellularTest, ModemStateChangeDisable) {
+  EXPECT_CALL(*proxy_,
+              Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
+      .WillOnce(Invoke(this, &CellularTest::InvokeDisconnect));
+  EXPECT_CALL(*proxy_,
+              Enable(false, _, _, CellularCapability::kTimeoutEnable))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateEnabledTechnologies());
+  device_->enabled_ = true;
+  device_->enabled_pending_ = true;
+  device_->state_ = Cellular::kStateEnabled;
+  device_->set_modem_state(Cellular::kModemStateEnabled);
+  SetCellularType(Cellular::kTypeCDMA);
+  GetCapabilityClassic()->InitProxies();
+
+  GetCapabilityClassic()->OnModemStateChangedSignal(kModemClassicStateEnabled,
+                                                    kModemClassicStateDisabled,
+                                                    0);
+  dispatcher_.DispatchPendingEvents();
+
+  EXPECT_EQ(Cellular::kModemStateDisabled, device_->modem_state());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state());
+  EXPECT_FALSE(device_->enabled());
+}
+
+TEST_F(CellularTest, ModemStateChangeStaleConnected) {
+  // Test to make sure that we ignore stale modem Connected state transitions.
+  // When a modem is asked to connect and before the connect completes, the
+  // modem is disabled, it may send a stale Connected state transition after
+  // it has been disabled.
+  AllowCreateGSMCardProxyFromFactory();
+  device_->state_ = Cellular::kStateDisabled;
+  device_->modem_state_ = Cellular::kModemStateEnabling;
+  device_->OnModemStateChanged(Cellular::kModemStateConnected);
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state());
+}
+
+TEST_F(CellularTest, ModemStateChangeValidConnected) {
+  device_->state_ = Cellular::kStateEnabled;
+  device_->modem_state_ = Cellular::kModemStateConnecting;
+  SetService();
+  device_->OnModemStateChanged(Cellular::kModemStateConnected);
+  EXPECT_EQ(Cellular::kStateConnected, device_->state());
+}
+
+TEST_F(CellularTest, ModemStateChangeLostRegistration) {
+  SetCellularType(Cellular::kTypeUniversal);
+  CellularCapabilityUniversal *capability = GetCapabilityUniversal();
+  capability->registration_state_ = MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
+  EXPECT_TRUE(capability->IsRegistered());
+  device_->set_modem_state(Cellular::kModemStateRegistered);
+  device_->OnModemStateChanged(Cellular::kModemStateEnabled);
+  EXPECT_FALSE(capability->IsRegistered());
+}
+
+TEST_F(CellularTest, StartModemCallback) {
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  EXPECT_EQ(device_->state_, Cellular::kStateDisabled);
+  device_->StartModemCallback(Bind(&CellularTest::TestCallback,
+                                   Unretained(this)),
+                              Error(Error::kSuccess));
+  EXPECT_EQ(device_->state_, Cellular::kStateEnabled);
+}
+
+TEST_F(CellularTest, StartModemCallbackFail) {
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  EXPECT_EQ(device_->state_, Cellular::kStateDisabled);
+  device_->StartModemCallback(Bind(&CellularTest::TestCallback,
+                                   Unretained(this)),
+                              Error(Error::kOperationFailed));
+  EXPECT_EQ(device_->state_, Cellular::kStateDisabled);
+}
+
+TEST_F(CellularTest, StopModemCallback) {
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  SetMockService();
+  device_->StopModemCallback(Bind(&CellularTest::TestCallback,
+                                  Unretained(this)),
+                             Error(Error::kSuccess));
+  EXPECT_EQ(device_->state_, Cellular::kStateDisabled);
+  EXPECT_FALSE(device_->service_.get());
+}
+
+TEST_F(CellularTest, StopModemCallbackFail) {
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  SetMockService();
+  device_->StopModemCallback(Bind(&CellularTest::TestCallback,
+                                  Unretained(this)),
+                             Error(Error::kOperationFailed));
+  EXPECT_EQ(device_->state_, Cellular::kStateDisabled);
+  EXPECT_FALSE(device_->service_.get());
+}
+
+TEST_F(CellularTest, SetAllowRoaming) {
+  EXPECT_FALSE(device_->allow_roaming_);
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateDevice(_));
+  Error error;
+  device_->SetAllowRoaming(true, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_TRUE(device_->allow_roaming_);
+}
+
+class TestRPCTaskDelegate :
+      public RPCTaskDelegate,
+      public base::SupportsWeakPtr<TestRPCTaskDelegate> {
+ public:
+  virtual void GetLogin(std::string *user, std::string *password) {}
+  virtual void Notify(const std::string &reason,
+                      const std::map<std::string, std::string> &dict) {}
+};
+
+TEST_F(CellularTest, LinkEventUpWithPPP) {
+  // If PPP is running, don't run DHCP as well.
+  TestRPCTaskDelegate task_delegate;
+  base::Callback<void(pid_t, int)> death_callback;
+  unique_ptr<NiceMock<MockExternalTask>> mock_task(
+      new NiceMock<MockExternalTask>(modem_info_.control_interface(),
+                                     modem_info_.glib(),
+                                     task_delegate.AsWeakPtr(),
+                                     death_callback));
+  EXPECT_CALL(*mock_task, OnDelete()).Times(AnyNumber());
+  device_->ppp_task_ = std::move(mock_task);
+  device_->state_ = Cellular::kStateConnected;
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*dhcp_config_, RequestIP()).Times(0);
+  device_->LinkEvent(IFF_UP, 0);
+}
+
+TEST_F(CellularTest, LinkEventUpWithoutPPP) {
+  // If PPP is not running, fire up DHCP.
+  device_->state_ = Cellular::kStateConnected;
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, _))
+      .WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_, RequestIP());
+  EXPECT_CALL(*dhcp_config_, ReleaseIP(_)).Times(AnyNumber());
+  device_->LinkEvent(IFF_UP, 0);
+}
+
+TEST_F(CellularTest, StartPPP) {
+  const int kPID = 234;
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+  StartPPP(kPID);
+}
+
+TEST_F(CellularTest, StartPPPAlreadyStarted) {
+  const int kPID = 234;
+  StartPPP(kPID);
+
+  const int kPID2 = 235;
+  StartPPP(kPID2);
+}
+
+TEST_F(CellularTest, StartPPPAfterEthernetUp) {
+  CellularService *service(SetService());
+  device_->state_ = Cellular::kStateLinked;
+  device_->set_ipconfig(dhcp_config_);
+  device_->SelectService(service);
+  EXPECT_CALL(*dhcp_config_, ReleaseIP(_))
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+  const int kPID = 234;
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+  StartPPP(kPID);
+  EXPECT_EQ(Cellular::kStateLinked, device_->state());
+}
+
+TEST_F(CellularTest, GetLogin) {
+  // Doesn't crash when there is no service.
+  string username_to_pppd;
+  string password_to_pppd;
+  EXPECT_FALSE(device_->service());
+  device_->GetLogin(&username_to_pppd, &password_to_pppd);
+
+  // Provides expected username and password in normal case.
+  const char kFakeUsername[] = "fake-user";
+  const char kFakePassword[] = "fake-password";
+  CellularService &service(*SetService());
+  service.ppp_username_ = kFakeUsername;
+  service.ppp_password_ = kFakePassword;
+  device_->GetLogin(&username_to_pppd, &password_to_pppd);
+}
+
+TEST_F(CellularTest, Notify) {
+  // Common setup.
+  MockPPPDeviceFactory *ppp_device_factory =
+      MockPPPDeviceFactory::GetInstance();
+  const int kPID = 91;
+  device_->ppp_device_factory_ = ppp_device_factory;
+  SetMockService();
+  StartPPP(kPID);
+
+  const map<string, string> kEmptyArgs;
+  device_->Notify(kPPPReasonAuthenticating, kEmptyArgs);
+  EXPECT_TRUE(device_->is_ppp_authenticating_);
+  device_->Notify(kPPPReasonAuthenticated, kEmptyArgs);
+  EXPECT_FALSE(device_->is_ppp_authenticating_);
+
+  // Normal connect.
+  const string kInterfaceName("fake-device");
+  const int kInterfaceIndex = 1;
+  scoped_refptr<MockPPPDevice> ppp_device;
+  map<string, string> ppp_config;
+  ppp_device =
+      new MockPPPDevice(modem_info_.control_interface(), nullptr, nullptr,
+                        nullptr, kInterfaceName, kInterfaceIndex);
+  ppp_config[kPPPInterfaceName] = kInterfaceName;
+  EXPECT_CALL(device_info_, GetIndex(kInterfaceName))
+      .WillOnce(Return(kInterfaceIndex));
+  EXPECT_CALL(device_info_, RegisterDevice(_));
+  EXPECT_CALL(*ppp_device_factory,
+              CreatePPPDevice(_, _, _, _, kInterfaceName, kInterfaceIndex))
+      .WillOnce(Return(ppp_device));
+  EXPECT_CALL(*ppp_device, SetEnabled(true));
+  EXPECT_CALL(*ppp_device, SelectService(_));
+  EXPECT_CALL(*ppp_device, UpdateIPConfigFromPPP(ppp_config, false));
+  device_->Notify(kPPPReasonConnect, ppp_config);
+  Mock::VerifyAndClearExpectations(&device_info_);
+  Mock::VerifyAndClearExpectations(ppp_device);
+
+  // Re-connect on same network device: if pppd sends us multiple connect
+  // events, we behave sanely.
+  EXPECT_CALL(device_info_, GetIndex(kInterfaceName))
+      .WillOnce(Return(kInterfaceIndex));
+  EXPECT_CALL(*ppp_device, SetEnabled(true));
+  EXPECT_CALL(*ppp_device, SelectService(_));
+  EXPECT_CALL(*ppp_device, UpdateIPConfigFromPPP(ppp_config, false));
+  device_->Notify(kPPPReasonConnect, ppp_config);
+  Mock::VerifyAndClearExpectations(&device_info_);
+  Mock::VerifyAndClearExpectations(ppp_device);
+
+  // Re-connect on new network device: if we still have the PPPDevice
+  // from a prior connect, this new connect should DTRT. This is
+  // probably an unlikely case.
+  const string kInterfaceName2("fake-device2");
+  const int kInterfaceIndex2 = 2;
+  scoped_refptr<MockPPPDevice> ppp_device2;
+  map<string, string> ppp_config2;
+  ppp_device2 =
+      new MockPPPDevice(modem_info_.control_interface(), nullptr, nullptr,
+                        nullptr, kInterfaceName2, kInterfaceIndex2);
+  ppp_config2[kPPPInterfaceName] = kInterfaceName2;
+  EXPECT_CALL(device_info_, GetIndex(kInterfaceName2))
+      .WillOnce(Return(kInterfaceIndex2));
+  EXPECT_CALL(device_info_,
+              RegisterDevice(static_cast<DeviceRefPtr>(ppp_device2)));
+  EXPECT_CALL(*ppp_device_factory,
+              CreatePPPDevice(_, _, _, _, kInterfaceName2, kInterfaceIndex2))
+      .WillOnce(Return(ppp_device2));
+  EXPECT_CALL(*ppp_device, SelectService(ServiceRefPtr(nullptr)));
+  EXPECT_CALL(*ppp_device2, SetEnabled(true));
+  EXPECT_CALL(*ppp_device2, SelectService(_));
+  EXPECT_CALL(*ppp_device2, UpdateIPConfigFromPPP(ppp_config2, false));
+  device_->Notify(kPPPReasonConnect, ppp_config2);
+  Mock::VerifyAndClearExpectations(&device_info_);
+  Mock::VerifyAndClearExpectations(ppp_device);
+  Mock::VerifyAndClearExpectations(ppp_device2);
+
+  // Disconnect should report unknown failure, since we had a
+  // Notify(kPPPReasonAuthenticated, ...).
+  EXPECT_CALL(*ppp_device2, SetServiceFailure(Service::kFailureUnknown));
+  device_->Notify(kPPPReasonDisconnect, kEmptyArgs);
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+
+  // |Cellular::ppp_task_| is destroyed on the task loop. Must dispatch once to
+  // cleanup.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularTest, PPPConnectionFailedBeforeAuth) {
+  // Test that we properly set Service state in the case where pppd
+  // disconnects before authenticating (as opposed to the Notify test,
+  // where pppd disconnects after connecting).
+  const int kPID = 52;
+  const map<string, string> kEmptyArgs;
+  MockCellularService *service = SetMockService();
+  StartPPP(kPID);
+
+  ExpectDisconnectCapabilityUniversal();
+  EXPECT_CALL(*service, SetFailure(Service::kFailureUnknown));
+  device_->Notify(kPPPReasonDisconnect, kEmptyArgs);
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+  VerifyDisconnect();
+
+  // |Cellular::ppp_task_| is destroyed on the task loop. Must dispatch once to
+  // cleanup.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularTest, PPPConnectionFailedDuringAuth) {
+  // Test that we properly set Service state in the case where pppd
+  // disconnects during authentication (as opposed to the Notify test,
+  // where pppd disconnects after connecting).
+  const int kPID = 52;
+  const map<string, string> kEmptyArgs;
+  MockCellularService *service = SetMockService();
+  StartPPP(kPID);
+
+  ExpectDisconnectCapabilityUniversal();
+  EXPECT_CALL(*service, SetFailure(Service::kFailurePPPAuth));
+  device_->Notify(kPPPReasonAuthenticating, kEmptyArgs);
+  device_->Notify(kPPPReasonDisconnect, kEmptyArgs);
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+  VerifyDisconnect();
+
+  // |Cellular::ppp_task_| is destroyed on the task loop. Must dispatch once to
+  // cleanup.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularTest, PPPConnectionFailedAfterAuth) {
+  // Test that we properly set Service state in the case where pppd
+  // disconnects after authenticating, but before connecting (as
+  // opposed to the Notify test, where pppd disconnects after
+  // connecting).
+  const int kPID = 52;
+  const map<string, string> kEmptyArgs;
+  MockCellularService *service = SetMockService();
+  StartPPP(kPID);
+
+  EXPECT_CALL(*service, SetFailure(Service::kFailureUnknown));
+  ExpectDisconnectCapabilityUniversal();
+  device_->Notify(kPPPReasonAuthenticating, kEmptyArgs);
+  device_->Notify(kPPPReasonAuthenticated, kEmptyArgs);
+  device_->Notify(kPPPReasonDisconnect, kEmptyArgs);
+  EXPECT_EQ(nullptr, device_->ppp_task_);
+  VerifyDisconnect();
+
+  // |Cellular::ppp_task_| is destroyed on the task loop. Must dispatch once to
+  // cleanup.
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(CellularTest, OnPPPDied) {
+  const int kPID = 1234;
+  const int kExitStatus = 5;
+  ExpectDisconnectCapabilityUniversal();
+  device_->OnPPPDied(kPID, kExitStatus);
+  VerifyDisconnect();
+}
+
+TEST_F(CellularTest, DropConnection) {
+  device_->set_ipconfig(dhcp_config_);
+  EXPECT_CALL(*dhcp_config_, ReleaseIP(_));
+  device_->DropConnection();
+  Mock::VerifyAndClearExpectations(dhcp_config_);  // verify before dtor
+  EXPECT_FALSE(device_->ipconfig());
+}
+
+TEST_F(CellularTest, DropConnectionPPP) {
+  scoped_refptr<MockPPPDevice> ppp_device(
+      new MockPPPDevice(modem_info_.control_interface(),
+                        nullptr, nullptr, nullptr, "fake_ppp0", -1));
+  EXPECT_CALL(*ppp_device, DropConnection());
+  device_->ppp_device_ = ppp_device;
+  device_->DropConnection();
+}
+
+TEST_F(CellularTest, ChangeServiceState) {
+  MockCellularService *service(SetMockService());
+  EXPECT_CALL(*service, SetState(_));
+  EXPECT_CALL(*service, SetFailure(_));
+  EXPECT_CALL(*service, SetFailureSilent(_));
+  ON_CALL(*service, state()).WillByDefault(Return(Service::kStateUnknown));
+
+  // Without PPP, these should be handled by our selected_service().
+  device_->SelectService(service);
+  device_->SetServiceState(Service::kStateConfiguring);
+  device_->SetServiceFailure(Service::kFailurePPPAuth);
+  device_->SetServiceFailureSilent(Service::kFailureUnknown);
+  Mock::VerifyAndClearExpectations(service);  // before Cellular dtor
+}
+
+TEST_F(CellularTest, ChangeServiceStatePPP) {
+  MockCellularService *service(SetMockService());
+  scoped_refptr<MockPPPDevice> ppp_device(
+      new MockPPPDevice(modem_info_.control_interface(),
+                        nullptr, nullptr, nullptr, "fake_ppp0", -1));
+  EXPECT_CALL(*ppp_device, SetServiceState(_));
+  EXPECT_CALL(*ppp_device, SetServiceFailure(_));
+  EXPECT_CALL(*ppp_device, SetServiceFailureSilent(_));
+  EXPECT_CALL(*service, SetState(_)).Times(0);
+  EXPECT_CALL(*service, SetFailure(_)).Times(0);
+  EXPECT_CALL(*service, SetFailureSilent(_)).Times(0);
+  device_->ppp_device_ = ppp_device;
+
+  // With PPP, these should all be punted over to the |ppp_device|.
+  // Note in particular that Cellular does not manipulate |service| in
+  // this case.
+  device_->SetServiceState(Service::kStateConfiguring);
+  device_->SetServiceFailure(Service::kFailurePPPAuth);
+  device_->SetServiceFailureSilent(Service::kFailureUnknown);
+}
+
+TEST_F(CellularTest, StopPPPOnDisconnect) {
+  const int kPID = 123;
+  Error error;
+  StartPPP(kPID);
+  FakeUpConnectedPPP();
+  ExpectPPPStopped();
+  device_->Disconnect(&error, "in test");
+  VerifyPPPStopped();
+}
+
+TEST_F(CellularTest, StopPPPOnSuspend) {
+  const int kPID = 123;
+  StartPPP(kPID);
+  FakeUpConnectedPPP();
+  ExpectPPPStopped();
+  device_->OnBeforeSuspend(ResultCallback());
+  VerifyPPPStopped();
+}
+
+TEST_F(CellularTest, OnAfterResumeDisabledWantDisabled) {
+  // The Device was disabled prior to resume, and the profile settings
+  // indicate that the device should be disabled. We should leave
+  // things alone.
+
+  // Initial state.
+  mm1::MockModemProxy *mm1_proxy = SetupOnAfterResume();
+  set_enabled_persistent(false);
+  EXPECT_FALSE(device_->running());
+  EXPECT_FALSE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+
+  // Resume, while device is disabled.
+  EXPECT_CALL(*mm1_proxy, Enable(_, _, _, _)).Times(0);
+  device_->OnAfterResume();
+  EXPECT_FALSE(device_->running());
+  EXPECT_FALSE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+}
+
+TEST_F(CellularTest, OnAfterResumeDisableInProgressWantDisabled) {
+  // The Device was not disabled prior to resume, but the profile
+  // settings indicate that the device _should be_ disabled. Most
+  // likely, we started disabling the device, but that did not
+  // complete before we suspended. We should leave things alone.
+
+  // Initial state.
+  mm1::MockModemProxy *mm1_proxy = SetupOnAfterResume();
+  Error error;
+  EXPECT_CALL(*mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  device_->SetEnabled(true);
+  EXPECT_TRUE(device_->running());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+
+  // Start disable.
+  EXPECT_CALL(*modem_info_.mock_manager(), UpdateDevice(_));
+  device_->SetEnabledPersistent(false, &error, ResultCallback());
+  EXPECT_FALSE(device_->running());  // changes immediately
+  EXPECT_FALSE(device_->enabled_persistent());  // changes immediately
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);  // changes on completion
+
+  // Resume, with disable still in progress.
+  device_->OnAfterResume();
+  EXPECT_FALSE(device_->running());
+  EXPECT_FALSE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+
+  // Finish the disable operation.
+  EXPECT_CALL(*mm1_proxy, Enable(false, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  EXPECT_CALL(*mm1_proxy, SetPowerState(_, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeSetPowerState));
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_FALSE(device_->running());
+  EXPECT_FALSE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+}
+
+TEST_F(CellularTest, OnAfterResumeDisableQueuedWantEnabled) {
+  // The Device was not disabled prior to resume, and the profile
+  // settings indicate that the device should be enabled. In
+  // particular, we went into suspend before we actually processed the
+  // task queued by CellularCapabilityUniversal::StopModem.
+  //
+  // This is unlikely, and a case where we fail to do the right thing.
+  // The tests exists to document this corner case, which we get wrong.
+
+  // Initial state.
+  mm1::MockModemProxy *mm1_proxy = SetupOnAfterResume();
+  EXPECT_CALL(*mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  device_->SetEnabled(true);
+  EXPECT_TRUE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+
+  // Start disable.
+  device_->SetEnabled(false);
+  EXPECT_FALSE(device_->running());  // changes immediately
+  EXPECT_TRUE(device_->enabled_persistent());  // no change
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);  // changes on completion
+
+  // Refresh proxies, since CellularCapabilityUniversal::StartModem wants
+  // new proxies. Also, stash away references for later.
+  PopulateProxies();
+  SetCommonOnAfterResumeExpectations();
+  mm1_proxy = mm1_proxy_.get();
+  auto dbus_properties_proxy = dbus_properties_proxy_.get();
+
+  // Resume, with disable still in progress.
+  EXPECT_CALL(*mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnableReturningWrongState));
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);  // disable still pending
+  device_->OnAfterResume();
+  EXPECT_TRUE(device_->running());  // changes immediately
+  EXPECT_TRUE(device_->enabled_persistent());  // no change
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);  // by OnAfterResume
+
+  // Set up state that we need.
+  DBusPropertiesMap modem_properties;
+  DBus::Variant modem_state;
+  modem_state.writer().append_int32(Cellular::kModemStateDisabled);
+  modem_properties = DBusPropertiesMap{{MM_MODEM_PROPERTY_STATE, modem_state}};
+
+  // Let the disable complete.
+  EXPECT_CALL(*mm1_proxy, Enable(false, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  EXPECT_CALL(*mm1_proxy, SetPowerState(_, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeSetPowerState));
+  EXPECT_CALL(*dbus_properties_proxy, GetAll(_))
+      .WillRepeatedly(Return(modem_properties));
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_TRUE(device_->running());  // last changed by OnAfterResume
+  EXPECT_TRUE(device_->enabled_persistent());  // last changed by OnAfterResume
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+
+  // There's nothing queued up to restart the modem. Even though we
+  // want to be running, we're stuck in the disabled state.
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_TRUE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+}
+
+TEST_F(CellularTest, OnAfterResumePowerDownInProgressWantEnabled) {
+  // The Device was not fully disabled prior to resume, and the
+  // profile settings indicate that the device should be enabled. In
+  // this case, we have disabled the device, but are waiting for the
+  // power-down (switch to low power) to complete.
+  //
+  // This test emulates the behavior of the Huawei E303 dongle, when
+  // Manager::kTerminationActionsTimeoutMilliseconds is 9500
+  // msec. (The dongle takes 10-11 seconds to go through the whole
+  // disable, power-down sequence).
+  //
+  // Eventually, the power-down would complete, and the device would
+  // be stuck in the disabled state. To counter-act that,
+  // OnAfterResume tries to enable the device now, even though the
+  // device is currently enabled.
+
+  // Initial state.
+  mm1::MockModemProxy *mm1_proxy = SetupOnAfterResume();
+  EXPECT_CALL(*mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(Invoke(this, &CellularTest::InvokeEnable));
+  device_->SetEnabled(true);
+  EXPECT_TRUE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+
+  // Start disable.
+  ResultCallback modem_proxy_enable_callback;
+  EXPECT_CALL(*mm1_proxy, Enable(false, _, _, _))
+      .WillOnce(SaveArg<2>(&modem_proxy_enable_callback));
+  device_->SetEnabled(false);
+  dispatcher_.DispatchPendingEvents();  // SetEnabled yields a deferred task
+  EXPECT_FALSE(device_->running());  // changes immediately
+  EXPECT_TRUE(device_->enabled_persistent());  // no change
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);  // changes on completion
+
+  // Let the disable complete. That will trigger power-down.
+  //
+  // Note that, unlike for mm1_proxy->Enable, we don't save the
+  // callback for mm1_proxy->SetPowerState. We expect the callback not
+  // to be executed, as explained in the comment about having a fresh
+  // proxy OnAfterResume, below.
+  Error error;
+  ASSERT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*mm1_proxy, SetPowerState(MM_MODEM_POWER_STATE_LOW, _, _, _))
+      .WillOnce(SetErrorTypeInArgument<1>(Error::kOperationInitiated));
+  modem_proxy_enable_callback.Run(error);
+
+  // No response to power-down yet. It probably completed while the host
+  // was asleep, and so the reply from the modem was lost.
+
+  // Refresh proxies, since CellularCapabilityUniversal::StartModem wants
+  // new proxies. Also, stash away references for later.
+  PopulateProxies();
+  SetCommonOnAfterResumeExpectations();
+  auto new_mm1_proxy = mm1_proxy_.get();
+  auto dbus_properties_proxy = dbus_properties_proxy_.get();
+
+  // Resume.
+  ResultCallback new_callback;
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);  // disable still pending
+  EXPECT_CALL(*new_mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(SaveArg<2>(&modem_proxy_enable_callback));
+  device_->OnAfterResume();
+  EXPECT_TRUE(device_->running());  // changes immediately
+  EXPECT_TRUE(device_->enabled_persistent());  // no change
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);  // by OnAfterResume
+
+  // We should have a fresh proxy OnAfterResume. Otherwise, we may get
+  // confused when the SetPowerState call completes (either naturally,
+  // or via a time-out from dbus-c++).
+  //
+  // The pointers must differ, because the new proxy is constructed
+  // before the old one is destructed.
+  EXPECT_FALSE(new_mm1_proxy == mm1_proxy);
+
+  // Set up state that we need.
+  DBusPropertiesMap modem_properties;
+  DBus::Variant modem_state;
+  modem_state.writer().append_int32(Cellular::kModemStateEnabled);
+  modem_properties = DBusPropertiesMap{{MM_MODEM_PROPERTY_STATE, modem_state}};
+
+  // Let the enable complete.
+  ASSERT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*dbus_properties_proxy, GetAll(_))
+      .WillRepeatedly(Return(modem_properties));
+  ASSERT_TRUE(!modem_proxy_enable_callback.is_null());
+  modem_proxy_enable_callback.Run(error);
+  EXPECT_TRUE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+}
+
+TEST_F(CellularTest, OnAfterResumeDisabledWantEnabled) {
+  // This is the ideal case. The disable process completed before
+  // going into suspend.
+  mm1::MockModemProxy *mm1_proxy = SetupOnAfterResume();
+  EXPECT_FALSE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateDisabled, device_->state_);
+
+  // Resume.
+  ResultCallback modem_proxy_enable_callback;
+  EXPECT_CALL(*mm1_proxy, Enable(true, _, _, _))
+      .WillOnce(SaveArg<2>(&modem_proxy_enable_callback));
+  device_->OnAfterResume();
+
+  // Complete enable.
+  Error error;
+  ASSERT_TRUE(error.IsSuccess());
+  modem_proxy_enable_callback.Run(error);
+  EXPECT_TRUE(device_->running());
+  EXPECT_TRUE(device_->enabled_persistent());
+  EXPECT_EQ(Cellular::kStateEnabled, device_->state_);
+}
+
+// Custom property setters should return false, and make no changes, if
+// the new value is the same as the old value.
+TEST_F(CellularTest, CustomSetterNoopChange) {
+  Error error;
+  EXPECT_FALSE(device_->allow_roaming_);
+  EXPECT_FALSE(device_->SetAllowRoaming(false, &error));
+  EXPECT_TRUE(error.IsSuccess());
+}
+
+TEST_F(CellularTest, ScanImmediateFailure) {
+  Error error;
+
+  device_->set_found_networks(kTestNetworksCellular);
+  EXPECT_FALSE(device_->scanning_);
+  // |InitProxies| must be called before calling any functions on the
+  // Capability*, to set up the modem proxies.
+  // Warning: The test loses all references to the proxies when |InitProxies| is
+  // called.
+  GetCapabilityGSM()->InitProxies();
+  device_->Scan(Device::kFullScan, &error, "");
+  EXPECT_TRUE(error.IsFailure());
+  EXPECT_FALSE(device_->scanning_);
+  EXPECT_EQ(kTestNetworksCellular, device_->found_networks());
+}
+
+TEST_F(CellularTest, ScanAsynchronousFailure) {
+  Error error;
+  ScanResultsCallback results_callback;
+
+  device_->set_found_networks(kTestNetworksCellular);
+  EXPECT_CALL(*gsm_network_proxy_, Scan(&error, _, _))
+      .WillOnce(DoAll(SetErrorTypeInArgument<0>(Error::kOperationInitiated),
+                      SaveArg<1>(&results_callback)));
+  EXPECT_FALSE(device_->scanning_);
+  // |InitProxies| must be called before calling any functions on the
+  // Capability*, to set up the modem proxies.
+  // Warning: The test loses all references to the proxies when |InitProxies| is
+  // called.
+  GetCapabilityGSM()->InitProxies();
+  device_->Scan(Device::kFullScan, &error, "");
+  EXPECT_TRUE(error.IsOngoing());
+  EXPECT_TRUE(device_->scanning_);
+
+  // Asynchronously fail the scan.
+  error.Populate(Error::kOperationFailed);
+  results_callback.Run(kTestNetworksGSM, error);
+  EXPECT_FALSE(device_->scanning_);
+  EXPECT_TRUE(device_->found_networks().empty());
+}
+
+TEST_F(CellularTest, ScanSuccess) {
+  Error error;
+  ScanResultsCallback results_callback;
+
+  device_->clear_found_networks();
+  EXPECT_CALL(*gsm_network_proxy_, Scan(&error, _, _))
+      .WillOnce(DoAll(SetErrorTypeInArgument<0>(Error::kOperationInitiated),
+                      SaveArg<1>(&results_callback)));
+  EXPECT_FALSE(device_->scanning_);
+  // |InitProxies| must be called before calling any functions on the
+  // Capability*, to set up the modem proxies.
+  // Warning: The test loses all references to the proxies when |InitProxies| is
+  // called.
+  GetCapabilityGSM()->InitProxies();
+  device_->Scan(Device::kFullScan, &error, "");
+  EXPECT_TRUE(error.IsOngoing());
+  EXPECT_TRUE(device_->scanning_);
+
+  // Successfully complete the scan.
+  const GSMScanResults gsm_results{};
+  error.Populate(Error::kSuccess);
+  results_callback.Run(kTestNetworksGSM, error);
+  EXPECT_FALSE(device_->scanning_);
+  EXPECT_EQ(kTestNetworksCellular, device_->found_networks());
+}
+
+TEST_F(CellularTest, EstablishLinkDHCP) {
+  unique_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodDHCP);
+  SetCapabilityUniversalActiveBearer(std::move(bearer));
+  device_->state_ = Cellular::kStateConnected;
+
+  MockCellularService *service = SetMockService();
+  ON_CALL(*service, state()).WillByDefault(Return(Service::kStateUnknown));
+
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
+  EXPECT_CALL(dhcp_provider_, CreateConfig(kTestDeviceName, _, _, _))
+      .WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_, RequestIP()).WillOnce(Return(true));
+  EXPECT_CALL(*service, SetState(Service::kStateConfiguring));
+  device_->EstablishLink();
+  EXPECT_EQ(service, device_->selected_service());
+  Mock::VerifyAndClearExpectations(service);  // before Cellular dtor
+}
+
+TEST_F(CellularTest, EstablishLinkPPP) {
+  unique_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodPPP);
+  SetCapabilityUniversalActiveBearer(std::move(bearer));
+  device_->state_ = Cellular::kStateConnected;
+
+  const int kPID = 123;
+  MockGLib &mock_glib(*dynamic_cast<MockGLib *>(modem_info_.glib()));
+  EXPECT_CALL(mock_glib, ChildWatchAdd(kPID, _, _));
+  EXPECT_CALL(mock_glib, SpawnAsync(_, _, _, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgumentPointee<6>(kPID), Return(true)));
+  device_->EstablishLink();
+  EXPECT_FALSE(device_->ipconfig());  // No DHCP client.
+  EXPECT_FALSE(device_->selected_service());
+  EXPECT_FALSE(device_->is_ppp_authenticating_);
+  EXPECT_NE(nullptr, device_->ppp_task_);
+}
+
+TEST_F(CellularTest, EstablishLinkStatic) {
+  IPAddress::Family kAddressFamily = IPAddress::kFamilyIPv4;
+  const char kAddress[] = "10.0.0.1";
+  const char kGateway[] = "10.0.0.254";
+  const int32_t kSubnetPrefix = 16;
+  const char *const kDNS[] = {"10.0.0.2", "8.8.4.4", "8.8.8.8"};
+
+  unique_ptr<IPConfig::Properties> ipconfig_properties(
+      new IPConfig::Properties);
+  ipconfig_properties->address_family = kAddressFamily;
+  ipconfig_properties->address = kAddress;
+  ipconfig_properties->gateway = kGateway;
+  ipconfig_properties->subnet_prefix = kSubnetPrefix;
+  ipconfig_properties->dns_servers = vector<string>{kDNS[0], kDNS[1], kDNS[2]};
+
+  unique_ptr<CellularBearer> bearer(
+      new CellularBearer(&proxy_factory_, "", ""));
+  bearer->set_ipv4_config_method(IPConfig::kMethodStatic);
+  bearer->set_ipv4_config_properties(std::move(ipconfig_properties));
+  SetCapabilityUniversalActiveBearer(std::move(bearer));
+  device_->state_ = Cellular::kStateConnected;
+
+  MockCellularService *service = SetMockService();
+  ON_CALL(*service, state()).WillByDefault(Return(Service::kStateUnknown));
+
+  EXPECT_CALL(device_info_, GetFlags(device_->interface_index(), _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(IFF_UP), Return(true)));
+  EXPECT_CALL(*service, SetState(Service::kStateConfiguring));
+  device_->EstablishLink();
+  EXPECT_EQ(service, device_->selected_service());
+  ASSERT_TRUE(device_->ipconfig());
+  EXPECT_EQ(kAddressFamily, device_->ipconfig()->properties().address_family);
+  EXPECT_EQ(kAddress, device_->ipconfig()->properties().address);
+  EXPECT_EQ(kGateway, device_->ipconfig()->properties().gateway);
+  EXPECT_EQ(kSubnetPrefix, device_->ipconfig()->properties().subnet_prefix);
+  ASSERT_EQ(3, device_->ipconfig()->properties().dns_servers.size());
+  EXPECT_EQ(kDNS[0], device_->ipconfig()->properties().dns_servers[0]);
+  EXPECT_EQ(kDNS[1], device_->ipconfig()->properties().dns_servers[1]);
+  EXPECT_EQ(kDNS[2], device_->ipconfig()->properties().dns_servers[2]);
+  Mock::VerifyAndClearExpectations(service);  // before Cellular dtor
+}
+
+}  // namespace shill
diff --git a/cellular/dbus_objectmanager_proxy.cc b/cellular/dbus_objectmanager_proxy.cc
new file mode 100644
index 0000000..7479cfd
--- /dev/null
+++ b/cellular/dbus_objectmanager_proxy.cc
@@ -0,0 +1,90 @@
+// 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/dbus_objectmanager_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+
+namespace shill {
+
+DBusObjectManagerProxy::DBusObjectManagerProxy(
+    DBus::Connection *connection,
+    const string &path,
+    const string &service)
+    : proxy_(connection, path, service) {}
+DBusObjectManagerProxy::~DBusObjectManagerProxy() {}
+
+void DBusObjectManagerProxy::GetManagedObjects(
+    Error *error,
+    const ManagedObjectsCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetManagedObjectsAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+void DBusObjectManagerProxy::set_interfaces_added_callback(
+      const InterfacesAddedSignalCallback &callback) {
+  proxy_.set_interfaces_added_callback(callback);
+}
+
+void DBusObjectManagerProxy::set_interfaces_removed_callback(
+      const InterfacesRemovedSignalCallback &callback) {
+  proxy_.set_interfaces_removed_callback(callback);
+}
+
+// Inherited from DBusObjectManagerProxyInterface.
+DBusObjectManagerProxy::Proxy::Proxy(DBus::Connection *connection,
+                                     const std::string &path,
+                                     const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+DBusObjectManagerProxy::Proxy::~Proxy() {}
+
+void DBusObjectManagerProxy::Proxy::set_interfaces_added_callback(
+      const InterfacesAddedSignalCallback &callback) {
+  interfaces_added_callback_ = callback;
+}
+
+void DBusObjectManagerProxy::Proxy::set_interfaces_removed_callback(
+      const InterfacesRemovedSignalCallback &callback) {
+  interfaces_removed_callback_ = callback;
+}
+
+// Signal callback
+void DBusObjectManagerProxy::Proxy::InterfacesAdded(
+    const ::DBus::Path &object_path,
+    const DBusInterfaceToProperties &interface_to_properties) {
+  SLOG(&path(), 2) << __func__ << "(" << object_path << ")";
+  interfaces_added_callback_.Run(object_path, interface_to_properties);
+}
+
+// Signal callback
+void DBusObjectManagerProxy::Proxy::InterfacesRemoved(
+    const ::DBus::Path &object_path,
+    const std::vector<std::string> &interfaces) {
+  SLOG(&path(), 2) << __func__ << "(" << object_path << ")";
+  interfaces_removed_callback_.Run(object_path, interfaces);
+}
+
+// Method callback
+void DBusObjectManagerProxy::Proxy::GetManagedObjectsCallback(
+    const DBusObjectsWithProperties &objects_with_properties,
+    const DBus::Error &dberror,
+    void *data) {
+  SLOG(&path(), 2) << __func__;
+  shill::Error error;
+  CellularError::FromDBusError(dberror, &error);
+  std::unique_ptr<ManagedObjectsCallback> callback(
+      reinterpret_cast<ManagedObjectsCallback *>(data));
+
+  callback->Run(objects_with_properties, error);
+}
+
+}  // namespace shill
diff --git a/cellular/dbus_objectmanager_proxy.h b/cellular/dbus_objectmanager_proxy.h
new file mode 100644
index 0000000..13f7cde
--- /dev/null
+++ b/cellular/dbus_objectmanager_proxy.h
@@ -0,0 +1,76 @@
+// 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_DBUS_OBJECTMANAGER_PROXY_H_
+#define SHILL_CELLULAR_DBUS_OBJECTMANAGER_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "shill/cellular/dbus_objectmanager_proxy_interface.h"
+#include "shill/dbus_proxies/dbus-objectmanager.h"
+
+namespace shill {
+
+class DBusObjectManagerProxy : public DBusObjectManagerProxyInterface {
+ public:
+  // Constructs a org.freedesktop.DBus.ObjectManager DBus object proxy
+  // at |path| owned by |service|.
+
+  DBusObjectManagerProxy(DBus::Connection *connection,
+                         const std::string &path,
+                         const std::string &service);
+  ~DBusObjectManagerProxy() override;
+
+  // Inherited methods from DBusObjectManagerProxyInterface.
+  virtual void GetManagedObjects(Error *error,
+                                 const ManagedObjectsCallback &callback,
+                                 int timeout);
+
+  virtual void set_interfaces_added_callback(
+      const InterfacesAddedSignalCallback &callback);
+  virtual void set_interfaces_removed_callback(
+      const InterfacesRemovedSignalCallback &callback);
+
+ private:
+  class Proxy : public org::freedesktop::DBus::ObjectManager_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    virtual void set_interfaces_added_callback(
+        const InterfacesAddedSignalCallback &callback);
+    virtual void set_interfaces_removed_callback(
+        const InterfacesRemovedSignalCallback &callback);
+
+   private:
+    // Signal callbacks
+    virtual void InterfacesAdded(
+        const ::DBus::Path &object_path,
+        const DBusInterfaceToProperties &interfaces_and_properties);
+    virtual void InterfacesRemoved(const ::DBus::Path &object_path,
+                                   const std::vector<std::string> &interfaces);
+
+    // Method callbacks
+    virtual void GetManagedObjectsCallback(
+        const DBusObjectsWithProperties &objects_with_properties,
+        const DBus::Error &error,
+        void *call_handler);
+
+    InterfacesAddedSignalCallback interfaces_added_callback_;
+    InterfacesRemovedSignalCallback interfaces_removed_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(DBusObjectManagerProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_DBUS_OBJECTMANAGER_PROXY_H_
diff --git a/cellular/dbus_objectmanager_proxy_interface.h b/cellular/dbus_objectmanager_proxy_interface.h
new file mode 100644
index 0000000..ebde700
--- /dev/null
+++ b/cellular/dbus_objectmanager_proxy_interface.h
@@ -0,0 +1,52 @@
+// 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_DBUS_OBJECTMANAGER_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_DBUS_OBJECTMANAGER_PROXY_INTERFACE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+
+#include "shill/dbus_properties.h"  // For DBusPropertiesMap
+
+namespace shill {
+
+class Error;
+
+typedef std::map<std::string, DBusPropertiesMap> DBusInterfaceToProperties;
+typedef std::map<::DBus::Path, DBusInterfaceToProperties>
+    DBusObjectsWithProperties;
+typedef base::Callback<void(const DBusObjectsWithProperties &, const Error &)>
+    ManagedObjectsCallback;
+typedef base::Callback<void(const DBusInterfaceToProperties &, const Error &)>
+    InterfaceAndPropertiesCallback;
+typedef base::Callback<void(const DBus::Path &,
+                            const DBusInterfaceToProperties &)>
+    InterfacesAddedSignalCallback;
+typedef base::Callback<void(const DBus::Path &,
+                            const std::vector<std::string> &)>
+    InterfacesRemovedSignalCallback;
+
+// These are the methods that a org.freedesktop.DBus.ObjectManager
+// proxy must support.  The interface is provided so that it can be
+// mocked in tests.  All calls are made asynchronously. Call completion
+// is signalled via the callbacks passed to the methods.
+class DBusObjectManagerProxyInterface {
+ public:
+  virtual ~DBusObjectManagerProxyInterface() {}
+  virtual void GetManagedObjects(Error *error,
+                                 const ManagedObjectsCallback &callback,
+                                 int timeout) = 0;
+  virtual void set_interfaces_added_callback(
+      const InterfacesAddedSignalCallback &callback) = 0;
+  virtual void set_interfaces_removed_callback(
+      const InterfacesRemovedSignalCallback &callback) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_DBUS_OBJECTMANAGER_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_bearer_proxy.cc b/cellular/mm1_bearer_proxy.cc
new file mode 100644
index 0000000..22da205
--- /dev/null
+++ b/cellular/mm1_bearer_proxy.cc
@@ -0,0 +1,71 @@
+// 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/mm1_bearer_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+BearerProxy::BearerProxy(DBus::Connection *connection,
+                         const std::string &path,
+                         const std::string &service)
+    : proxy_(connection, path, service) {}
+
+BearerProxy::~BearerProxy() {}
+
+
+// Inherited methods from BearerProxyInterface
+void BearerProxy::Connect(Error *error,
+                          const ResultCallback &callback,
+                          int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ConnectAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout);
+}
+
+void BearerProxy::Disconnect(Error *error,
+                             const ResultCallback &callback,
+                             int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::DisconnectAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout);
+}
+
+BearerProxy::Proxy::Proxy(DBus::Connection *connection,
+                          const std::string &path,
+                          const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+BearerProxy::Proxy::~Proxy() {}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::BearerProxy
+void BearerProxy::Proxy::ConnectCallback(const ::DBus::Error &dberror,
+                                         void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void BearerProxy::Proxy::DisconnectCallback(const ::DBus::Error &dberror,
+                                            void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_bearer_proxy.h b/cellular/mm1_bearer_proxy.h
new file mode 100644
index 0000000..bdddf13
--- /dev/null
+++ b/cellular/mm1_bearer_proxy.h
@@ -0,0 +1,64 @@
+// 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_MM1_BEARER_PROXY_H_
+#define SHILL_CELLULAR_MM1_BEARER_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Bearer.h"
+#include "shill/cellular/mm1_bearer_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Bearer.
+class BearerProxy : public BearerProxyInterface {
+ public:
+  // Constructs an org.freedesktop.ModemManager1.Bearer DBus object
+  // proxy at |path| owned by |service|.
+  BearerProxy(DBus::Connection *connection,
+              const std::string &path,
+              const std::string &service);
+
+  ~BearerProxy() override;
+
+  // Inherited methods from BearerProxyInterface
+  virtual void Connect(Error *error,
+                       const ResultCallback &callback,
+                       int timeout);
+  virtual void Disconnect(Error *error,
+                          const ResultCallback &callback,
+                          int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Bearer_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::BearerProxy
+    virtual void ConnectCallback(const ::DBus::Error &dberror,
+                                 void *data);
+    virtual void DisconnectCallback(const ::DBus::Error &dberror,
+                                    void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(BearerProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_BEARER_PROXY_H_
diff --git a/cellular/mm1_bearer_proxy_interface.h b/cellular/mm1_bearer_proxy_interface.h
new file mode 100644
index 0000000..b51d8db
--- /dev/null
+++ b/cellular/mm1_bearer_proxy_interface.h
@@ -0,0 +1,37 @@
+// 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_MM1_BEARER_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_BEARER_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+
+class Error;
+
+namespace mm1 {
+
+// These are the methods that an org.freedesktop.ModemManager1.Bearer
+// proxy must support. The interface is provided so that it can be mocked
+// in tests. All calls are made asynchronously. Call completion is signalled
+// via callbacks passed to the methods.
+class BearerProxyInterface {
+ public:
+  virtual ~BearerProxyInterface() {}
+
+  virtual void Connect(Error *error,
+                       const ResultCallback &callback,
+                       int timeout) = 0;
+  virtual void Disconnect(Error *error,
+                          const ResultCallback &callback,
+                          int timeout) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_BEARER_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_location_proxy.cc b/cellular/mm1_modem_location_proxy.cc
new file mode 100644
index 0000000..d84aabf
--- /dev/null
+++ b/cellular/mm1_modem_location_proxy.cc
@@ -0,0 +1,75 @@
+// 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/mm1_modem_location_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+ModemLocationProxy::ModemLocationProxy(DBus::Connection *connection,
+                                       const string &path,
+                                       const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemLocationProxy::~ModemLocationProxy() {}
+
+void ModemLocationProxy::Setup(uint32_t sources,
+                               bool signal_location,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::SetupAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     sources, signal_location);
+}
+
+void ModemLocationProxy::GetLocation(Error *error,
+                                     const DBusEnumValueMapCallback &callback,
+                                     int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetLocationAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout);
+}
+
+ModemLocationProxy::Proxy::Proxy(DBus::Connection *connection,
+                                 const string &path,
+                                 const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemLocationProxy::Proxy::~Proxy() {}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::Modem:LocationProxy
+void ModemLocationProxy::Proxy::SetupCallback(const ::DBus::Error &dberror,
+                                              void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemLocationProxy::Proxy::GetLocationCallback(
+    const DBusEnumValueMap &location,
+    const ::DBus::Error &dberror,
+    void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<DBusEnumValueMapCallback> callback(
+      reinterpret_cast<DBusEnumValueMapCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(location, error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_location_proxy.h b/cellular/mm1_modem_location_proxy.h
new file mode 100644
index 0000000..16d5be0
--- /dev/null
+++ b/cellular/mm1_modem_location_proxy.h
@@ -0,0 +1,69 @@
+// 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_MM1_MODEM_LOCATION_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_LOCATION_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.Location.h"
+#include "shill/cellular/mm1_modem_location_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Bearer.
+class ModemLocationProxy : public ModemLocationProxyInterface {
+ public:
+  // Constructs an org.freedesktop.ModemManager1.Modem.Location DBus
+  // object proxy at |path| owned by |service|.
+  ModemLocationProxy(DBus::Connection *connection,
+                     const std::string &path,
+                     const std::string &service);
+
+  ~ModemLocationProxy() override;
+
+  // Inherited methods from ModemLocationProxyInterface.
+  virtual void Setup(uint32_t sources,
+                     bool signal_location,
+                     Error *error,
+                     const ResultCallback &callback,
+                     int timeout);
+
+  virtual void GetLocation(Error *error,
+                           const DBusEnumValueMapCallback &callback,
+                           int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem::Location_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::Modem:Location_proxy
+    virtual void SetupCallback(const ::DBus::Error &dberror,
+                               void *data);
+
+    virtual void GetLocationCallback(const DBusEnumValueMap &location,
+                                     const ::DBus::Error &dberror,
+                                     void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemLocationProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_LOCATION_PROXY_H_
diff --git a/cellular/mm1_modem_location_proxy_interface.h b/cellular/mm1_modem_location_proxy_interface.h
new file mode 100644
index 0000000..12aaba3
--- /dev/null
+++ b/cellular/mm1_modem_location_proxy_interface.h
@@ -0,0 +1,37 @@
+// 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_MM1_MODEM_LOCATION_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_LOCATION_PROXY_INTERFACE_H_
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+// These are the methods that an org.freedesktop.ModemManager1.Modem.Location
+// proxy must support. The interface is provided so that it can be mocked
+// in tests. All calls are made asynchronously. Call completion is signalled
+// via callbacks passed to the methods.
+class ModemLocationProxyInterface {
+ public:
+  virtual ~ModemLocationProxyInterface() {}
+
+  virtual void Setup(uint32_t sources,
+                     bool signal_location,
+                     Error *error,
+                     const ResultCallback &callback,
+                     int timeout) = 0;
+
+  virtual void GetLocation(Error *error,
+                           const DBusEnumValueMapCallback &callback,
+                           int timeout) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_LOCATION_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_modem3gpp_proxy.cc b/cellular/mm1_modem_modem3gpp_proxy.cc
new file mode 100644
index 0000000..3673f4a
--- /dev/null
+++ b/cellular/mm1_modem_modem3gpp_proxy.cc
@@ -0,0 +1,75 @@
+// 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/mm1_modem_modem3gpp_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+ModemModem3gppProxy::ModemModem3gppProxy(
+    DBus::Connection *connection,
+    const string &path,
+    const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemModem3gppProxy::~ModemModem3gppProxy() {}
+
+void ModemModem3gppProxy::Register(const std::string &operator_id,
+                                   Error *error,
+                                   const ResultCallback &callback,
+                                   int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::RegisterAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     operator_id);
+}
+
+void ModemModem3gppProxy::Scan(Error *error,
+                               const DBusPropertyMapsCallback &callback,
+                               int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ScanAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout);
+}
+
+// ModemModem3gppProxy::Proxy
+ModemModem3gppProxy::Proxy::Proxy(DBus::Connection *connection,
+                                  const std::string &path,
+                                  const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemModem3gppProxy::Proxy::~Proxy() {}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::Modem::ModemModem3gppProxy
+void ModemModem3gppProxy::Proxy::RegisterCallback(const ::DBus::Error& dberror,
+                                                  void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemModem3gppProxy::Proxy::ScanCallback(
+    const std::vector<DBusPropertiesMap> &results,
+    const ::DBus::Error& dberror, void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<DBusPropertyMapsCallback> callback(
+      reinterpret_cast<DBusPropertyMapsCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(results, error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_modem3gpp_proxy.h b/cellular/mm1_modem_modem3gpp_proxy.h
new file mode 100644
index 0000000..8b7de1f
--- /dev/null
+++ b/cellular/mm1_modem_modem3gpp_proxy.h
@@ -0,0 +1,64 @@
+// 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_MM1_MODEM_MODEM3GPP_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_MODEM3GPP_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.Modem3gpp.h"
+#include "shill/cellular/mm1_modem_modem3gpp_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Modem.Modem3gpp.
+class ModemModem3gppProxy : public ModemModem3gppProxyInterface {
+ public:
+  // Constructs an org.freedesktop.ModemManager1.Modem.Modem3gpp DBus
+  // object proxy at |path| owned by |service|.
+  ModemModem3gppProxy(DBus::Connection *connection,
+                      const std::string &path,
+                      const std::string &service);
+  ~ModemModem3gppProxy() override;
+  // Inherited methods from ModemModem3gppProxyInterface.
+  virtual void Register(const std::string &operator_id,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout);
+  virtual void Scan(Error *error,
+                    const DBusPropertyMapsCallback &callback,
+                    int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem::Modem3gpp_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::Modem::Modem3gppProxy
+    virtual void RegisterCallback(const ::DBus::Error& dberror, void *data);
+    virtual void ScanCallback(
+        const std::vector<DBusPropertiesMap> &results,
+        const ::DBus::Error& dberror, void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemModem3gppProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_MODEM3GPP_PROXY_H_
diff --git a/cellular/mm1_modem_modem3gpp_proxy_interface.h b/cellular/mm1_modem_modem3gpp_proxy_interface.h
new file mode 100644
index 0000000..10a8508
--- /dev/null
+++ b/cellular/mm1_modem_modem3gpp_proxy_interface.h
@@ -0,0 +1,38 @@
+// 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_MM1_MODEM_MODEM3GPP_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_MODEM3GPP_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+// These are the methods that a
+// org.freedesktop.ModemManager1.Modem.Modem3gpp proxy must support.
+// The interface is provided so that it can be mocked in tests.
+// All calls are made asynchronously. Call completion is signalled via
+// the callbacks passed to the methods.
+class ModemModem3gppProxyInterface {
+ public:
+  virtual ~ModemModem3gppProxyInterface() {}
+
+  virtual void Register(const std::string &operator_id,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout) = 0;
+  virtual void Scan(Error *error,
+                    const DBusPropertyMapsCallback &callback,
+                    int timeout) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_MODEM3GPP_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_modemcdma_proxy.cc b/cellular/mm1_modem_modemcdma_proxy.cc
new file mode 100644
index 0000000..2a8f59e
--- /dev/null
+++ b/cellular/mm1_modem_modemcdma_proxy.cc
@@ -0,0 +1,97 @@
+// 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/mm1_modem_modemcdma_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+ModemModemCdmaProxy::ModemModemCdmaProxy(DBus::Connection *connection,
+                                         const string &path,
+                                         const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemModemCdmaProxy::~ModemModemCdmaProxy() {}
+
+void ModemModemCdmaProxy::Activate(const std::string &carrier,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ActivateAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     carrier);
+}
+
+void ModemModemCdmaProxy::ActivateManual(
+    const DBusPropertiesMap &properties,
+    Error *error,
+    const ResultCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ActivateManualAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     properties);
+}
+
+void ModemModemCdmaProxy::set_activation_state_callback(
+    const ActivationStateSignalCallback &callback) {
+  proxy_.set_activation_state_callback(callback);
+}
+
+// ModemModemCdmaProxy::Proxy
+ModemModemCdmaProxy::Proxy::Proxy(DBus::Connection *connection,
+                                  const std::string &path,
+                                  const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemModemCdmaProxy::Proxy::~Proxy() {}
+
+void ModemModemCdmaProxy::Proxy::set_activation_state_callback(
+    const ActivationStateSignalCallback &callback) {
+  activation_state_callback_ = callback;
+}
+
+// Signal callbacks inherited from Proxy
+void ModemModemCdmaProxy::Proxy::ActivationStateChanged(
+    const uint32_t &activation_state,
+    const uint32_t &activation_error,
+    const DBusPropertiesMap &status_changes) {
+  SLOG(&path(), 2) << __func__;
+  activation_state_callback_.Run(activation_state,
+                                 activation_error,
+                                 status_changes);
+}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::Modem::ModemModemCdmaProxy
+void ModemModemCdmaProxy::Proxy::ActivateCallback(const ::DBus::Error& dberror,
+                                                  void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemModemCdmaProxy::Proxy::ActivateManualCallback(
+    const ::DBus::Error& dberror,
+    void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_modemcdma_proxy.h b/cellular/mm1_modem_modemcdma_proxy.h
new file mode 100644
index 0000000..d1ae73c
--- /dev/null
+++ b/cellular/mm1_modem_modemcdma_proxy.h
@@ -0,0 +1,79 @@
+// 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_MM1_MODEM_MODEMCDMA_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_MODEMCDMA_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.ModemCdma.h"
+#include "shill/cellular/mm1_modem_modemcdma_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Modem.ModemCdma.
+class ModemModemCdmaProxy : public ModemModemCdmaProxyInterface {
+ public:
+  // Constructs a org.freedesktop.ModemManager1.Modem.ModemCdma DBus
+  // object proxy at |path| owned by |service|.
+  ModemModemCdmaProxy(DBus::Connection *connection,
+                      const std::string &path,
+                      const std::string &service);
+  ~ModemModemCdmaProxy() override;
+
+  // Inherited methods from ModemModemCdmaProxyInterface.
+  virtual void Activate(const std::string &carrier,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout);
+  virtual void ActivateManual(
+      const DBusPropertiesMap &properties,
+      Error *error,
+      const ResultCallback &callback,
+      int timeout);
+
+  virtual void set_activation_state_callback(
+      const ActivationStateSignalCallback &callback);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem::ModemCdma_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    virtual void set_activation_state_callback(
+        const ActivationStateSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from Proxy
+    // handle signals
+    void ActivationStateChanged(
+        const uint32_t &activation_state,
+        const uint32_t &activation_error,
+        const DBusPropertiesMap &status_changes);
+
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::Modem::ModemCdmaProxy
+    virtual void ActivateCallback(const ::DBus::Error& dberror, void *data);
+    virtual void ActivateManualCallback(const ::DBus::Error& dberror,
+                                        void *data);
+    ActivationStateSignalCallback activation_state_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemModemCdmaProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_MODEMCDMA_PROXY_H_
diff --git a/cellular/mm1_modem_modemcdma_proxy_interface.h b/cellular/mm1_modem_modemcdma_proxy_interface.h
new file mode 100644
index 0000000..6fe7fcc
--- /dev/null
+++ b/cellular/mm1_modem_modemcdma_proxy_interface.h
@@ -0,0 +1,43 @@
+// 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_MM1_MODEM_MODEMCDMA_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_MODEMCDMA_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+// These are the methods that a
+// org.freedesktop.ModemManager1.Modem.ModemCdma proxy must support.
+// The interface is provided so that it can be mocked in tests.  All
+// calls are made asynchronously. Call completion is signalled via
+// the callbacks passed to the methods.
+class ModemModemCdmaProxyInterface {
+ public:
+  virtual ~ModemModemCdmaProxyInterface() {}
+
+  virtual void Activate(const std::string &carrier,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout) = 0;
+  virtual void ActivateManual(
+      const DBusPropertiesMap &properties,
+      Error *error,
+      const ResultCallback &callback,
+      int timeout) = 0;
+
+  virtual void set_activation_state_callback(
+      const ActivationStateSignalCallback &callback) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_MODEMCDMA_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_proxy.cc b/cellular/mm1_modem_proxy.cc
new file mode 100644
index 0000000..25d62f7
--- /dev/null
+++ b/cellular/mm1_modem_proxy.cc
@@ -0,0 +1,243 @@
+// 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/mm1_modem_proxy.h"
+
+#include <ModemManager/ModemManager.h>
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+template<typename TraceMsgT, typename CallT, typename CallbackT,
+         typename... ArgTypes>
+void ModemProxy::BeginCall(
+    const TraceMsgT &trace_msg, const CallT &call, const CallbackT &callback,
+    Error *error, int timeout, ArgTypes... rest) {
+  BeginAsyncDBusCall(trace_msg, proxy_, call, callback, error,
+                     &CellularError::FromMM1DBusError, timeout, rest...);
+}
+
+ModemProxy::ModemProxy(DBus::Connection *connection,
+                       const string &path,
+                       const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemProxy::~ModemProxy() {}
+
+void ModemProxy::set_state_changed_callback(
+    const ModemStateChangedSignalCallback &callback) {
+  proxy_.set_state_changed_callback(callback);
+}
+
+void ModemProxy::Enable(bool enable,
+                        Error *error,
+                        const ResultCallback &callback,
+                        int timeout) {
+  SLOG(&proxy_.path(), 2) << __func__ << "(" << enable << ", "
+                                      << timeout << ")";
+  BeginCall(__func__, &Proxy::EnableAsync, callback, error, timeout,
+            enable);
+}
+
+void ModemProxy::CreateBearer(
+    const DBusPropertiesMap &properties,
+    Error *error,
+    const DBusPathCallback &callback,
+    int timeout) {
+  BeginCall(__func__, &Proxy::CreateBearerAsync, callback, error, timeout,
+            properties);
+}
+
+void ModemProxy::DeleteBearer(const ::DBus::Path &bearer,
+                              Error *error,
+                              const ResultCallback &callback,
+                              int timeout) {
+  BeginCall(__func__, &Proxy::DeleteBearerAsync, callback, error, timeout,
+            bearer);
+}
+
+void ModemProxy::Reset(Error *error,
+                       const ResultCallback &callback,
+                       int timeout) {
+  BeginCall(__func__, &Proxy::ResetAsync, callback, error, timeout);
+}
+
+void ModemProxy::FactoryReset(const std::string &code,
+                              Error *error,
+                              const ResultCallback &callback,
+                              int timeout) {
+  BeginCall(__func__, &Proxy::FactoryResetAsync, callback, error, timeout,
+            code);
+}
+
+void ModemProxy::SetCurrentCapabilities(const uint32_t &capabilities,
+                                        Error *error,
+                                        const ResultCallback &callback,
+                                        int timeout) {
+  BeginCall(__func__, &Proxy::SetCurrentCapabilitiesAsync, callback, error,
+            timeout, capabilities);
+}
+
+void ModemProxy::SetCurrentModes(
+    const ::DBus::Struct<uint32_t, uint32_t> &modes,
+    Error *error,
+    const ResultCallback &callback,
+    int timeout) {
+  BeginCall(__func__, &Proxy::SetCurrentModesAsync, callback, error, timeout,
+            modes);
+}
+
+void ModemProxy::SetCurrentBands(const std::vector<uint32_t> &bands,
+                                 Error *error,
+                                 const ResultCallback &callback,
+                                 int timeout) {
+  BeginCall(__func__, &Proxy::SetCurrentBandsAsync, callback, error, timeout,
+            bands);
+}
+
+void ModemProxy::Command(const std::string &cmd,
+                         const uint32_t &user_timeout,
+                         Error *error,
+                         const StringCallback &callback,
+                         int timeout) {
+  BeginCall(__func__, &Proxy::CommandAsync, callback, error, timeout,
+            cmd, user_timeout);
+}
+
+void ModemProxy::SetPowerState(const uint32_t &power_state,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout) {
+  BeginCall(__func__, &Proxy::SetPowerStateAsync, callback, error, timeout,
+            power_state);
+}
+
+ModemProxy::Proxy::Proxy(DBus::Connection *connection,
+                         const std::string &path,
+                         const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemProxy::Proxy::~Proxy() {}
+
+void ModemProxy::Proxy::set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback) {
+  state_changed_callback_ = callback;
+}
+
+// Signal callbacks inherited from Proxy
+void ModemProxy::Proxy::StateChanged(const int32_t &old,
+                                     const int32_t &_new,
+                                     const uint32_t &reason) {
+  SLOG(DBus, &path(), 2) << __func__;
+  if (!state_changed_callback_.is_null())
+    state_changed_callback_.Run(old, _new, reason);
+}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::ModemProxy
+void ModemProxy::Proxy::EnableCallback(const ::DBus::Error &dberror,
+                                       void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::CreateBearerCallback(const ::DBus::Path &path,
+                                             const ::DBus::Error &dberror,
+                                             void *data) {
+  SLOG(DBus, &path, 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::DeleteBearerCallback(const ::DBus::Error &dberror,
+                                             void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::ResetCallback(const ::DBus::Error &dberror,
+                                      void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::FactoryResetCallback(const ::DBus::Error &dberror,
+                                             void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::SetCurrentCapabilitesCallback(
+    const ::DBus::Error &dberror,
+    void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::SetCurrentModesCallback(const ::DBus::Error &dberror,
+                                                void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::SetCurrentBandsCallback(const ::DBus::Error &dberror,
+                                                void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::CommandCallback(const std::string &response,
+                                        const ::DBus::Error &dberror,
+                                        void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::SetPowerStateCallback(const ::DBus::Error &dberror,
+                                              void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_proxy.h b/cellular/mm1_modem_proxy.h
new file mode 100644
index 0000000..d4590ed
--- /dev/null
+++ b/cellular/mm1_modem_proxy.h
@@ -0,0 +1,132 @@
+// 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_MM1_MODEM_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.h"
+#include "shill/cellular/mm1_modem_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Modem.
+class ModemProxy : public ModemProxyInterface {
+ public:
+  // Constructs a org.freedesktop.ModemManager1.Modem DBus object
+  // proxy at |path| owned by |service|.
+  ModemProxy(DBus::Connection *connection,
+             const std::string &path,
+             const std::string &service);
+  ~ModemProxy() override;
+
+  // Inherited methods from ModemProxyInterface.
+  virtual void Enable(bool enable,
+                      Error *error,
+                      const ResultCallback &callback,
+                      int timeout);
+  virtual void CreateBearer(const DBusPropertiesMap &properties,
+                            Error *error,
+                            const DBusPathCallback &callback,
+                            int timeout);
+  virtual void DeleteBearer(const ::DBus::Path &bearer,
+                            Error *error,
+                            const ResultCallback &callback,
+                            int timeout);
+  virtual void Reset(Error *error,
+                     const ResultCallback &callback,
+                     int timeout);
+  virtual void FactoryReset(const std::string &code,
+                            Error *error,
+                            const ResultCallback &callback,
+                            int timeout);
+  virtual void SetCurrentCapabilities(const uint32_t &capabilities,
+                                      Error *error,
+                                      const ResultCallback &callback,
+                                      int timeout);
+  virtual void SetCurrentModes(const ::DBus::Struct<uint32_t, uint32_t> &modes,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout);
+  virtual void SetCurrentBands(const std::vector<uint32_t> &bands,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout);
+  virtual void Command(const std::string &cmd,
+                       const uint32_t &user_timeout,
+                       Error *error,
+                       const StringCallback &callback,
+                       int timeout);
+  virtual void SetPowerState(const uint32_t &power_state,
+                             Error *error,
+                             const ResultCallback &callback,
+                             int timeout);
+
+  virtual void set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    void set_state_changed_callback(
+        const ModemStateChangedSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from Proxy
+    // handle signals
+    void StateChanged(const int32_t &old,
+                      const int32_t &_new,
+                      const uint32_t &reason);
+
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::ModemProxy
+    virtual void EnableCallback(const ::DBus::Error &dberror, void *data);
+    virtual void CreateBearerCallback(const ::DBus::Path &bearer,
+                                      const ::DBus::Error &dberror, void *data);
+    virtual void DeleteBearerCallback(const ::DBus::Error &dberror, void *data);
+    virtual void ResetCallback(const ::DBus::Error &dberror, void *data);
+    virtual void FactoryResetCallback(const ::DBus::Error &dberror, void *data);
+    virtual void SetCurrentCapabilitesCallback(const ::DBus::Error &dberror,
+                                               void *data);
+    virtual void SetCurrentModesCallback(const ::DBus::Error &dberror,
+                                         void *data);
+    virtual void SetCurrentBandsCallback(const ::DBus::Error &dberror,
+                                         void *data);
+    virtual void CommandCallback(const std::string &response,
+                                 const ::DBus::Error &dberror,
+                                 void *data);
+    virtual void SetPowerStateCallback(const ::DBus::Error &dberror,
+                                       void *data);
+
+    ModemStateChangedSignalCallback state_changed_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  template<typename TraceMsgT, typename CallT, typename CallbackT,
+      typename... ArgTypes>
+      void BeginCall(
+          const TraceMsgT &trace_msg, const CallT &call,
+          const CallbackT &callback, Error *error, int timeout,
+          ArgTypes... rest);
+
+  DISALLOW_COPY_AND_ASSIGN(ModemProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_PROXY_H_
diff --git a/cellular/mm1_modem_proxy_interface.h b/cellular/mm1_modem_proxy_interface.h
new file mode 100644
index 0000000..cf76978
--- /dev/null
+++ b/cellular/mm1_modem_proxy_interface.h
@@ -0,0 +1,78 @@
+// 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_MM1_MODEM_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_PROXY_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+typedef base::Callback<void(int32_t,
+                            int32_t, uint32_t)> ModemStateChangedSignalCallback;
+
+// These are the methods that a org.freedesktop.ModemManager1.Modem
+// proxy must support. The interface is provided so that it can be
+// mocked in tests. All calls are made asynchronously. Call completion
+// is signalled via the callbacks passed to the methods.
+class ModemProxyInterface {
+ public:
+  virtual ~ModemProxyInterface() {}
+
+  virtual void Enable(bool enable,
+                      Error *error,
+                      const ResultCallback &callback,
+                      int timeout) = 0;
+  virtual void CreateBearer(const DBusPropertiesMap &properties,
+                            Error *error,
+                            const DBusPathCallback &callback,
+                            int timeout) = 0;
+  virtual void DeleteBearer(const ::DBus::Path &bearer,
+                            Error *error,
+                            const ResultCallback &callback,
+                            int timeout) = 0;
+  virtual void Reset(Error *error,
+                     const ResultCallback &callback,
+                     int timeout) = 0;
+  virtual void FactoryReset(const std::string &code,
+                            Error *error,
+                            const ResultCallback &callback,
+                            int timeout) = 0;
+  virtual void SetCurrentCapabilities(const uint32_t &capabilities,
+                                      Error *error,
+                                      const ResultCallback &callback,
+                                      int timeout) = 0;
+  virtual void SetCurrentModes(const ::DBus::Struct<uint32_t, uint32_t> &modes,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout) = 0;
+  virtual void SetCurrentBands(const std::vector<uint32_t> &bands,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout) = 0;
+  virtual void Command(const std::string &cmd,
+                       const uint32_t &user_timeout,
+                       Error *error,
+                       const StringCallback &callback,
+                       int timeout) = 0;
+  virtual void SetPowerState(const uint32_t &power_state,
+                             Error *error,
+                             const ResultCallback &callback,
+                             int timeout) = 0;
+
+
+  virtual void set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_simple_proxy.cc b/cellular/mm1_modem_simple_proxy.cc
new file mode 100644
index 0000000..cb42a03
--- /dev/null
+++ b/cellular/mm1_modem_simple_proxy.cc
@@ -0,0 +1,97 @@
+// 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/mm1_modem_simple_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+ModemSimpleProxy::ModemSimpleProxy(DBus::Connection *connection,
+                                   const string &path,
+                                   const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemSimpleProxy::~ModemSimpleProxy() {}
+
+void ModemSimpleProxy::Connect(
+    const DBusPropertiesMap &properties,
+    Error *error,
+    const DBusPathCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ConnectAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     properties);
+}
+
+void ModemSimpleProxy::Disconnect(const ::DBus::Path &bearer,
+                                  Error *error,
+                                  const ResultCallback &callback,
+                                  int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::DisconnectAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout,
+                     bearer);
+}
+
+void ModemSimpleProxy::GetStatus(Error *error,
+                                 const DBusPropertyMapCallback &callback,
+                                 int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetStatusAsync, callback,
+                     error, &CellularError::FromMM1DBusError, timeout);
+}
+
+
+// ModemSimpleProxy::Proxy
+ModemSimpleProxy::Proxy::Proxy(DBus::Connection *connection,
+                               const std::string &path,
+                               const std::string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemSimpleProxy::Proxy::~Proxy() {}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::Modem::ModemSimpleProxy
+void ModemSimpleProxy::Proxy::ConnectCallback(const ::DBus::Path &bearer,
+                                              const ::DBus::Error &dberror,
+                                              void *data) {
+  SLOG(&bearer, 2) << __func__;
+  unique_ptr<DBusPathCallback> callback(
+      reinterpret_cast<DBusPathCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(bearer, error);
+}
+
+void ModemSimpleProxy::Proxy::DisconnectCallback(const ::DBus::Error &dberror,
+                                                 void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemSimpleProxy::Proxy::GetStatusCallback(
+    const DBusPropertiesMap &properties,
+    const ::DBus::Error &dberror,
+    void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<DBusPropertyMapCallback> callback(
+      reinterpret_cast<DBusPropertyMapCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(properties, error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_simple_proxy.h b/cellular/mm1_modem_simple_proxy.h
new file mode 100644
index 0000000..c2b5047
--- /dev/null
+++ b/cellular/mm1_modem_simple_proxy.h
@@ -0,0 +1,73 @@
+// 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_MM1_MODEM_SIMPLE_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_SIMPLE_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.Simple.h"
+#include "shill/cellular/mm1_modem_simple_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Modem.Simple.
+class ModemSimpleProxy : public ModemSimpleProxyInterface {
+ public:
+  // Constructs a org.freedesktop.ModemManager1.Modem.Simple DBus
+  // object proxy at |path| owned by |service|.
+  ModemSimpleProxy(DBus::Connection *connection,
+                   const std::string &path,
+                   const std::string &service);
+  ~ModemSimpleProxy() override;
+
+  // Inherited methods from SimpleProxyInterface.
+  virtual void Connect(
+      const DBusPropertiesMap &properties,
+      Error *error,
+      const DBusPathCallback &callback,
+      int timeout);
+  virtual void Disconnect(const ::DBus::Path &bearer,
+                          Error *error,
+                          const ResultCallback &callback,
+                          int timeout);
+  virtual void GetStatus(Error *error,
+                         const DBusPropertyMapCallback &callback,
+                         int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem::Simple_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::Modem::SimpleProxy
+    virtual void ConnectCallback(const ::DBus::Path &bearer,
+                                 const ::DBus::Error &dberror,
+                                 void *data);
+    virtual void DisconnectCallback(const ::DBus::Error &dberror, void *data);
+    virtual void GetStatusCallback(
+        const DBusPropertiesMap &bearer,
+        const ::DBus::Error &dberror,
+        void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemSimpleProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_SIMPLE_PROXY_H_
diff --git a/cellular/mm1_modem_simple_proxy_interface.h b/cellular/mm1_modem_simple_proxy_interface.h
new file mode 100644
index 0000000..8ba256b
--- /dev/null
+++ b/cellular/mm1_modem_simple_proxy_interface.h
@@ -0,0 +1,42 @@
+// 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_MM1_MODEM_SIMPLE_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_SIMPLE_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+// These are the methods that a
+// org.freedesktop.ModemManager1.Modem.Simple proxy must support.  The
+// interface is provided so that it can be mocked in tests.  All calls
+// are made asynchronously. Call completion is signalled via the callbacks
+// passed to the methods.
+class ModemSimpleProxyInterface {
+ public:
+  virtual ~ModemSimpleProxyInterface() {}
+
+  virtual void Connect(const DBusPropertiesMap &properties,
+                       Error *error,
+                       const DBusPathCallback &callback,
+                       int timeout) = 0;
+  virtual void Disconnect(const ::DBus::Path &bearer,
+                          Error *error,
+                          const ResultCallback &callback,
+                          int timeout) = 0;
+  virtual void GetStatus(Error *error,
+                         const DBusPropertyMapCallback &callback,
+                         int timeout) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_SIMPLE_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_modem_time_proxy.cc b/cellular/mm1_modem_time_proxy.cc
new file mode 100644
index 0000000..2ba2e1e
--- /dev/null
+++ b/cellular/mm1_modem_time_proxy.cc
@@ -0,0 +1,71 @@
+// 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/mm1_modem_time_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+ModemTimeProxy::ModemTimeProxy(DBus::Connection *connection,
+                               const string &path,
+                               const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemTimeProxy::~ModemTimeProxy() {}
+
+void ModemTimeProxy::set_network_time_changed_callback(
+    const NetworkTimeChangedSignalCallback &callback) {
+  proxy_.set_network_time_changed_callback(callback);
+}
+
+void ModemTimeProxy::GetNetworkTime(Error *error,
+                                    const StringCallback &callback,
+                                    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetNetworkTimeAsync, callback,
+                     error,  &CellularError::FromMM1DBusError, timeout);
+}
+
+ModemTimeProxy::Proxy::Proxy(DBus::Connection *connection,
+                             const string &path,
+                             const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemTimeProxy::Proxy::~Proxy() {}
+
+void ModemTimeProxy::Proxy::set_network_time_changed_callback(
+        const NetworkTimeChangedSignalCallback &callback) {
+  network_time_changed_callback_ = callback;
+}
+
+// Signal callbacks inherited from Proxy
+void ModemTimeProxy::Proxy::NetworkTimeChanged(const string &time) {
+  SLOG(&path(), 2) << __func__;
+  if (!network_time_changed_callback_.is_null())
+    network_time_changed_callback_.Run(time);
+}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::Modem::TimeProxy
+void ModemTimeProxy::Proxy::GetNetworkTimeCallback(const string &time,
+                                                   const ::DBus::Error &dberror,
+                                                   void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<StringCallback> callback(reinterpret_cast<StringCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(time, error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_modem_time_proxy.h b/cellular/mm1_modem_time_proxy.h
new file mode 100644
index 0000000..36cf07b
--- /dev/null
+++ b/cellular/mm1_modem_time_proxy.h
@@ -0,0 +1,71 @@
+// 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_MM1_MODEM_TIME_PROXY_H_
+#define SHILL_CELLULAR_MM1_MODEM_TIME_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Modem.Time.h"
+#include "shill/cellular/mm1_modem_time_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Modem.Time.
+class ModemTimeProxy : public ModemTimeProxyInterface {
+ public:
+  // Constructs an org.freedesktop.ModemManager1.Modem.Time DBus object
+  // proxy at |path| owned by |service|.
+  ModemTimeProxy(DBus::Connection *connection,
+                 const std::string &path,
+                 const std::string &service);
+  ~ModemTimeProxy() override;
+
+  // Inherited methods from ModemTimeProxyInterface.
+  virtual void GetNetworkTime(Error *error,
+                              const StringCallback &callback,
+                              int timeout);
+
+  virtual void set_network_time_changed_callback(
+      const NetworkTimeChangedSignalCallback &callback);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Modem::Time_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    void set_network_time_changed_callback(
+        const NetworkTimeChangedSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from Proxy
+    // handle signals.
+    void NetworkTimeChanged(const std::string &time);
+
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::Modem::Time_proxy.
+    virtual void GetNetworkTimeCallback(const std::string &time,
+                                        const ::DBus::Error &dberror,
+                                        void *data);
+
+    NetworkTimeChangedSignalCallback network_time_changed_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemTimeProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_TIME_PROXY_H_
diff --git a/cellular/mm1_modem_time_proxy_interface.h b/cellular/mm1_modem_time_proxy_interface.h
new file mode 100644
index 0000000..31e84b1
--- /dev/null
+++ b/cellular/mm1_modem_time_proxy_interface.h
@@ -0,0 +1,39 @@
+// 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_MM1_MODEM_TIME_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_MODEM_TIME_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+class Error;
+
+namespace mm1 {
+
+typedef base::Callback<void(const std::string &)>
+    NetworkTimeChangedSignalCallback;
+
+// These are the methods that an org.freedesktop.ModemManager1.Modem.Time
+// proxy must support. The interface is provided so that it can be mocked
+// in tests. All calls are made asynchronously. Call completion is signalled
+// via callbacks passed to the methods.
+class ModemTimeProxyInterface {
+ public:
+  virtual ~ModemTimeProxyInterface() {}
+
+  virtual void GetNetworkTime(Error *error,
+                              const StringCallback &callback,
+                              int timeout) = 0;
+
+  virtual void set_network_time_changed_callback(
+      const NetworkTimeChangedSignalCallback &callback) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_MODEM_TIME_PROXY_INTERFACE_H_
diff --git a/cellular/mm1_sim_proxy.cc b/cellular/mm1_sim_proxy.cc
new file mode 100644
index 0000000..190925d
--- /dev/null
+++ b/cellular/mm1_sim_proxy.cc
@@ -0,0 +1,127 @@
+// 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/mm1_sim_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+namespace mm1 {
+
+template<typename TraceMsgT, typename CallT, typename CallbackT,
+         typename... ArgTypes>
+void SimProxy::BeginCall(
+    const TraceMsgT &trace_msg, const CallT &call, const CallbackT &callback,
+    Error *error, int timeout, ArgTypes... rest) {
+  BeginAsyncDBusCall(trace_msg, proxy_, call, callback, error,
+                     &CellularError::FromMM1DBusError, timeout, rest...);
+}
+
+SimProxy::SimProxy(DBus::Connection *connection,
+                   const string &path,
+                   const string &service)
+    : proxy_(connection, path, service) {}
+
+SimProxy::~SimProxy() {}
+
+
+void SimProxy::SendPin(const string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout) {
+  // pin is intentionally not logged.
+  SLOG(&proxy_.path(), 2) << __func__ << "( XXX, " << timeout << ")";
+  BeginCall(__func__, &Proxy::SendPinAsync, callback, error, timeout,
+            pin);
+}
+
+void SimProxy::SendPuk(const string &puk,
+                       const string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout) {
+  // pin and puk are intentionally not logged.
+  SLOG(&proxy_.path(), 2) << __func__ << "( XXX, XXX, " << timeout << ")";
+  BeginCall(__func__, &Proxy::SendPukAsync, callback, error, timeout,
+            puk, pin);
+}
+
+void SimProxy::EnablePin(const string &pin,
+                         const bool enabled,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout) {
+  // pin is intentionally not logged.
+  SLOG(&proxy_.path(), 2) << __func__ << "( XXX, "
+                          << enabled << ", " << timeout << ")";
+  BeginCall(__func__, &Proxy::EnablePinAsync, callback, error, timeout,
+            pin, enabled);
+}
+
+void SimProxy::ChangePin(const string &old_pin,
+                         const string &new_pin,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout) {
+  // old_pin and new_pin are intentionally not logged.
+  SLOG(&proxy_.path(), 2) << __func__ << "( XXX, XXX, " << timeout << ")";
+  BeginCall(__func__, &Proxy::ChangePinAsync, callback, error, timeout,
+            old_pin, new_pin);
+}
+
+SimProxy::Proxy::Proxy(DBus::Connection *connection,
+                       const string &path,
+                       const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+SimProxy::Proxy::~Proxy() {}
+
+// Method callbacks inherited from
+// org::freedesktop::ModemManager1::SimProxy
+void SimProxy::Proxy::SendPinCallback(const ::DBus::Error &dberror,
+                                      void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void SimProxy::Proxy::SendPukCallback(const ::DBus::Error &dberror,
+                                      void *data)  {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void SimProxy::Proxy::EnablePinCallback(const ::DBus::Error &dberror,
+                                        void *data)  {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void SimProxy::Proxy::ChangePinCallback(const ::DBus::Error &dberror,
+                                        void *data) {
+  SLOG(DBus, &path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromMM1DBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mm1_sim_proxy.h b/cellular/mm1_sim_proxy.h
new file mode 100644
index 0000000..ce72fd6
--- /dev/null
+++ b/cellular/mm1_sim_proxy.h
@@ -0,0 +1,87 @@
+// 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_MM1_SIM_PROXY_H_
+#define SHILL_CELLULAR_MM1_SIM_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager1.Sim.h"
+#include "shill/cellular/mm1_sim_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+namespace mm1 {
+
+// A proxy to org.freedesktop.ModemManager1.Sim.
+class SimProxy : public SimProxyInterface {
+ public:
+  // Constructs an org.freedesktop.ModemManager1.Sim DBus object
+  // proxy at |path| owned by |service|.
+  SimProxy(DBus::Connection *connection,
+           const std::string &path,
+           const std::string &service);
+  ~SimProxy() override;
+
+  // Inherited methods from SimProxyInterface.
+  virtual void SendPin(const std::string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout);
+  virtual void SendPuk(const std::string &puk,
+                       const std::string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout);
+  virtual void EnablePin(const std::string &pin,
+                         const bool enabled,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout);
+  virtual void ChangePin(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager1::Sim_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Method callbacks inherited from
+    // org::freedesktop::ModemManager1::SimProxy
+    virtual void SendPinCallback(const ::DBus::Error &dberror,
+                                 void *data);
+    virtual void SendPukCallback(const ::DBus::Error &dberror,
+                                 void *data);
+    virtual void EnablePinCallback(const ::DBus::Error &dberror,
+                                   void *data);
+    virtual void ChangePinCallback(const ::DBus::Error &dberror,
+                                   void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  template<typename TraceMsgT, typename CallT, typename CallbackT,
+      typename... ArgTypes>
+      void BeginCall(
+          const TraceMsgT &trace_msg, const CallT &call,
+          const CallbackT &callback, Error *error, int timeout,
+          ArgTypes... rest);
+
+  DISALLOW_COPY_AND_ASSIGN(SimProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_SIM_PROXY_H_
diff --git a/cellular/mm1_sim_proxy_interface.h b/cellular/mm1_sim_proxy_interface.h
new file mode 100644
index 0000000..b7a3769
--- /dev/null
+++ b/cellular/mm1_sim_proxy_interface.h
@@ -0,0 +1,50 @@
+// 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_MM1_SIM_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MM1_SIM_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+
+class Error;
+
+namespace mm1 {
+
+// These are the methods that a org.freedesktop.ModemManager1.Sim
+// proxy must support. The interface is provided so that it can be
+// mocked in tests. All calls are made asynchronously. Call completion
+// is signalled via the callbacks passed to the methods.
+class SimProxyInterface {
+ public:
+  virtual ~SimProxyInterface() {}
+
+  virtual void SendPin(const std::string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout) = 0;
+  virtual void SendPuk(const std::string &puk,
+                       const std::string &pin,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout) = 0;
+  virtual void EnablePin(const std::string &pin,
+                         const bool enabled,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout) = 0;
+  virtual void ChangePin(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error,
+                         const ResultCallback &callback,
+                         int timeout) = 0;
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MM1_SIM_PROXY_INTERFACE_H_
diff --git a/cellular/mobile_operator_info.cc b/cellular/mobile_operator_info.cc
new file mode 100644
index 0000000..988875c
--- /dev/null
+++ b/cellular/mobile_operator_info.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/mobile_operator_info.h"
+
+#include <sstream>
+
+#include "shill/cellular/mobile_operator_info_impl.h"
+#include "shill/logging.h"
+
+namespace shill {
+
+using base::FilePath;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(const MobileOperatorInfo *m) {
+  return "(mobile_operator_info)";
+}
+}
+
+// /////////////////////////////////////////////////////////////////////////////
+// MobileOperatorInfo implementation note:
+// MobileOperatorInfo simply forwards all operations to |impl_|.
+// It also logs the functions/arguments/results at sane log levels. So the
+// implementation need not leave a trace itself.
+
+MobileOperatorInfo::MobileOperatorInfo(EventDispatcher *dispatcher,
+                                       const string &info_owner)
+    : impl_(new MobileOperatorInfoImpl(dispatcher, info_owner)) {}
+
+MobileOperatorInfo::~MobileOperatorInfo() {}
+
+string MobileOperatorInfo::GetLogPrefix(const char *func) const {
+  return impl_->info_owner() + ": " + func;
+}
+
+void MobileOperatorInfo::ClearDatabasePaths() {
+  SLOG(this, 3) << GetLogPrefix(__func__);
+  impl_->ClearDatabasePaths();
+}
+
+void MobileOperatorInfo::AddDatabasePath(const FilePath &absolute_path) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << absolute_path.value()
+                << ")";
+  impl_->AddDatabasePath(absolute_path);
+}
+
+bool MobileOperatorInfo::Init() {
+  auto result = impl_->Init();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+void MobileOperatorInfo::AddObserver(MobileOperatorInfo::Observer *observer) {
+  SLOG(this, 3) << GetLogPrefix(__func__);
+  impl_->AddObserver(observer);
+}
+
+void MobileOperatorInfo::RemoveObserver(
+    MobileOperatorInfo::Observer *observer) {
+  SLOG(this, 3) << GetLogPrefix(__func__);
+  impl_->RemoveObserver(observer);
+}
+
+bool MobileOperatorInfo::IsMobileNetworkOperatorKnown() const {
+  auto result = impl_->IsMobileNetworkOperatorKnown();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+bool MobileOperatorInfo::IsMobileVirtualNetworkOperatorKnown() const {
+  auto result = impl_->IsMobileVirtualNetworkOperatorKnown();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::uuid() const {
+  const auto &result = impl_->uuid();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::operator_name() const {
+  const auto &result = impl_->operator_name();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::country() const {
+  const auto &result = impl_->country();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::mccmnc() const {
+  const auto &result = impl_->mccmnc();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::sid() const {
+  const auto &result = impl_->sid();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const string &MobileOperatorInfo::nid() const {
+  const auto &result = impl_->nid();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+const vector<string> &MobileOperatorInfo::mccmnc_list() const {
+  const auto &result = impl_->mccmnc_list();
+  if (SLOG_IS_ON(Cellular, 3)) {
+    stringstream pp_result;
+    for (const auto &mccmnc : result) {
+      pp_result << mccmnc << " ";
+    }
+    SLOG(this, 3) << GetLogPrefix(__func__) << ": Result["
+                  << pp_result.str() << "]";
+  }
+  return result;
+}
+
+const vector<string> &MobileOperatorInfo::sid_list() const {
+  const auto &result = impl_->sid_list();
+  if (SLOG_IS_ON(Cellular, 3)) {
+    stringstream pp_result;
+    for (const auto &sid : result) {
+      pp_result << sid << " ";
+    }
+    SLOG(this, 3) << GetLogPrefix(__func__) << ": Result["
+                  << pp_result.str() << "]";
+  }
+  return result;
+}
+
+const vector<MobileOperatorInfo::LocalizedName> &
+MobileOperatorInfo::operator_name_list() const {
+  const auto &result = impl_->operator_name_list();
+  if (SLOG_IS_ON(Cellular, 3)) {
+    stringstream pp_result;
+    for (const auto &operator_name : result) {
+      pp_result << "(" << operator_name.name << ", " << operator_name.language
+                << ") ";
+    }
+    SLOG(this, 3) << GetLogPrefix(__func__) << ": Result["
+                  << pp_result.str() << "]";
+  }
+  return result;
+}
+
+const ScopedVector<MobileOperatorInfo::MobileAPN> &
+MobileOperatorInfo::apn_list() const {
+  const auto &result = impl_->apn_list();
+  if (SLOG_IS_ON(Cellular, 3)) {
+    stringstream pp_result;
+    for (const auto &mobile_apn : result) {
+      pp_result << "(apn: " << mobile_apn->apn
+                << ", username: " << mobile_apn->username
+                << ", password: " << mobile_apn->password;
+      pp_result << ", operator_name_list: '";
+      for (const auto &operator_name : mobile_apn->operator_name_list) {
+        pp_result << "(" << operator_name.name << ", " << operator_name.language
+                  << ") ";
+      }
+      pp_result << "') ";
+    }
+    SLOG(this, 3) << GetLogPrefix(__func__) << ": Result["
+                  << pp_result.str() << "]";
+  }
+  return result;
+}
+
+const vector<MobileOperatorInfo::OnlinePortal> &MobileOperatorInfo::olp_list()
+    const {
+  const auto &result = impl_->olp_list();
+  if (SLOG_IS_ON(Cellular, 3)) {
+    stringstream pp_result;
+    for (const auto &olp : result) {
+      pp_result << "(url: " << olp.url << ", method: " << olp.method
+                << ", post_data: " << olp.post_data << ") ";
+    }
+    SLOG(this, 3) << GetLogPrefix(__func__) << ": Result["
+                  << pp_result.str() << "]";
+  }
+  return result;
+}
+
+const string &MobileOperatorInfo::activation_code() const {
+  const auto &result = impl_->activation_code();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+bool MobileOperatorInfo::requires_roaming() const {
+  auto result = impl_->requires_roaming();
+  SLOG(this, 3) << GetLogPrefix(__func__) << ": Result[" << result << "]";
+  return result;
+}
+
+void MobileOperatorInfo::UpdateIMSI(const string &imsi) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << imsi << ")";
+  impl_->UpdateIMSI(imsi);
+}
+
+void MobileOperatorInfo::UpdateICCID(const string &iccid) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << iccid << ")";
+  impl_->UpdateICCID(iccid);
+}
+
+void MobileOperatorInfo::UpdateMCCMNC(const string &mccmnc) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << mccmnc << ")";
+  impl_->UpdateMCCMNC(mccmnc);
+}
+
+void MobileOperatorInfo::UpdateSID(const string &sid) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << sid << ")";
+  impl_->UpdateSID(sid);
+}
+
+void MobileOperatorInfo::UpdateNID(const string &nid) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << nid << ")";
+  impl_->UpdateNID(nid);
+}
+
+void MobileOperatorInfo::UpdateOperatorName(const string &operator_name) {
+  SLOG(this, 3) << GetLogPrefix(__func__) << "(" << operator_name << ")";
+  impl_->UpdateOperatorName(operator_name);
+}
+
+void MobileOperatorInfo::UpdateOnlinePortal(const string &url,
+                                            const string &method,
+                                            const string &post_data) {
+  SLOG(this, 3) << GetLogPrefix(__func__)
+                << "(" << url
+                    << ", " << method
+                    << ", " << post_data << ")";
+  impl_->UpdateOnlinePortal(url, method, post_data);
+}
+
+void MobileOperatorInfo::Reset() {
+  SLOG(this, 3) << GetLogPrefix(__func__);
+  impl_->Reset();
+}
+
+}  // namespace shill
diff --git a/cellular/mobile_operator_info.h b/cellular/mobile_operator_info.h
new file mode 100644
index 0000000..27bd6bc
--- /dev/null
+++ b/cellular/mobile_operator_info.h
@@ -0,0 +1,218 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_CELLULAR_MOBILE_OPERATOR_INFO_H_
+#define SHILL_CELLULAR_MOBILE_OPERATOR_INFO_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/memory/scoped_vector.h>
+
+namespace shill {
+
+class EventDispatcher;
+class MobileOperatorInfoImpl;
+
+// An MobileOperatorInfo object encapsulates the knowledge pertaining to all
+// mobile operators. Typical usage consists of three steps:
+//   - Initialize the object, set database file paths for the operator
+//   information.
+//   - Add observers to be notified whenever an M[V]NO has been determined / any
+//   information about the M[V]NO changes.
+//   - Send operator information updates to the object.
+//
+// So a class Foo that wants to use this object typically looks like:
+//
+// class Foo {
+//   class OperatorObserver : public MobileOperatorInfoObserver {
+//     // Implement all Observer functions.
+//   }
+//   ...
+//
+//   MobileOperatorInfo operator_info;
+//   // Optional: Set a non-default database file.
+//   operator_info.ClearDatabasePaths();
+//   operator_info.AddDatabasePath(some_path);
+//
+//   operator_info.Init();  // Required.
+//
+//   OperatorObserver my_observer;
+//   operator_info.AddObserver(my_observer);
+//   ...
+//   operator_info.UpdateIMSI(some_imsi);
+//   operator_info.UpdateName(some_name);
+//   ...
+//   // Whenever enough information is available, |operator_info| notifies us
+//   through |my_observer|.
+// };
+//
+class MobileOperatorInfo {
+ public:
+  class Observer {
+   public:
+    virtual ~Observer() {}
+
+    // This event fires when
+    //   - A mobile [virtual] network operator
+    //     - is first determined.
+    //     - changes.
+    //     - becomes invalid.
+    //   - Some information about the known operator changes.
+    virtual void OnOperatorChanged() = 0;
+  };
+
+  // |Init| must be called on the constructed object before it is used.
+  // This object does not take ownership of dispatcher, and |dispatcher| is
+  // expected to outlive this object.
+  MobileOperatorInfo(EventDispatcher *dispatcher,
+                     const std::string &info_owner);
+  virtual ~MobileOperatorInfo();
+
+  // These functions can be called before Init to read non default database
+  // file(s).
+  void ClearDatabasePaths();
+  void AddDatabasePath(const base::FilePath &absolute_path);
+
+  std::string GetLogPrefix(const char *func) const;
+  bool Init();
+
+  // Add/remove observers to subscribe to notifications.
+  void AddObserver(MobileOperatorInfo::Observer *observer);
+  void RemoveObserver(MobileOperatorInfo::Observer *observer);
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Objects that encapsulate related information about the mobile operator.
+
+  // Encapsulates a name and the language that name has been localized to.
+  // The name can be a carrier name, or the name that a cellular carrier
+  // prefers to show for a certain access point.
+  struct LocalizedName {
+    // The name as it appears in the corresponding language.
+    std::string name;
+    // The language of this localized name. The format of a language is a two
+    // letter language code, e.g. 'en' for English.
+    // It is legal for an instance of LocalizedName to have an empty |language|
+    // field, as sometimes the underlying database does not contain that
+    // information.
+    std::string language;
+  };
+
+  // Encapsulates information on a mobile access point name. This information
+  // is usually necessary for 3GPP networks to be able to connect to a mobile
+  // network. So far, CDMA networks don't use this information.
+  struct MobileAPN {
+    // The access point url, which is fed to the modemmanager while connecting.
+    std::string apn;
+    // A list of localized names for this access point. Usually there is only
+    // one for each country that the associated cellular carrier operates in.
+    std::vector<LocalizedName> operator_name_list;
+    // The username and password fields that are required by the modemmanager.
+    // Either of these values can be empty if none is present. If a MobileAPN
+    // instance that is obtained from this parser contains a non-empty value
+    // for username/password, this usually means that the carrier requires
+    // a certain default pair.
+    std::string username;
+    std::string password;
+  };
+
+  // Encapsulates information about the Online payment portal used by chrome to
+  // redirect users for some carriers.
+  struct OnlinePortal {
+    std::string url;
+    std::string method;
+    std::string post_data;
+  };
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Functions to obtain information about the current mobile operator.
+  // Any of these accessors can return an emtpy response if the information is
+  // not available. Use |IsMobileNetworkOperatorKnown| and
+  // |IsMobileVirtualNetworkOperatorKnown| to determine if a fix on the operator
+  // has been made. Note that the information returned by the other accessors is
+  // only valid when at least |IsMobileNetworkOperatorKnown| returns true. Their
+  // values are undefined otherwise.
+
+  // Query whether a mobile network operator has been successfully determined.
+  virtual bool IsMobileNetworkOperatorKnown() const;
+  // Query whether a mobile network operator has been successfully
+  // determined.
+  bool IsMobileVirtualNetworkOperatorKnown() const;
+
+
+  // The unique identifier of this carrier. This is primarily used to
+  // identify the user profile in store for each carrier. This identifier is
+  // access technology agnostic and should be the same across 3GPP and CDMA.
+  virtual const std::string &uuid() const;
+
+  virtual const std::string &operator_name() const;
+  virtual const std::string &country() const;
+  virtual const std::string &mccmnc() const;
+  const std::string &sid() const;
+  const std::string &nid() const;
+
+  // A given MVNO can be associated with multiple mcc/mnc pairs. A list of all
+  // associated mcc/mnc pairs concatenated together.
+  const std::vector<std::string> &mccmnc_list() const;
+  // A given MVNO can be associated with multiple sid(s). A list of all
+  // associated sid(s).
+  // There are likely many SID values associated with a CDMA carrier as they
+  // vary across regions and are more fine grained than countries. An important
+  // thing to keep in mind is that, since an SID contains fine grained
+  // information on where a modem is physically located, it should be regarded
+  // as user-sensitive information.
+  const std::vector<std::string> &sid_list() const;
+  // All localized names associated with this carrier entry.
+  const std::vector<LocalizedName> &operator_name_list() const;
+  // All access point names associated with this carrier entry.
+  const ScopedVector<MobileAPN> &apn_list() const;
+  // All Online Payment Portal URLs associated with this carrier entry. There
+  // are usually multiple OLPs based on access technology and it is up to the
+  // application to use the appropriate one.
+  virtual const std::vector<OnlinePortal> &olp_list() const;
+
+  // The number to dial for automatic activation.
+  virtual const std::string &activation_code() const;
+  // Some carriers are only available while roaming. This is mainly used by
+  // Chrome.
+  bool requires_roaming() const;
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Functions used to notify this object of operator data changes.
+  // The Update* methods update the corresponding property of the network
+  // operator, and this value may be used to determine the M[V]NO.
+  // These values are also the values reported through accessors, overriding any
+  // information from the database.
+
+  // Throw away all information provided to the object, and start from top.
+  void Reset();
+
+  // Both MCCMNC and SID correspond to operator code in the different
+  // technologies. They are never to be used together. If you want to use SID
+  // after MCCMNC (or vice-versa), ensure a call to |Reset| to clear state.
+  virtual void UpdateMCCMNC(const std::string &mccmnc);
+  virtual void UpdateSID(const std::string &sid);
+
+  virtual void UpdateIMSI(const std::string &imsi);
+  void UpdateICCID(const std::string &iccid);
+  virtual void UpdateNID(const std::string &nid);
+  virtual void UpdateOperatorName(const std::string &operator_name);
+  void UpdateOnlinePortal(const std::string &url,
+                          const std::string &method,
+                          const std::string &post_data);
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Expose implementation for test purposes only.
+  MobileOperatorInfoImpl * impl() { return impl_.get(); }
+
+ private:
+  std::unique_ptr<MobileOperatorInfoImpl> impl_;
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfo);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOBILE_OPERATOR_INFO_H_
diff --git a/cellular/mobile_operator_info_impl.cc b/cellular/mobile_operator_info_impl.cc
new file mode 100644
index 0000000..43d4f86
--- /dev/null
+++ b/cellular/mobile_operator_info_impl.cc
@@ -0,0 +1,958 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/mobile_operator_info_impl.h"
+
+#include <regex.h>
+
+#include <algorithm>
+#include <cctype>
+#include <map>
+
+#include <base/bind.h>
+#include <base/strings/string_util.h>
+#include <google/protobuf/repeated_field.h>
+
+#include "shill/logging.h"
+#include "shill/protobuf_lite_streams.h"
+
+using base::Bind;
+using base::FilePath;
+using google::protobuf::io::CopyingInputStreamAdaptor;
+using google::protobuf::RepeatedField;
+using google::protobuf::RepeatedPtrField;
+using shill::mobile_operator_db::Data;
+using shill::mobile_operator_db::Filter;
+using shill::mobile_operator_db::LocalizedName;
+using shill::mobile_operator_db::MobileAPN;
+using shill::mobile_operator_db::MobileNetworkOperator;
+using shill::mobile_operator_db::MobileOperatorDB;
+using shill::mobile_operator_db::MobileVirtualNetworkOperator;
+using shill::mobile_operator_db::OnlinePortal;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(const MobileOperatorInfoImpl *m) {
+  return "(mobile_operator_info_impl)";
+}
+}
+
+// static
+const char *MobileOperatorInfoImpl::kDefaultDatabasePath =
+    "/usr/share/shill/serviceproviders.pbf";
+const int MobileOperatorInfoImpl::kMCCMNCMinLen = 5;
+
+namespace {
+
+// Wrap some low level functions from the GNU regex librarly.
+string GetRegError(int code, const regex_t *compiled) {
+  size_t length = regerror(code, compiled, nullptr, 0);
+  vector<char> buffer(length);
+  DCHECK_EQ(length, regerror(code, compiled, buffer.data(), length));
+  return buffer.data();
+}
+
+}  // namespace
+
+MobileOperatorInfoImpl::MobileOperatorInfoImpl(EventDispatcher *dispatcher,
+                                               const string &info_owner)
+    : dispatcher_(dispatcher),
+      info_owner_(info_owner),
+      observers_(ObserverList<MobileOperatorInfo::Observer, true>::NOTIFY_ALL),
+      operator_code_type_(kOperatorCodeTypeUnknown),
+      current_mno_(nullptr),
+      current_mvno_(nullptr),
+      requires_roaming_(false),
+      user_olp_empty_(true),
+      weak_ptr_factory_(this) {
+  AddDatabasePath(FilePath(kDefaultDatabasePath));
+}
+
+MobileOperatorInfoImpl::~MobileOperatorInfoImpl() {}
+
+void MobileOperatorInfoImpl::ClearDatabasePaths() {
+  database_paths_.clear();
+}
+
+void MobileOperatorInfoImpl::AddDatabasePath(const FilePath &absolute_path) {
+  database_paths_.push_back(absolute_path);
+}
+
+bool MobileOperatorInfoImpl::Init() {
+  ScopedVector<MobileOperatorDB> databases;
+
+  // |database_| is guaranteed to be set once |Init| is called.
+  database_.reset(new MobileOperatorDB());
+
+  for (const auto &database_path : database_paths_) {
+    const char *database_path_cstr = database_path.value().c_str();
+    std::unique_ptr<CopyingInputStreamAdaptor> database_stream;
+    database_stream.reset(protobuf_lite_file_input_stream(database_path_cstr));
+    if (!database_stream.get()) {
+      LOG(ERROR) << "Failed to read mobile operator database: "
+                 << database_path_cstr;
+      continue;
+    }
+
+    std::unique_ptr<MobileOperatorDB> database(new MobileOperatorDB());
+    if (!database->ParseFromZeroCopyStream(database_stream.get())) {
+      LOG(ERROR) << "Could not parse mobile operator database: "
+                 << database_path_cstr;
+      continue;
+    }
+    LOG(INFO) << "Successfully loaded database: " << database_path_cstr;
+    // Hand over ownership to the vector.
+    databases.push_back(database.release());
+  }
+
+  // Collate all loaded databases into one.
+  if (databases.size() == 0) {
+    LOG(ERROR) << "Could not read any mobile operator database. "
+               << "Will not be able to determine MVNO.";
+    return false;
+  }
+
+  for (const auto &database : databases) {
+    // TODO(pprabhu) This merge might be very costly. Determine if we need to
+    // implement move semantics / bias the merge to use the largest database
+    // as the base database and merge other databases into it.
+    database_->MergeFrom(*database);
+  }
+  PreprocessDatabase();
+  return true;
+}
+
+void MobileOperatorInfoImpl::AddObserver(
+    MobileOperatorInfo::Observer *observer) {
+  observers_.AddObserver(observer);
+}
+
+void MobileOperatorInfoImpl::RemoveObserver(
+    MobileOperatorInfo::Observer *observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool MobileOperatorInfoImpl::IsMobileNetworkOperatorKnown() const {
+  return (current_mno_ != nullptr);
+}
+
+bool MobileOperatorInfoImpl::IsMobileVirtualNetworkOperatorKnown() const {
+  return (current_mvno_ != nullptr);
+}
+
+// ///////////////////////////////////////////////////////////////////////////
+// Getters.
+const string &MobileOperatorInfoImpl::info_owner() const {
+  return info_owner_;
+}
+
+const string &MobileOperatorInfoImpl::uuid() const {
+  return uuid_;
+}
+
+const string &MobileOperatorInfoImpl::operator_name() const {
+  // TODO(pprabhu) I'm not very sure yet what is the right thing to do here.
+  // It is possible that we obtain a name OTA, and then using some other
+  // information (say the iccid range), determine that this is an MVNO. In
+  // that case, we may want to *override* |user_operator_name_| by the name
+  // obtained from the DB for the MVNO.
+  return operator_name_;
+}
+
+const string &MobileOperatorInfoImpl::country() const {
+  return country_;
+}
+
+const string &MobileOperatorInfoImpl::mccmnc() const {
+  return mccmnc_;
+}
+
+const string &MobileOperatorInfoImpl::MobileOperatorInfoImpl::sid() const {
+  return sid_;
+}
+
+const string &MobileOperatorInfoImpl::nid() const {
+  return (user_nid_ == "") ? nid_ : user_nid_;
+}
+
+const vector<string> &MobileOperatorInfoImpl::mccmnc_list() const {
+  return mccmnc_list_;
+}
+
+const vector<string> &MobileOperatorInfoImpl::sid_list() const {
+  return sid_list_;
+}
+
+const vector<MobileOperatorInfo::LocalizedName> &
+MobileOperatorInfoImpl::operator_name_list() const {
+  return operator_name_list_;
+}
+
+const ScopedVector<MobileOperatorInfo::MobileAPN> &
+MobileOperatorInfoImpl::apn_list() const {
+  return apn_list_;
+}
+
+const vector<MobileOperatorInfo::OnlinePortal> &
+MobileOperatorInfoImpl::olp_list() const {
+  return olp_list_;
+}
+
+const string &MobileOperatorInfoImpl::activation_code() const {
+  return activation_code_;
+}
+
+bool MobileOperatorInfoImpl::requires_roaming() const {
+  return requires_roaming_;
+}
+
+// ///////////////////////////////////////////////////////////////////////////
+// Functions used to notify this object of operator data changes.
+void MobileOperatorInfoImpl::UpdateIMSI(const string &imsi) {
+  bool operator_changed = false;
+  if (user_imsi_ == imsi) {
+    return;
+  }
+
+  user_imsi_ = imsi;
+
+  if (!user_mccmnc_.empty()) {
+    if (!StartsWithASCII(imsi, user_mccmnc_, false)) {
+      LOG(WARNING) << "MCCMNC [" << user_mccmnc_ << "] is not a substring of "
+                   << "the IMSI [" << imsi << "].";
+    }
+  } else {
+    // Attempt to determine the MNO from IMSI since MCCMNC is absent.
+    AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen));
+    AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen + 1));
+    if (!candidates_by_operator_code_.empty()) {
+      // We found some candidates using IMSI.
+      operator_changed |= UpdateMNO();
+    }
+  }
+  operator_changed |= UpdateMVNO();
+
+  // No special notification should be sent for this property, since the object
+  // does not expose |imsi| as a property at all.
+  if (operator_changed) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateICCID(const string &iccid) {
+  if (user_iccid_ == iccid) {
+    return;
+  }
+
+  user_iccid_ = iccid;
+  // |iccid| is not an exposed property, so don't raise event for just this
+  // property update.
+  if (UpdateMVNO()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateMCCMNC(const string &mccmnc) {
+  if (user_mccmnc_ == mccmnc) {
+    return;
+  }
+
+  user_mccmnc_ = mccmnc;
+  HandleMCCMNCUpdate();
+  candidates_by_operator_code_.clear();
+  AppendToCandidatesByMCCMNC(mccmnc);
+
+  // Always update M[V]NO, even if we found no candidates, since we might have
+  // lost some candidates due to an incorrect MCCMNC.
+  bool operator_changed = false;
+  operator_changed |= UpdateMNO();
+  operator_changed |= UpdateMVNO();
+  if (operator_changed || ShouldNotifyPropertyUpdate()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateSID(const string &sid) {
+  if (user_sid_ == sid) {
+    return;
+  }
+
+  user_sid_ = sid;
+  HandleSIDUpdate();
+  candidates_by_operator_code_.clear();
+  AppendToCandidatesBySID(sid);
+
+  // Always update M[V]NO, even if we found no candidates, since we might have
+  // lost some candidates due to an incorrect SID.
+  bool operator_changed = false;
+  operator_changed |= UpdateMNO();
+  operator_changed |= UpdateMVNO();
+  if (operator_changed || ShouldNotifyPropertyUpdate()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateNID(const string &nid) {
+  if (user_nid_ == nid) {
+    return;
+  }
+
+  user_nid_ = nid;
+  if (UpdateMVNO() || ShouldNotifyPropertyUpdate()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateOperatorName(const string &operator_name) {
+  bool operator_changed = false;
+  if (user_operator_name_ == operator_name) {
+    return;
+  }
+
+  user_operator_name_ = operator_name;
+  HandleOperatorNameUpdate();
+
+  // We must update the candidates by name anyway.
+  StringToMNOListMap::const_iterator cit = name_to_mnos_.find(
+      NormalizeOperatorName(operator_name));
+  candidates_by_name_.clear();
+  if (cit != name_to_mnos_.end()) {
+    candidates_by_name_ = cit->second;
+    // We should never have inserted an empty vector into the map.
+    DCHECK(!candidates_by_name_.empty());
+  } else {
+    LOG(INFO) << "Operator name [" << operator_name << "] "
+              << "(Normalized: [" << NormalizeOperatorName(operator_name)
+              << "]) does not match any MNO.";
+  }
+
+  operator_changed |= UpdateMNO();
+  operator_changed |= UpdateMVNO();
+  if (operator_changed || ShouldNotifyPropertyUpdate()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::UpdateOnlinePortal(const string &url,
+                                                const string &method,
+                                                const string &post_data) {
+  if (!user_olp_empty_ &&
+      user_olp_.url == url &&
+      user_olp_.method == method &&
+      user_olp_.post_data == post_data) {
+    return;
+  }
+
+  user_olp_empty_ = false;
+  user_olp_.url = url;
+  user_olp_.method = method;
+  user_olp_.post_data = post_data;
+  HandleOnlinePortalUpdate();
+
+  // OnlinePortal is never used in deciding M[V]NO.
+  if (ShouldNotifyPropertyUpdate()) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::Reset() {
+  bool should_notify = current_mno_ != nullptr || current_mvno_ != nullptr;
+
+  current_mno_ = nullptr;
+  current_mvno_ = nullptr;
+  operator_code_type_ = kOperatorCodeTypeUnknown;
+  candidates_by_operator_code_.clear();
+  candidates_by_name_.clear();
+
+  ClearDBInformation();
+
+  user_imsi_.clear();
+  user_iccid_.clear();
+  user_mccmnc_.clear();
+  user_sid_.clear();
+  user_nid_.clear();
+  user_operator_name_.clear();
+  user_olp_empty_ = true;
+  user_olp_.url.clear();
+  user_olp_.method.clear();
+  user_olp_.post_data.clear();
+
+  if (should_notify) {
+    PostNotifyOperatorChanged();
+  }
+}
+
+void MobileOperatorInfoImpl::PreprocessDatabase() {
+  SLOG(this, 3) << __func__;
+
+  mccmnc_to_mnos_.clear();
+  sid_to_mnos_.clear();
+  name_to_mnos_.clear();
+
+  const RepeatedPtrField<MobileNetworkOperator> &mnos = database_->mno();
+  for (const auto &mno : mnos) {
+    // MobileNetworkOperator::data is a required field.
+    DCHECK(mno.has_data());
+    const Data &data = mno.data();
+
+    const RepeatedPtrField<string> &mccmncs = data.mccmnc();
+    for (const auto &mccmnc : mccmncs) {
+      InsertIntoStringToMNOListMap(&mccmnc_to_mnos_, mccmnc, &mno);
+    }
+
+    const RepeatedPtrField<string> &sids = data.sid();
+    for (const auto &sid : sids) {
+      InsertIntoStringToMNOListMap(&sid_to_mnos_, sid, &mno);
+    }
+
+    const RepeatedPtrField<LocalizedName> &localized_names =
+        data.localized_name();
+    for (const auto &localized_name : localized_names) {
+      // LocalizedName::name is a required field.
+      DCHECK(localized_name.has_name());
+      InsertIntoStringToMNOListMap(&name_to_mnos_,
+                                   NormalizeOperatorName(localized_name.name()),
+                                   &mno);
+    }
+  }
+
+  if (database_->imvno_size() > 0) {
+    // TODO(pprabhu) Support IMVNOs.
+    LOG(ERROR) << "InternationalMobileVirtualNetworkOperators are not "
+               << "supported yet. Ignoring all IMVNOs.";
+  }
+}
+
+// This function assumes that duplicate |values| are never inserted for the
+// same |key|. If you do that, the function is too dumb to deduplicate the
+// |value|s, and two copies will get stored.
+void MobileOperatorInfoImpl::InsertIntoStringToMNOListMap(
+    StringToMNOListMap *table,
+    const string &key,
+    const MobileNetworkOperator *value) {
+  (*table)[key].push_back(value);
+}
+
+bool MobileOperatorInfoImpl::AppendToCandidatesByMCCMNC(const string &mccmnc) {
+  // First check that we haven't determined candidates using SID.
+  if (operator_code_type_ == kOperatorCodeTypeSID) {
+    LOG(WARNING) << "SID update will be overridden by the MCCMNC update for "
+                    "determining MNO.";
+    candidates_by_operator_code_.clear();
+  }
+
+  operator_code_type_ = kOperatorCodeTypeMCCMNC;
+  StringToMNOListMap::const_iterator cit = mccmnc_to_mnos_.find(mccmnc);
+  if (cit == mccmnc_to_mnos_.end()) {
+    LOG(WARNING) << "Unknown MCCMNC value [" << mccmnc << "].";
+    return false;
+  }
+
+  // We should never have inserted an empty vector into the map.
+  DCHECK(!cit->second.empty());
+  for (const auto &mno : cit->second) {
+    candidates_by_operator_code_.push_back(mno);
+  }
+  return true;
+}
+
+bool MobileOperatorInfoImpl::AppendToCandidatesBySID(const string &sid) {
+  // First check that we haven't determined candidates using MCCMNC.
+  if (operator_code_type_ == kOperatorCodeTypeMCCMNC) {
+    LOG(WARNING) << "MCCMNC update will be overriden by the SID update for "
+                    "determining MNO.";
+    candidates_by_operator_code_.clear();
+  }
+
+  operator_code_type_ = kOperatorCodeTypeSID;
+  StringToMNOListMap::const_iterator cit = sid_to_mnos_.find(sid);
+  if (cit == sid_to_mnos_.end()) {
+    LOG(WARNING) << "Unknown SID value [" << sid << "].";
+    return false;
+  }
+
+  // We should never have inserted an empty vector into the map.
+  DCHECK(!cit->second.empty());
+  for (const auto &mno : cit->second) {
+    candidates_by_operator_code_.push_back(mno);
+  }
+  return true;
+}
+
+string MobileOperatorInfoImpl::OperatorCodeString() const {
+  switch (operator_code_type_) {
+    case kOperatorCodeTypeMCCMNC:
+      return "MCCMNC";
+    case kOperatorCodeTypeSID:
+      return "SID";
+    case kOperatorCodeTypeUnknown:  // FALLTHROUGH
+    default:
+      return "UnknownOperatorCodeType";
+  }
+}
+
+bool MobileOperatorInfoImpl::UpdateMNO() {
+  SLOG(this, 3) << __func__;
+  const MobileNetworkOperator *candidate = nullptr;
+
+  // The only way |operator_code_type_| can be |kOperatorCodeTypeUnknown| is
+  // that we haven't received any operator_code updates yet.
+  DCHECK(operator_code_type_ == kOperatorCodeTypeMCCMNC ||
+         operator_code_type_ == kOperatorCodeTypeSID ||
+         (user_mccmnc_.empty() && user_sid_.empty()));
+
+  // TODO(pprabhu) Remove this despicable hack. (crosbug.com/p/30200)
+  // We currently have no principled way to handle an MVNO for which the
+  // database does not have MCCMNC data. It is possible that some other MNO
+  // matches the MCCMNC, while the MVNO matches the operator name. We special
+  // case one such operator here and override all the logic below.
+  const char kCubicUUID[] = "2de39b14-c3ba-4143-abb5-c67a390034ee";
+  for (auto candidate_by_name : candidates_by_name_) {
+    CHECK(candidate_by_name->has_data());
+    CHECK(candidate_by_name->data().has_uuid());
+    if (candidate_by_name->data().uuid() == kCubicUUID) {
+      current_mno_ = candidate_by_name;
+      RefreshDBInformation();
+      return true;
+    }
+  }
+
+  if (candidates_by_operator_code_.size() == 1) {
+    candidate = candidates_by_operator_code_[0];
+    if (candidates_by_name_.size() > 0) {
+      bool found_match = false;
+      for (auto candidate_by_name : candidates_by_name_) {
+        if (candidate_by_name == candidate) {
+          found_match = true;
+          break;
+        }
+      }
+      if (!found_match) {
+        const string &operator_code =
+            (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
+                                                               user_sid_;
+        SLOG(this, 1) << "MNO determined by "
+                      << OperatorCodeString() << " [" << operator_code
+                      << "] does not match any suggested by name["
+                      << user_operator_name_
+                      << "]. "
+                      << OperatorCodeString() << " overrides name!";
+      }
+    }
+  } else if (candidates_by_operator_code_.size() > 1) {
+    // Try to find an intersection of the two candidate lists. These lists
+    // should be almost always of length 1. Simply iterate.
+    for (auto candidate_by_mccmnc : candidates_by_operator_code_) {
+      for (auto candidate_by_name : candidates_by_name_) {
+        if (candidate_by_mccmnc == candidate_by_name) {
+          candidate = candidate_by_mccmnc;
+          break;
+        }
+      }
+      if (candidate != nullptr) {
+        break;
+      }
+    }
+    if (candidate == nullptr) {
+      const string &operator_code =
+          (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
+                                                             user_sid_;
+      SLOG(this, 1) << "MNOs suggested by "
+                    << OperatorCodeString() << " [" << operator_code
+                    << "] are multiple and disjoint from those suggested "
+                    << "by name["
+                    << user_operator_name_
+                    << "].";
+      candidate = PickOneFromDuplicates(candidates_by_operator_code_);
+    }
+  } else {  // candidates_by_operator_code_.size() == 0
+    // Special case: In case we had a *wrong* operator_code update, we want
+    // to override the suggestions from |user_operator_name_|. We should not
+    // determine an MNO in this case.
+    if ((operator_code_type_ == kOperatorCodeTypeMCCMNC &&
+         !user_mccmnc_.empty()) ||
+        (operator_code_type_ == kOperatorCodeTypeSID && !user_sid_.empty())) {
+      SLOG(this, 1) << "A non-matching "
+                    << OperatorCodeString() << " "
+                    << "was reported by the user."
+                    << "We fail the MNO match in this case.";
+    } else if (candidates_by_name_.size() == 1) {
+      candidate = candidates_by_name_[0];
+    } else if (candidates_by_name_.size() > 1) {
+      SLOG(this, 1) << "Multiple MNOs suggested by name["
+                    << user_operator_name_
+                    << "], and none by MCCMNC.";
+      candidate = PickOneFromDuplicates(candidates_by_name_);
+    } else {  // candidates_by_name_.size() == 0
+      SLOG(this, 1) << "No candidates suggested.";
+    }
+  }
+
+  if (candidate != current_mno_) {
+    current_mno_ = candidate;
+    RefreshDBInformation();
+    return true;
+  }
+  return false;
+}
+
+bool MobileOperatorInfoImpl::UpdateMVNO() {
+  SLOG(this, 3) << __func__;
+  if (current_mno_ == nullptr) {
+    return false;
+  }
+
+  for (const auto &candidate_mvno : current_mno_->mvno()) {
+    bool passed_all_filters = true;
+    for (const auto &filter : candidate_mvno.mvno_filter()) {
+      if (!FilterMatches(filter)) {
+        passed_all_filters = false;
+        break;
+      }
+    }
+    if (passed_all_filters) {
+      if (current_mvno_ == &candidate_mvno) {
+        return false;
+      }
+      current_mvno_ = &candidate_mvno;
+      RefreshDBInformation();
+      return true;
+    }
+  }
+
+  // We did not find any valid MVNO.
+  if (current_mvno_ != nullptr) {
+    current_mvno_ = nullptr;
+    RefreshDBInformation();
+    return true;
+  }
+  return false;
+}
+
+const MobileNetworkOperator *MobileOperatorInfoImpl::PickOneFromDuplicates(
+    const vector<const MobileNetworkOperator*> &duplicates) const {
+  if (duplicates.empty())
+    return nullptr;
+
+  for (auto candidate : duplicates) {
+    if (candidate->earmarked()) {
+      SLOG(this, 2) << "Picking earmarked candidate: "
+                    << candidate->data().uuid();
+      return candidate;
+    }
+  }
+  SLOG(this, 2) << "No earmarked candidate found. Choosing the first.";
+  return duplicates[0];
+}
+
+bool MobileOperatorInfoImpl::FilterMatches(const Filter &filter) {
+  DCHECK(filter.has_regex());
+  string to_match;
+  switch (filter.type()) {
+    case mobile_operator_db::Filter_Type_IMSI:
+      to_match = user_imsi_;
+      break;
+    case mobile_operator_db::Filter_Type_ICCID:
+      to_match = user_iccid_;
+      break;
+    case mobile_operator_db::Filter_Type_SID:
+      to_match = user_sid_;
+      break;
+    case mobile_operator_db::Filter_Type_OPERATOR_NAME:
+      to_match = user_operator_name_;
+      break;
+    case mobile_operator_db::Filter_Type_MCCMNC:
+      to_match = user_mccmnc_;
+      break;
+    default:
+      SLOG(this, 1) << "Unknown filter type [" << filter.type() << "]";
+      return false;
+  }
+  // |to_match| can be empty if we have no *user provided* information of the
+  // correct type.
+  if (to_match.empty()) {
+    SLOG(this, 2) << "Nothing to match against (filter: "
+                  << filter.regex() << ").";
+    return false;
+  }
+
+  // Must use GNU regex implementation, since C++11 implementation is
+  // incomplete.
+  regex_t filter_regex;
+  string filter_regex_str = filter.regex();
+
+  // |regexec| matches the given regular expression to a substring of the
+  // given query string. Ensure that |filter_regex_str| uses anchors to
+  // accept only a full match.
+  if (filter_regex_str.front() != '^') {
+    filter_regex_str = "^" + filter_regex_str;
+  }
+  if (filter_regex_str.back() != '$') {
+    filter_regex_str = filter_regex_str + "$";
+  }
+
+  int regcomp_error = regcomp(&filter_regex,
+                              filter_regex_str.c_str(),
+                              REG_EXTENDED | REG_NOSUB);
+  if (regcomp_error) {
+    LOG(WARNING) << "Could not compile regex '" << filter.regex() << "'. "
+                 << "Error returned: "
+                 << GetRegError(regcomp_error, &filter_regex) << ". ";
+    regfree(&filter_regex);
+    return false;
+  }
+
+  int regexec_error = regexec(&filter_regex,
+                              to_match.c_str(),
+                              0,
+                              nullptr,
+                              0);
+  if (regexec_error) {
+    string error_string;
+    error_string = GetRegError(regcomp_error, &filter_regex);
+    SLOG(this, 2) << "Could not match string " << to_match << " "
+                  << "against regexp " << filter.regex() << ". "
+                  << "Error returned: " << error_string << ". ";
+    regfree(&filter_regex);
+    return false;
+  }
+  regfree(&filter_regex);
+  return true;
+}
+
+void MobileOperatorInfoImpl::RefreshDBInformation() {
+  ClearDBInformation();
+
+  if (current_mno_ == nullptr) {
+    return;
+  }
+
+  // |data| is a required field.
+  DCHECK(current_mno_->has_data());
+  SLOG(this, 2) << "Reloading MNO data.";
+  ReloadData(current_mno_->data());
+
+  if (current_mvno_ != nullptr) {
+    // |data| is a required field.
+    DCHECK(current_mvno_->has_data());
+    SLOG(this, 2) << "Reloading MVNO data.";
+    ReloadData(current_mvno_->data());
+  }
+}
+
+void MobileOperatorInfoImpl::ClearDBInformation() {
+  uuid_.clear();
+  country_.clear();
+  nid_.clear();
+  mccmnc_list_.clear();
+  HandleMCCMNCUpdate();
+  sid_list_.clear();
+  HandleSIDUpdate();
+  operator_name_list_.clear();
+  HandleOperatorNameUpdate();
+  apn_list_.clear();
+  olp_list_.clear();
+  raw_olp_list_.clear();
+  HandleOnlinePortalUpdate();
+  activation_code_.clear();
+  requires_roaming_ = false;
+}
+
+void MobileOperatorInfoImpl::ReloadData(const Data &data) {
+  SLOG(this, 3) << __func__;
+  // |uuid_| is *always* overwritten. An MNO and MVNO should not share the
+  // |uuid_|.
+  CHECK(data.has_uuid());
+  uuid_ = data.uuid();
+
+  if (data.has_country()) {
+    country_ = data.country();
+  }
+
+  if (data.localized_name_size() > 0) {
+    operator_name_list_.clear();
+    for (const auto &localized_name : data.localized_name()) {
+      operator_name_list_.push_back({localized_name.name(),
+                                     localized_name.language()});
+    }
+    HandleOperatorNameUpdate();
+  }
+
+  if (data.has_requires_roaming()) {
+    requires_roaming_ = data.requires_roaming();
+  }
+
+  if (data.olp_size() > 0) {
+    raw_olp_list_.clear();
+    // Copy the olp list so we can mutate it.
+    for (const auto &olp : data.olp()) {
+      raw_olp_list_.push_back(olp);
+    }
+    HandleOnlinePortalUpdate();
+  }
+
+  if (data.mccmnc_size() > 0) {
+    mccmnc_list_.clear();
+    for (const auto &mccmnc : data.mccmnc()) {
+      mccmnc_list_.push_back(mccmnc);
+    }
+    HandleMCCMNCUpdate();
+  }
+
+  if (data.mobile_apn_size() > 0) {
+    apn_list_.clear();
+    for (const auto &apn_data : data.mobile_apn()) {
+      auto *apn = new MobileOperatorInfo::MobileAPN();
+      apn->apn = apn_data.apn();
+      apn->username = apn_data.username();
+      apn->password = apn_data.password();
+      for (const auto &localized_name : apn_data.localized_name()) {
+        apn->operator_name_list.push_back({localized_name.name(),
+                                           localized_name.language()});
+      }
+
+      // Takes ownership.
+      apn_list_.push_back(apn);
+    }
+  }
+
+  if (data.sid_size() > 0) {
+    sid_list_.clear();
+    for (const auto &sid : data.sid()) {
+      sid_list_.push_back(sid);
+    }
+    HandleSIDUpdate();
+  }
+
+  if (data.has_activation_code()) {
+    activation_code_ = data.activation_code();
+  }
+}
+
+void MobileOperatorInfoImpl::HandleMCCMNCUpdate() {
+  if (!user_mccmnc_.empty()) {
+    bool append_to_list = true;
+    for (const auto &mccmnc : mccmnc_list_) {
+      append_to_list &= (user_mccmnc_ != mccmnc);
+    }
+    if (append_to_list) {
+      mccmnc_list_.push_back(user_mccmnc_);
+    }
+  }
+
+  if (!user_mccmnc_.empty()) {
+    mccmnc_ = user_mccmnc_;
+  } else if (mccmnc_list_.size() > 0) {
+    mccmnc_ = mccmnc_list_[0];
+  } else {
+    mccmnc_.clear();
+  }
+}
+
+void MobileOperatorInfoImpl::HandleOperatorNameUpdate() {
+  if (!user_operator_name_.empty()) {
+    bool append_user_operator_name = true;
+    for (const auto &localized_name : operator_name_list_) {
+      append_user_operator_name &= (user_operator_name_ != localized_name.name);
+    }
+    if (append_user_operator_name) {
+      MobileOperatorInfo::LocalizedName localized_name {
+          user_operator_name_,
+          ""};
+      operator_name_list_.push_back(localized_name);
+    }
+  }
+
+  if (!operator_name_list_.empty()) {
+    operator_name_ = operator_name_list_[0].name;
+  } else if (!user_operator_name_.empty()) {
+    operator_name_ = user_operator_name_;
+  } else {
+    operator_name_.clear();
+  }
+}
+
+void MobileOperatorInfoImpl::HandleSIDUpdate() {
+  if (!user_sid_.empty()) {
+    bool append_user_sid = true;
+    for (const auto &sid : sid_list_) {
+      append_user_sid &= (user_sid_ != sid);
+    }
+    if (append_user_sid) {
+      sid_list_.push_back(user_sid_);
+    }
+  }
+
+  if (!user_sid_.empty()) {
+    sid_ = user_sid_;
+  } else if (sid_list_.size() > 0) {
+    sid_ = sid_list_[0];
+  } else {
+    sid_.clear();
+  }
+}
+
+// Warning: Currently, an MCCMNC/SID update by itself does not result into
+// recomputation of the |olp_list_|. This means that if the new MCCMNC/SID
+// causes an online portal filter to match, we'll miss that.
+// This won't be a problem if either the MNO or the MVNO changes, since data is
+// reloaded then.
+// This is a corner case that we don't expect to hit, since MCCMNC doesn't
+// really change in a running system.
+void MobileOperatorInfoImpl::HandleOnlinePortalUpdate() {
+  // Always recompute |olp_list_|. We don't expect this list to be big.
+  olp_list_.clear();
+  for (const auto &raw_olp : raw_olp_list_) {
+    if (!raw_olp.has_olp_filter() || FilterMatches(raw_olp.olp_filter())) {
+      olp_list_.push_back(MobileOperatorInfo::OnlinePortal {
+            raw_olp.url(),
+            (raw_olp.method() == raw_olp.GET) ? "GET" : "POST",
+            raw_olp.post_data()});
+    }
+  }
+  if (!user_olp_empty_) {
+    bool append_user_olp = true;
+    for (const auto &olp : olp_list_) {
+      append_user_olp &= (olp.url != user_olp_.url ||
+                          olp.method != user_olp_.method ||
+                          olp.post_data != user_olp_.post_data);
+    }
+    if (append_user_olp) {
+      olp_list_.push_back(user_olp_);
+    }
+  }
+}
+
+void MobileOperatorInfoImpl::PostNotifyOperatorChanged() {
+  SLOG(this, 3) << __func__;
+  // If there was an outstanding task, it will get replaced.
+  notify_operator_changed_task_.Reset(
+      Bind(&MobileOperatorInfoImpl::NotifyOperatorChanged,
+           weak_ptr_factory_.GetWeakPtr()));
+  dispatcher_->PostTask(notify_operator_changed_task_.callback());
+}
+
+void MobileOperatorInfoImpl::NotifyOperatorChanged() {
+  FOR_EACH_OBSERVER(MobileOperatorInfo::Observer,
+                    observers_,
+                    OnOperatorChanged());
+}
+
+bool MobileOperatorInfoImpl::ShouldNotifyPropertyUpdate() const {
+  return IsMobileNetworkOperatorKnown() ||
+         IsMobileVirtualNetworkOperatorKnown();
+}
+
+string MobileOperatorInfoImpl::NormalizeOperatorName(const string &name) const {
+  string result = base::StringToLowerASCII(name);
+  base::RemoveChars(result, base::kWhitespaceASCII, &result);
+  return result;
+}
+
+}  // namespace shill
diff --git a/cellular/mobile_operator_info_impl.h b/cellular/mobile_operator_info_impl.h
new file mode 100644
index 0000000..5e7f0d2
--- /dev/null
+++ b/cellular/mobile_operator_info_impl.h
@@ -0,0 +1,222 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_CELLULAR_MOBILE_OPERATOR_INFO_IMPL_H_
+#define SHILL_CELLULAR_MOBILE_OPERATOR_INFO_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/cancelable_callback.h>
+#include <base/files/file_util.h>
+#include <base/memory/scoped_vector.h>
+#include <base/memory/weak_ptr.h>
+#include <base/observer_list.h>
+
+#include "shill/cellular/mobile_operator_info.h"
+#include "shill/event_dispatcher.h"
+#include "shill/proto_bindings/mobile_operator_db/mobile_operator_db.pb.h"
+
+namespace shill {
+
+class MobileOperatorInfoImpl {
+ public:
+  typedef
+  std::map<std::string,
+           std::vector<const mobile_operator_db::MobileNetworkOperator *>>
+      StringToMNOListMap;
+
+  MobileOperatorInfoImpl(EventDispatcher *dispatcher,
+                         const std::string &info_owner);
+  ~MobileOperatorInfoImpl();
+
+  // API functions of the interface.
+  // See mobile_operator_info_impl.h for details.
+  void ClearDatabasePaths();
+  void AddDatabasePath(const base::FilePath &absolute_path);
+  bool Init();
+  void AddObserver(MobileOperatorInfo::Observer *observer);
+  void RemoveObserver(MobileOperatorInfo::Observer *observer);
+  bool IsMobileNetworkOperatorKnown() const;
+  bool IsMobileVirtualNetworkOperatorKnown() const;
+  const std::string &info_owner() const;
+  const std::string &uuid() const;
+  const std::string &operator_name() const;
+  const std::string &country() const;
+  const std::string &mccmnc() const;
+  const std::string &sid() const;
+  const std::string &nid() const;
+  const std::vector<std::string> &mccmnc_list() const;
+  const std::vector<std::string> &sid_list() const;
+  const std::vector<MobileOperatorInfo::LocalizedName>
+      &operator_name_list() const;
+  const ScopedVector<MobileOperatorInfo::MobileAPN> &apn_list() const;
+  const std::vector<MobileOperatorInfo::OnlinePortal> &olp_list() const;
+  const std::string &activation_code() const;
+  bool requires_roaming() const;
+  void Reset();
+  void UpdateIMSI(const std::string &imsi);
+  void UpdateICCID(const std::string &iccid);
+  void UpdateMCCMNC(const std::string &mccmnc);
+  void UpdateSID(const std::string &sid);
+  void UpdateNID(const std::string &nid);
+  void UpdateOperatorName(const std::string &operator_name);
+  void UpdateOnlinePortal(const std::string &url,
+                          const std::string &method,
+                          const std::string &post_data);
+
+ private:
+  friend class MobileOperatorInfoInitTest;
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Static variables.
+  // Default databases to load.
+  static const char *kDefaultDatabasePath;
+  // MCCMNC can be of length 5 or 6. When using this constant, keep in mind that
+  // the length of MCCMNC can by |kMCCMNCMinLen| or |kMCCMNCMinLen + 1|.
+  static const int kMCCMNCMinLen;
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Functions.
+  void PreprocessDatabase();
+  // This function assumes that duplicate |values| are never inserted for the
+  // same |key|. If you do that, the function is too dumb to deduplicate the
+  // |value|s, and two copies will get stored.
+  void InsertIntoStringToMNOListMap(
+      StringToMNOListMap *table,
+      const std::string &key,
+      const mobile_operator_db::MobileNetworkOperator *value);
+
+  bool UpdateMNO();
+  bool UpdateMVNO();
+  bool FilterMatches(const shill::mobile_operator_db::Filter &filter);
+  const mobile_operator_db::MobileNetworkOperator *PickOneFromDuplicates(
+      const std::vector<const mobile_operator_db::MobileNetworkOperator*>
+          &duplicates) const;
+  // Reloads the information about M[V]NO from the database.
+  void RefreshDBInformation();
+  void ClearDBInformation();
+  // Reload all data from |data|.
+  // Semantics: If a field data.x exists, then it *overwrites* the current
+  // information gained from data.x. E.g., if |data.name_size() > 0| is true,
+  // then we replace *all* names. Otherwise, we leave names untouched.
+  // This allows MVNOs to overwrite information obtained from the corresponding
+  // MNO.
+  void ReloadData(const mobile_operator_db::Data &data);
+  // Append candidates recognized by |mccmnc| to the candidate list.
+  bool AppendToCandidatesByMCCMNC(const std::string &mccmnc);
+  bool AppendToCandidatesBySID(const std::string &sid);
+  std::string OperatorCodeString() const;
+
+  // Notifies all observers that the operator has changed.
+  void PostNotifyOperatorChanged();
+  // The actual notification is sent out here. This should not be called
+  // directly from any function.
+  void NotifyOperatorChanged();
+
+  // For a property update that does not result in an M[V]NO update, this
+  // function determines whether observers should be notified anyway.
+  bool ShouldNotifyPropertyUpdate() const;
+
+  // OperatorName comparisons for determining the MNO are done after normalizing
+  // the names to ignore case and spaces.
+  std::string NormalizeOperatorName(const std::string &name) const;
+
+  // These functions encapsulate the logic to update different properties
+  // properly whenever an update is either received from the user or the
+  // database.
+  void HandleMCCMNCUpdate();
+  void HandleOperatorNameUpdate();
+  void HandleSIDUpdate();
+  void HandleOnlinePortalUpdate();
+
+  // Accessor functions for testing purpose only.
+  mobile_operator_db::MobileOperatorDB *database() {
+    return database_.get();
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Data.
+  // Not owned by MobileOperatorInfoImpl.
+  EventDispatcher *const dispatcher_;
+
+  const std::string info_owner_;
+
+  // Owned by MobileOperatorInfoImpl, may be created externally.
+  std::vector<base::FilePath> database_paths_;
+
+  // Owned and modified only by MobileOperatorInfoImpl.
+  // The observers added to this list are not owned by this object. Moreover,
+  // the observer is likely to outlive this object. We do enforce removal of all
+  // observers before this object is destroyed.
+  ObserverList<MobileOperatorInfo::Observer> observers_;
+  base::CancelableClosure notify_operator_changed_task_;
+
+  std::unique_ptr<mobile_operator_db::MobileOperatorDB> database_;
+  StringToMNOListMap mccmnc_to_mnos_;
+  StringToMNOListMap sid_to_mnos_;
+  StringToMNOListMap name_to_mnos_;
+
+  // |candidates_by_operator_code| can be determined either using MCCMNC or
+  // using SID.  At any one time, we only expect one of these operator codes to
+  // be updated by the user. We use |operator_code_type_| to keep track of which
+  // update we have received and warn the user if we receive both.
+  enum OperatorCodeType {
+    kOperatorCodeTypeUnknown = 0,
+    kOperatorCodeTypeMCCMNC,
+    kOperatorCodeTypeSID,
+  };
+  OperatorCodeType operator_code_type_;
+  std::vector<const mobile_operator_db::MobileNetworkOperator *>
+      candidates_by_operator_code_;
+
+  std::vector<const mobile_operator_db::MobileNetworkOperator *>
+      candidates_by_name_;
+  const mobile_operator_db::MobileNetworkOperator *current_mno_;
+  const mobile_operator_db::MobileVirtualNetworkOperator *current_mvno_;
+
+  // These fields are the information expected to be populated by this object
+  // after successfully determining the MVNO.
+  std::string uuid_;
+  std::string operator_name_;
+  std::string country_;
+  std::string mccmnc_;
+  std::string sid_;
+  std::string nid_;
+  std::vector<std::string> mccmnc_list_;
+  std::vector<std::string> sid_list_;
+  std::vector<MobileOperatorInfo::LocalizedName> operator_name_list_;
+  ScopedVector<MobileOperatorInfo::MobileAPN> apn_list_;
+  std::vector<MobileOperatorInfo::OnlinePortal> olp_list_;
+  std::vector<mobile_operator_db::OnlinePortal> raw_olp_list_;
+  std::string activation_code_;
+  bool requires_roaming_;
+  // These fields store the data obtained from the Update* methods.
+  // The database information is kept separate from the information gathered
+  // through the Update* methods, because one or the other may be given
+  // precedence in different situations.
+  // Note: For simplicity, we do not allow the user to enforce an empty value
+  // for these variables. So, if |user_mccmnc_| == "", the |mccmnc_| obtained
+  // from the database will be used, even if |user_mccmnc_| was explicitly set
+  // by the user.
+  std::string user_imsi_;
+  std::string user_iccid_;
+  std::string user_mccmnc_;
+  std::string user_sid_;
+  std::string user_nid_;
+  std::string user_operator_name_;
+  bool user_olp_empty_;
+  MobileOperatorInfo::OnlinePortal user_olp_;
+
+  // This must be the last data member of this class.
+  base::WeakPtrFactory<MobileOperatorInfoImpl> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoImpl);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOBILE_OPERATOR_INFO_IMPL_H_
diff --git a/cellular/mobile_operator_info_unittest.cc b/cellular/mobile_operator_info_unittest.cc
new file mode 100644
index 0000000..c865a6e
--- /dev/null
+++ b/cellular/mobile_operator_info_unittest.cc
@@ -0,0 +1,1616 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/mobile_operator_info.h"
+
+#include <fstream>
+#include <map>
+#include <ostream>
+#include <set>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/macros.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "shill/cellular/mobile_operator_info_impl.h"
+#include "shill/event_dispatcher.h"
+#include "shill/logging.h"
+
+// These files contain binary protobuf definitions used by the following tests
+// inside the namespace ::mobile_operator_db
+#define IN_MOBILE_OPERATOR_INFO_UNITTEST_CC
+#include "shill/mobile_operator_db/test_protos/data_test.h"
+#include "shill/mobile_operator_db/test_protos/init_test_empty_db_init.h"
+#include "shill/mobile_operator_db/test_protos/init_test_multiple_db_init_1.h"
+#include "shill/mobile_operator_db/test_protos/init_test_multiple_db_init_2.h"
+#include "shill/mobile_operator_db/test_protos/init_test_successful_init.h"
+#include "shill/mobile_operator_db/test_protos/main_test.h"
+#undef IN_MOBILE_OPERATOR_INFO_UNITTEST_CC
+
+using base::FilePath;
+using shill::mobile_operator_db::MobileOperatorDB;
+using std::map;
+using std::ofstream;
+using std::set;
+using std::string;
+using std::vector;
+using testing::Mock;
+using testing::Test;
+using testing::Values;
+using testing::WithParamInterface;
+
+// The tests run from the fixture |MobileOperatorInfoMainTest| and
+// |MobileOperatorDataTest| can be run in two modes:
+//   - strict event checking: We check that an event is raised for each update
+//     to the state of the object.
+//   - non-strict event checking: We check that a single event is raised as a
+//     result of many updates to the object.
+// The first case corresponds to a very aggressive event loop, that dispatches
+// events as soon as they are posted; the second one corresponds to an
+// over-crowded event loop that only dispatches events just before we verify
+// that events were raised.
+//
+// We use ::testing::WithParamInterface to templatize the test fixtures to do
+// string/non-strict event checking. When writing test cases using these
+// fixtures, use the |Update*|, |ExpectEventCount|, |VerifyEventCount| functions
+// provided by the fixture, and write the test as if event checking is strict.
+//
+// For |MobileOperatorObserverTest|, only the strict event checking case makes
+// sense, so we only instantiate that.
+namespace shill {
+
+namespace {
+
+enum EventCheckingPolicy {
+  kEventCheckingPolicyStrict,
+  kEventCheckingPolicyNonStrict
+};
+
+}  // namespace
+
+class MockMobileOperatorInfoObserver : public MobileOperatorInfo::Observer {
+ public:
+  MockMobileOperatorInfoObserver() {}
+  virtual ~MockMobileOperatorInfoObserver() {}
+
+  MOCK_METHOD0(OnOperatorChanged, void());
+};
+
+class MobileOperatorInfoInitTest : public Test {
+ public:
+  MobileOperatorInfoInitTest()
+      : operator_info_(new MobileOperatorInfo(&dispatcher_, "Operator")),
+        operator_info_impl_(operator_info_->impl()) {}
+
+  void TearDown() override {
+    for (const auto &tmp_db_path : tmp_db_paths_) {
+      base::DeleteFile(tmp_db_path, false);
+    }
+  }
+
+ protected:
+  void AddDatabase(const unsigned char database_data[], size_t num_elems) {
+    FilePath tmp_db_path;
+    CHECK(base::CreateTemporaryFile(&tmp_db_path));
+    tmp_db_paths_.push_back(tmp_db_path);
+
+    ofstream tmp_db(tmp_db_path.value(), ofstream::binary);
+    for (size_t i = 0; i < num_elems; ++i) {
+      tmp_db << database_data[i];
+    }
+    tmp_db.close();
+    operator_info_->AddDatabasePath(tmp_db_path);
+  }
+
+  void AssertDatabaseEmpty() {
+    EXPECT_EQ(0, operator_info_impl_->database()->mno_size());
+    EXPECT_EQ(0, operator_info_impl_->database()->imvno_size());
+  }
+
+  const MobileOperatorDB *GetDatabase() {
+    return operator_info_impl_->database();
+  }
+
+  EventDispatcher dispatcher_;
+  vector<FilePath> tmp_db_paths_;
+  std::unique_ptr<MobileOperatorInfo> operator_info_;
+  // Owned by |operator_info_| and tied to its life cycle.
+  MobileOperatorInfoImpl *operator_info_impl_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoInitTest);
+};
+
+TEST_F(MobileOperatorInfoInitTest, FailedInitNoPath) {
+  // - Initialize object with no database paths set
+  // - Verify that initialization fails.
+  operator_info_->ClearDatabasePaths();
+  EXPECT_FALSE(operator_info_->Init());
+  AssertDatabaseEmpty();
+}
+
+TEST_F(MobileOperatorInfoInitTest, FailedInitBadPath) {
+  // - Initialize object with non-existent path.
+  // - Verify that initialization fails.
+  const FilePath database_path("nonexistent.pbf");
+  operator_info_->ClearDatabasePaths();
+  operator_info_->AddDatabasePath(database_path);
+  EXPECT_FALSE(operator_info_->Init());
+  AssertDatabaseEmpty();
+}
+
+TEST_F(MobileOperatorInfoInitTest, FailedInitBadDatabase) {
+  // - Initialize object with malformed database.
+  // - Verify that initialization fails.
+  // TODO(pprabhu): It's hard to get a malformed database in binary format.
+}
+
+TEST_F(MobileOperatorInfoInitTest, EmptyDBInit) {
+  // - Initialize the object with a database file that is empty.
+  // - Verify that initialization succeeds, and that the database is empty.
+  operator_info_->ClearDatabasePaths();
+  // Can't use arraysize on empty array.
+  AddDatabase(mobile_operator_db::init_test_empty_db_init, 0);
+  EXPECT_TRUE(operator_info_->Init());
+  AssertDatabaseEmpty();
+}
+
+TEST_F(MobileOperatorInfoInitTest, SuccessfulInit) {
+  operator_info_->ClearDatabasePaths();
+  AddDatabase(mobile_operator_db::init_test_successful_init,
+              arraysize(mobile_operator_db::init_test_successful_init));
+  EXPECT_TRUE(operator_info_->Init());
+  EXPECT_GT(GetDatabase()->mno_size(), 0);
+  EXPECT_GT(GetDatabase()->imvno_size(), 0);
+}
+
+TEST_F(MobileOperatorInfoInitTest, MultipleDBInit) {
+  // - Initialize the object with two database files.
+  // - Verify that intialization succeeds, and both databases are loaded.
+  operator_info_->ClearDatabasePaths();
+  AddDatabase(mobile_operator_db::init_test_multiple_db_init_1,
+              arraysize(mobile_operator_db::init_test_multiple_db_init_1));
+  AddDatabase(mobile_operator_db::init_test_multiple_db_init_2,
+              arraysize(mobile_operator_db::init_test_multiple_db_init_2));
+  operator_info_->Init();
+  EXPECT_GT(GetDatabase()->mno_size(), 0);
+  EXPECT_GT(GetDatabase()->imvno_size(), 0);
+}
+
+TEST_F(MobileOperatorInfoInitTest, InitWithObserver) {
+  // - Add an Observer.
+  // - Initialize the object with empty database file.
+  // - Verify innitialization succeeds.
+  MockMobileOperatorInfoObserver dumb_observer;
+
+  operator_info_->ClearDatabasePaths();
+  // Can't use arraysize with empty array.
+  AddDatabase(mobile_operator_db::init_test_empty_db_init, 0);
+  operator_info_->AddObserver(&dumb_observer);
+  EXPECT_TRUE(operator_info_->Init());
+}
+
+class MobileOperatorInfoMainTest
+    : public MobileOperatorInfoInitTest,
+      public WithParamInterface<EventCheckingPolicy> {
+ public:
+  MobileOperatorInfoMainTest()
+      : MobileOperatorInfoInitTest(),
+        event_checking_policy_(GetParam()) {}
+
+  virtual void SetUp() {
+    operator_info_->ClearDatabasePaths();
+    AddDatabase(mobile_operator_db::main_test,
+                arraysize(mobile_operator_db::main_test));
+    operator_info_->Init();
+    operator_info_->AddObserver(&observer_);
+  }
+
+ protected:
+  // ///////////////////////////////////////////////////////////////////////////
+  // Helper functions.
+  void VerifyMNOWithUUID(const string &uuid) {
+    EXPECT_TRUE(operator_info_->IsMobileNetworkOperatorKnown());
+    EXPECT_FALSE(operator_info_->IsMobileVirtualNetworkOperatorKnown());
+    EXPECT_EQ(uuid, operator_info_->uuid());
+  }
+
+  void VerifyMVNOWithUUID(const string &uuid) {
+    EXPECT_TRUE(operator_info_->IsMobileNetworkOperatorKnown());
+    EXPECT_TRUE(operator_info_->IsMobileVirtualNetworkOperatorKnown());
+    EXPECT_EQ(uuid, operator_info_->uuid());
+  }
+
+  void VerifyNoMatch() {
+    EXPECT_FALSE(operator_info_->IsMobileNetworkOperatorKnown());
+    EXPECT_FALSE(operator_info_->IsMobileVirtualNetworkOperatorKnown());
+    EXPECT_EQ("", operator_info_->uuid());
+  }
+
+  void ExpectEventCount(int count) {
+    // In case we're running in the non-strict event checking mode, we only
+    // expect one overall event to be raised for all the updates.
+    if (event_checking_policy_ == kEventCheckingPolicyNonStrict) {
+      count = (count > 0) ? 1 : 0;
+    }
+    EXPECT_CALL(observer_, OnOperatorChanged()).Times(count);
+  }
+
+  void VerifyEventCount() {
+    dispatcher_.DispatchPendingEvents();
+    Mock::VerifyAndClearExpectations(&observer_);
+  }
+
+  void ResetOperatorInfo() {
+    operator_info_->Reset();
+    // Eat up any events caused by |Reset|.
+    dispatcher_.DispatchPendingEvents();
+    VerifyNoMatch();
+  }
+
+  // Use these wrappers to send updates to |operator_info_|. These wrappers
+  // optionally run the dispatcher if we want strict checking of the number of
+  // events raised.
+  void UpdateMCCMNC(const std::string &mccmnc) {
+    operator_info_->UpdateMCCMNC(mccmnc);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateSID(const std::string &sid) {
+    operator_info_->UpdateSID(sid);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateIMSI(const std::string &imsi) {
+    operator_info_->UpdateIMSI(imsi);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateICCID(const std::string &iccid) {
+    operator_info_->UpdateICCID(iccid);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateNID(const std::string &nid) {
+    operator_info_->UpdateNID(nid);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateOperatorName(const std::string &operator_name) {
+    operator_info_->UpdateOperatorName(operator_name);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void UpdateOnlinePortal(const std::string &url,
+                          const std::string &method,
+                          const std::string &post_data) {
+    operator_info_->UpdateOnlinePortal(url, method, post_data);
+    DispatchPendingEventsIfStrict();
+  }
+
+  void DispatchPendingEventsIfStrict() {
+    if (event_checking_policy_ == kEventCheckingPolicyStrict) {
+      dispatcher_.DispatchPendingEvents();
+    }
+  }
+
+  // ///////////////////////////////////////////////////////////////////////////
+  // Data.
+  MockMobileOperatorInfoObserver observer_;
+  const EventCheckingPolicy event_checking_policy_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoMainTest);
+};
+
+TEST_P(MobileOperatorInfoMainTest, InitialConditions) {
+  // - Initialize a new object.
+  // - Verify that all initial values of properties are reasonable.
+  EXPECT_FALSE(operator_info_->IsMobileNetworkOperatorKnown());
+  EXPECT_FALSE(operator_info_->IsMobileVirtualNetworkOperatorKnown());
+  EXPECT_TRUE(operator_info_->uuid().empty());
+  EXPECT_TRUE(operator_info_->operator_name().empty());
+  EXPECT_TRUE(operator_info_->country().empty());
+  EXPECT_TRUE(operator_info_->mccmnc().empty());
+  EXPECT_TRUE(operator_info_->sid().empty());
+  EXPECT_TRUE(operator_info_->nid().empty());
+  EXPECT_TRUE(operator_info_->mccmnc_list().empty());
+  EXPECT_TRUE(operator_info_->sid_list().empty());
+  EXPECT_TRUE(operator_info_->operator_name_list().empty());
+  EXPECT_TRUE(operator_info_->apn_list().empty());
+  EXPECT_TRUE(operator_info_->olp_list().empty());
+  EXPECT_TRUE(operator_info_->activation_code().empty());
+  EXPECT_FALSE(operator_info_->requires_roaming());
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNC) {
+  // message: Has an MNO with no MVNO.
+  // match by: MCCMNC.
+  // verify: Observer event, uuid.
+
+  ExpectEventCount(0);
+  UpdateMCCMNC("101999");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("101001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid101");
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("101999");
+  VerifyEventCount();
+  VerifyNoMatch();
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCMultipleMCCMNCOptions) {
+  // message: Has an MNO with no MCCMNC.
+  // match by: One of the MCCMNCs of the multiple ones in the MNO.
+  // verify: Observer event, uuid.
+  ExpectEventCount(1);
+  UpdateMCCMNC("102002");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid102");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCMultipleMNOOptions) {
+  // message: Two messages with the same MCCMNC.
+  // match by: Both MNOs matched, one is earmarked.
+  // verify: The earmarked MNO is picked.
+  ExpectEventCount(1);
+  UpdateMCCMNC("124001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid124002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorName) {
+  // message: Has an MNO with no MVNO.
+  // match by: OperatorName.
+  // verify: Observer event, uuid.
+  ExpectEventCount(0);
+  UpdateOperatorName("name103999");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name103");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid103");
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name103999");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorNameMultipleMNOOptions) {
+  // message: Two messages with the same operator name.
+  // match by: Both MNOs matched, one is earmarked.
+  // verify: The earmarked MNO is picked.
+  ExpectEventCount(1);
+  UpdateOperatorName("name125001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid125002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorNameAggressiveMatch) {
+  // These network operators match by name but only after normalizing the names.
+  // Both the name from the database and the name provided to
+  // |UpdateOperatoName| must be normalized for this test to pass.
+  ExpectEventCount(1);
+  UpdateOperatorName("name126001 casedoesnotmatch");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid126001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(1);
+  UpdateOperatorName("name126002 CaseStillDoesNotMatch");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid126002");
+
+  ResetOperatorInfo();
+  ExpectEventCount(1);
+  UpdateOperatorName("name126003GiveMeMoreSpace");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid126003");
+
+  ResetOperatorInfo();
+  ExpectEventCount(1);
+  UpdateOperatorName("name126004  Too  Much   Air Here");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid126004");
+
+  ResetOperatorInfo();
+  ExpectEventCount(1);
+  UpdateOperatorName("näméwithNon-Äσ¢ii");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid126005");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorNameWithLang) {
+  // message: Has an MNO with no MVNO.
+  // match by: OperatorName.
+  // verify: Observer event, fields.
+  ExpectEventCount(1);
+  UpdateOperatorName("name105");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid105");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorNameMultipleNameOptions) {
+  // message: Has an MNO with no MVNO.
+  // match by: OperatorName, one of the multiple present in the MNO.
+  // verify: Observer event, fields.
+  ExpectEventCount(1);
+  UpdateOperatorName("name104002");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid104");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCAndOperatorName) {
+  // message: Has MNOs with no MVNO.
+  // match by: MCCMNC finds two candidates (first one is chosen), Name narrows
+  //           down to one.
+  // verify: Observer event, fields.
+  // This is merely a MCCMNC update.
+  ExpectEventCount(1);
+  UpdateMCCMNC("106001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid106001");
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name106002");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid106002");
+
+  ResetOperatorInfo();
+  // Try updates in reverse order.
+  ExpectEventCount(1);
+  UpdateOperatorName("name106001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid106001");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByOperatorNameAndMCCMNC) {
+  // message: Has MNOs with no MVNO.
+  // match by: OperatorName finds two (first one is chosen), MCCMNC narrows down
+  //           to one.
+  // verify: Observer event, fields.
+  // This is merely an OperatorName update.
+  ExpectEventCount(1);
+  UpdateOperatorName("name107");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid107001");
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("107002");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid107002");
+
+  ResetOperatorInfo();
+  // Try updates in reverse order.
+  ExpectEventCount(1);
+  UpdateMCCMNC("107001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid107001");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCOverridesOperatorName) {
+  // message: Has MNOs with no MVNO.
+  // match by: First MCCMNC finds one. Then, OperatorName matches another.
+  // verify: MCCMNC match prevails. No change on OperatorName update.
+  ExpectEventCount(1);
+  UpdateMCCMNC("108001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid108001");
+
+  // An event is sent for the updated OperatorName.
+  ExpectEventCount(1);
+  UpdateOperatorName("name108002");  // Does not match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid108001");
+  // OperatorName from the database is given preference over the user supplied
+  // one.
+  EXPECT_EQ("name108001", operator_info_->operator_name());
+
+  ResetOperatorInfo();
+  // message: Same as above.
+  // match by: First OperatorName finds one, then MCCMNC overrides it.
+  // verify: Two events, MCCMNC one overriding the OperatorName one.
+  ExpectEventCount(1);
+  UpdateOperatorName("name108001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid108001");
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("108002");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid108002");
+  EXPECT_EQ("name108002", operator_info_->operator_name());
+
+  // message: Same as above.
+  // match by: First a *wrong* MCCMNC update, followed by the correct Name
+  // update.
+  // verify: No MNO, since MCCMNC is given precedence.
+  ResetOperatorInfo();
+  ExpectEventCount(0);
+  UpdateMCCMNC("108999");  // Does not match.
+  UpdateOperatorName("name108001");
+  VerifyEventCount();
+  VerifyNoMatch();
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByIMSI) {
+  // message: Has MNO with no MVNO.
+  // match by: MCCMNC part of IMSI of length 5 / 6.
+  ExpectEventCount(0);
+  UpdateIMSI("109");  // Too short.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(0);
+  UpdateIMSI("109995432154321");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ResetOperatorInfo();
+  // Short MCCMNC match.
+  ExpectEventCount(1);
+  UpdateIMSI("109015432154321");  // First 5 digits match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid10901");
+
+  ResetOperatorInfo();
+  // Long MCCMNC match.
+  ExpectEventCount(1);
+  UpdateIMSI("10900215432154321");  // First 6 digits match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid109002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCOverridesIMSI) {
+  // message: Has MNOs with no MVNO.
+  // match by: One matches MCCMNC, then one matches a different MCCMNC substring
+  //    of IMSI
+  // verify: Observer event for the first match, all fields. Second Update
+  // ignored.
+  ExpectEventCount(1);
+  UpdateMCCMNC("110001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110001");
+
+  // MNO remains unchanged on a mismatched IMSI update.
+  ExpectEventCount(0);
+  UpdateIMSI("1100025432154321");  // First 6 digits match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110001");
+
+  // MNO remains uncnaged on an invalid IMSI update.
+  ExpectEventCount(0);
+  UpdateIMSI("1100035432154321");  // Prefix does not match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110001");
+
+  ExpectEventCount(0);
+  UpdateIMSI("110");  // Too small.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110001");
+
+  ResetOperatorInfo();
+  // Same as above, but this time, match with IMSI, followed by a contradictory
+  // MCCMNC update. The second update should override the first one.
+  ExpectEventCount(1);
+  UpdateIMSI("1100025432154321");  // First 6 digits match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110002");
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("110001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid110001");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOUchangedBySecondaryUpdates) {
+  // This test verifies that only some updates affect the MNO.
+  // message: Has MNOs with no MVNO.
+  // match by: First matches the MCCMNC. Later, MNOs with a different MCCMNC
+  //    matchs the given SID, NID, ICCID.
+  // verify: Only one Observer event, on the first MCCMNC match.
+  ExpectEventCount(1);
+  UpdateMCCMNC("111001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid111001");
+
+  ExpectEventCount(1);  // NID change event.
+  UpdateNID("111202");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid111001");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNODefaultMatch) {
+  // message: MNO with one MVNO (no filter).
+  // match by: MNO matches by MCCMNC.
+  // verify: Observer event for MVNO match. Uuid match the MVNO.
+  // second update: ICCID.
+  // verify: No observer event, match remains unchanged.
+  ExpectEventCount(1);
+  UpdateMCCMNC("112001");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid112002");
+
+  ExpectEventCount(0);
+  UpdateICCID("112002");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid112002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNONameMatch) {
+  // message: MNO with one MVNO (name filter).
+  // match by: MNO matches by MCCMNC,
+  //           MVNO fails to match by fist name update,
+  //           then MVNO matches by name.
+  // verify: Two Observer events: MNO followed by MVNO.
+  ExpectEventCount(1);
+  UpdateMCCMNC("113001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid113001");
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name113999");  // No match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid113001");
+  // Name from the database is given preference.
+  EXPECT_EQ("name113001", operator_info_->operator_name());
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name113002");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid113002");
+  EXPECT_EQ("name113002", operator_info_->operator_name());
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNONameMalformedRegexMatch) {
+  // message: MNO with one MVNO (name filter with a malformed regex).
+  // match by: MNO matches by MCCMNC.
+  //           MVNO does not match
+  ExpectEventCount(2);
+  UpdateMCCMNC("114001");
+  UpdateOperatorName("name[");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid114001");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNONameSubexpressionRegexMatch) {
+  // message: MNO with one MVNO (name filter with simple regex).
+  // match by: MNO matches by MCCMNC.
+  //           MVNO does not match with a name whose subexpression matches the
+  //           regex.
+  ExpectEventCount(2);  // One event for just the name update.
+  UpdateMCCMNC("115001");
+  UpdateOperatorName("name115_ExtraCrud");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid115001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);  // One event for just the name update.
+  UpdateMCCMNC("115001");
+  UpdateOperatorName("ExtraCrud_name115");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid115001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);  // One event for just the name update.
+  UpdateMCCMNC("115001");
+  UpdateOperatorName("ExtraCrud_name115_ExtraCrud");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid115001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);  // One event for just the name update.
+  UpdateMCCMNC("115001");
+  UpdateOperatorName("name_ExtraCrud_115");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid115001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("115001");
+  UpdateOperatorName("name115");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid115002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNONameRegexMatch) {
+  // message: MNO with one MVNO (name filter with non-trivial regex).
+  // match by: MNO matches by MCCMNC.
+  //           MVNO fails to match several times with different strings.
+  //           MVNO matches several times with different values.
+
+  // Make sure we're not taking the regex literally!
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("name[a-zA-Z_]*116[0-9]{0,3}");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid116001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("name[a-zA-Z_]116[0-9]");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid116001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("nameb*1167");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid116001");
+
+  // Success!
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("name116");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid116002");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("nameSomeWord116");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid116002");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("116001");
+  UpdateOperatorName("name116567");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid116002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNONameMatchMultipleFilters) {
+  // message: MNO with one MVNO with two name filters.
+  // match by: MNO matches by MCCMNC.
+  //           MVNO first fails on the second filter alone.
+  //           MVNO fails on the first filter alone.
+  //           MVNO matches on both filters.
+  ExpectEventCount(2);
+  UpdateMCCMNC("117001");
+  UpdateOperatorName("nameA_crud");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid117001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("117001");
+  UpdateOperatorName("crud_nameB");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid117001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("117001");
+  UpdateOperatorName("crud_crud");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid117001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("117001");
+  UpdateOperatorName("nameA_nameB");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid117002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOIMSIMatch) {
+  // message: MNO with one MVNO (imsi filter).
+  // match by: MNO matches by MCCMNC,
+  //           MVNO fails to match by fist imsi update,
+  //           then MVNO matches by imsi.
+  // verify: Two Observer events: MNO followed by MVNO.
+  ExpectEventCount(1);
+  UpdateMCCMNC("118001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid118001");
+
+  ExpectEventCount(0);
+  UpdateIMSI("1180011234512345");  // No match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid118001");
+
+  ExpectEventCount(1);
+  UpdateIMSI("1180015432154321");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid118002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOICCIDMatch) {
+  // message: MNO with one MVNO (iccid filter).
+  // match by: MNO matches by MCCMNC,
+  //           MVNO fails to match by fist iccid update,
+  //           then MVNO matches by iccid.
+  // verify: Two Observer events: MNO followed by MVNO.
+  ExpectEventCount(1);
+  UpdateMCCMNC("119001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid119001");
+
+  ExpectEventCount(0);
+  UpdateICCID("119987654321");  // No match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid119001");
+
+  ExpectEventCount(1);
+  UpdateICCID("119123456789");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid119002");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOSIDMatch) {
+  // message: MNO with one MVNO (sid filter).
+  // match by: MNO matches by SID,
+  //           MVNO fails to match by fist sid update,
+  //           then MVNO matches by sid.
+  // verify: Two Observer events: MNO followed by MVNO.
+  ExpectEventCount(0);
+  UpdateSID("120999");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateSID("120001");  // Only MNO matches.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid120001");
+  EXPECT_EQ("120001", operator_info_->sid());
+
+  ExpectEventCount(1);
+  UpdateSID("120002");  // MVNO matches as well.
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid120002");
+  EXPECT_EQ("120002", operator_info_->sid());
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOAllMatch) {
+  // message: MNO with following MVNOS:
+  //   - one with no filter.
+  //   - one with name filter.
+  //   - one with imsi filter.
+  //   - one with iccid filter.
+  //   - one with name and iccid filter.
+  // verify:
+  //   - initial MCCMNC matches the default MVNO directly (not MNO)
+  //   - match each of the MVNOs in turn.
+  //   - give super set information that does not match any MVNO correctly,
+  //     verify that the MNO matches.
+  ExpectEventCount(1);
+  UpdateMCCMNC("121001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid121001");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("121001");
+  UpdateOperatorName("name121003");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid121003");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("121001");
+  UpdateIMSI("1210045432154321");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid121004");
+
+  ResetOperatorInfo();
+  ExpectEventCount(2);
+  UpdateMCCMNC("121001");
+  UpdateICCID("121005123456789");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid121005");
+
+  ResetOperatorInfo();
+  ExpectEventCount(3);
+  UpdateMCCMNC("121001");
+  UpdateOperatorName("name121006");
+  VerifyMNOWithUUID("uuid121001");
+  UpdateICCID("121006123456789");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid121006");
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOMatchAndMismatch) {
+  // message: MNO with one MVNO with name filter.
+  // match by: MNO matches by MCCMNC
+  //           MVNO matches by name.
+  //           Second name update causes the MVNO to not match again.
+  ExpectEventCount(1);
+  UpdateMCCMNC("113001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid113001");
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name113002");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid113002");
+  EXPECT_EQ("name113002", operator_info_->operator_name());
+
+  ExpectEventCount(1);
+  UpdateOperatorName("name113999");  // No match.
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid113001");
+  // Name from database is given preference.
+  EXPECT_EQ("name113001", operator_info_->operator_name());
+}
+
+TEST_P(MobileOperatorInfoMainTest, MVNOMatchAndReset) {
+  // message: MVNO with name filter.
+  // verify;
+  //   - match MVNO by name.
+  //   - Reset object, verify Observer event, and not match.
+  //   - match MVNO by name again.
+  ExpectEventCount(1);
+  UpdateMCCMNC("113001");
+  VerifyEventCount();
+  ExpectEventCount(1);
+  VerifyMNOWithUUID("uuid113001");
+  UpdateOperatorName("name113002");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid113002");
+  EXPECT_EQ("name113002", operator_info_->operator_name());
+
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("113001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid113001");
+  ExpectEventCount(1);
+  UpdateOperatorName("name113002");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid113002");
+  EXPECT_EQ("name113002", operator_info_->operator_name());
+}
+
+// Here, we rely on our knowledge about the implementation: The SID and MCCMNC
+// updates follow the same code paths, and so we can get away with not testing
+// all the scenarios we test above for MCCMNC. Instead, we only do basic testing
+// to make sure that SID upates operator as MCCMNC updates do.
+TEST_P(MobileOperatorInfoMainTest, MNOBySID) {
+  // message: Has an MNO with no MVNO.
+  // match by: SID.
+  // verify: Observer event, uuid.
+
+  ExpectEventCount(0);
+  UpdateSID("1229");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateSID("1221");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid1221");
+
+  ExpectEventCount(1);
+  UpdateSID("1229");  // No Match.
+  VerifyEventCount();
+  VerifyNoMatch();
+}
+
+TEST_P(MobileOperatorInfoMainTest, MNOByMCCMNCAndSID) {
+  // message: Has an MNO with no MVNO.
+  // match by: SID / MCCMNC alternately.
+  // verify: Observer event, uuid.
+
+  ExpectEventCount(0);
+  UpdateMCCMNC("123999");  // NO match.
+  UpdateSID("1239");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("123001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid123001");
+
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateSID("1232");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid1232");
+
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("123001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid123001");
+}
+
+class MobileOperatorInfoDataTest : public MobileOperatorInfoMainTest {
+ public:
+  MobileOperatorInfoDataTest() : MobileOperatorInfoMainTest() {}
+
+  // Same as MobileOperatorInfoMainTest, except that the database used is
+  // different.
+  virtual void SetUp() {
+    operator_info_->ClearDatabasePaths();
+    AddDatabase(mobile_operator_db::data_test,
+                arraysize(mobile_operator_db::data_test));
+    operator_info_->Init();
+    operator_info_->AddObserver(&observer_);
+  }
+
+ protected:
+  // This is a function that does a best effort verification of the information
+  // that is obtained from the database by the MobileOperatorInfo object against
+  // expectations stored in the form of data members in this class.
+  // This is not a full proof check. In particular:
+  //  - It is unspecified in some case which of the values from a list is
+  //    exposed as a property. For example, at best, we can check that |sid| is
+  //    non-empty.
+  //  - It is not robust to "" as property values at times.
+  void VerifyDatabaseData() {
+    EXPECT_EQ(country_, operator_info_->country());
+    EXPECT_EQ(requires_roaming_, operator_info_->requires_roaming());
+    EXPECT_EQ(activation_code_, operator_info_->activation_code());
+
+    EXPECT_EQ(mccmnc_list_.size(), operator_info_->mccmnc_list().size());
+    set<string> mccmnc_set(operator_info_->mccmnc_list().begin(),
+                           operator_info_->mccmnc_list().end());
+    for (const auto &mccmnc : mccmnc_list_) {
+      EXPECT_TRUE(mccmnc_set.find(mccmnc) != mccmnc_set.end());
+    }
+    if (mccmnc_list_.size() > 0) {
+      // It is not specified which entry will be chosen, but mccmnc() must be
+      // non empty.
+      EXPECT_FALSE(operator_info_->mccmnc().empty());
+    }
+
+    VerifyNameListsMatch(operator_name_list_,
+                         operator_info_->operator_name_list());
+
+    // This comparison breaks if two apns have the same |apn| field.
+    EXPECT_EQ(apn_list_.size(), operator_info_->apn_list().size());
+    map<string, const MobileOperatorInfo::MobileAPN *> mobile_apns;
+    for (const auto &apn_node : operator_info_->apn_list()) {
+      mobile_apns[apn_node->apn] = apn_node;
+    }
+    for (const auto &apn_lhs : apn_list_) {
+      ASSERT_TRUE(mobile_apns.find(apn_lhs->apn) != mobile_apns.end());
+      const auto &apn_rhs = mobile_apns[apn_lhs->apn];
+      // Only comparing apn, name, username, password.
+      EXPECT_EQ(apn_lhs->apn, apn_rhs->apn);
+      EXPECT_EQ(apn_lhs->username, apn_rhs->username);
+      EXPECT_EQ(apn_lhs->password, apn_rhs->password);
+      VerifyNameListsMatch(apn_lhs->operator_name_list,
+                           apn_rhs->operator_name_list);
+    }
+
+    EXPECT_EQ(olp_list_.size(), operator_info_->olp_list().size());
+    // This comparison breaks if two OLPs have the same |url|.
+    map<string, MobileOperatorInfo::OnlinePortal> olps;
+    for (const auto &olp : operator_info_->olp_list()) {
+      olps[olp.url] = olp;
+    }
+    for (const auto &olp : olp_list_) {
+      ASSERT_TRUE(olps.find(olp.url) != olps.end());
+      const auto &olp_rhs = olps[olp.url];
+      EXPECT_EQ(olp.method, olp_rhs.method);
+      EXPECT_EQ(olp.post_data, olp_rhs.post_data);
+    }
+
+    EXPECT_EQ(sid_list_.size(), operator_info_->sid_list().size());
+    set<string> sid_set(operator_info_->sid_list().begin(),
+                        operator_info_->sid_list().end());
+    for (const auto &sid : sid_list_) {
+      EXPECT_TRUE(sid_set.find(sid) != sid_set.end());
+    }
+    if (sid_list_.size() > 0) {
+      // It is not specified which entry will be chosen, but |sid()| must be
+      // non-empty.
+      EXPECT_FALSE(operator_info_->sid().empty());
+    }
+  }
+
+  // This function does some extra checks for the user data that can not be done
+  // when data is obtained from the database.
+  void VerifyUserData() {
+    EXPECT_EQ(sid_, operator_info_->sid());
+  }
+
+  void VerifyNameListsMatch(
+      const vector<MobileOperatorInfo::LocalizedName> &operator_name_list_lhs,
+      const vector<MobileOperatorInfo::LocalizedName> &operator_name_list_rhs) {
+    // This comparison breaks if two localized names have the same |name|.
+    map<string, MobileOperatorInfo::LocalizedName> localized_names;
+    for (const auto &localized_name : operator_name_list_rhs) {
+      localized_names[localized_name.name] = localized_name;
+    }
+    for (const auto &localized_name : operator_name_list_lhs) {
+      EXPECT_TRUE(localized_names.find(localized_name.name) !=
+                  localized_names.end());
+      EXPECT_EQ(localized_name.language,
+                localized_names[localized_name.name].language);
+    }
+  }
+
+  // Use this function to pre-popluate all the data members of this object with
+  // values matching the MNO for the database in |data_test.prototxt|.
+  void PopulateMNOData() {
+    country_ = "us";
+    requires_roaming_ = true;
+    activation_code_ = "open sesame";
+
+    mccmnc_list_.clear();
+    mccmnc_list_.push_back("200001");
+    mccmnc_list_.push_back("200002");
+    mccmnc_list_.push_back("200003");
+
+    operator_name_list_.clear();
+    operator_name_list_.push_back({"name200001", "en"});
+    operator_name_list_.push_back({"name200002", ""});
+
+    apn_list_.clear();
+    MobileOperatorInfo::MobileAPN *apn;
+    apn = new MobileOperatorInfo::MobileAPN();
+    apn->apn = "test@test.com";
+    apn->username = "testuser";
+    apn->password = "is_public_boohoohoo";
+    apn->operator_name_list.push_back({"name200003", "hi"});
+    apn_list_.push_back(apn);  // Takes ownership.
+
+    olp_list_.clear();
+    olp_list_.push_back({"some@random.com", "POST", "random_data"});
+
+    sid_list_.clear();
+    sid_list_.push_back("200123");
+    sid_list_.push_back("200234");
+    sid_list_.push_back("200345");
+  }
+
+  // Use this function to pre-populate all the data members of this object with
+  // values matching the MVNO for the database in |data_test.prototext|.
+  void PopulateMVNOData() {
+    country_ = "ca";
+    requires_roaming_ = false;
+    activation_code_ = "khul ja sim sim";
+
+    mccmnc_list_.clear();
+    mccmnc_list_.push_back("200001");
+    mccmnc_list_.push_back("200102");
+
+    operator_name_list_.clear();
+    operator_name_list_.push_back({"name200101", "en"});
+    operator_name_list_.push_back({"name200102", ""});
+
+    apn_list_.clear();
+    MobileOperatorInfo::MobileAPN *apn;
+    apn = new MobileOperatorInfo::MobileAPN();
+    apn->apn = "test2@test.com";
+    apn->username = "testuser2";
+    apn->password = "is_public_boohoohoo_too";
+    apn_list_.push_back(apn);  // Takes ownership.
+
+    olp_list_.clear();
+    olp_list_.push_back({"someother@random.com", "GET", ""});
+
+    sid_list_.clear();
+    sid_list_.push_back("200345");
+  }
+
+  // Data to be verified against the database.
+  string country_;
+  bool requires_roaming_;
+  string activation_code_;
+  vector<string> mccmnc_list_;
+  vector<MobileOperatorInfo::LocalizedName> operator_name_list_;
+  ScopedVector<MobileOperatorInfo::MobileAPN> apn_list_;
+  vector<MobileOperatorInfo::OnlinePortal> olp_list_;
+  vector<string> sid_list_;
+
+  // Extra data to be verified only against user updates.
+  string sid_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoDataTest);
+};
+
+
+TEST_P(MobileOperatorInfoDataTest, MNODetailedInformation) {
+  // message: MNO with all the information filled in.
+  // match by: MNO matches by MCCMNC
+  // verify: All information is correctly loaded.
+  ExpectEventCount(1);
+  UpdateMCCMNC("200001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid200001");
+
+  PopulateMNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, MVNOInheritsInformation) {
+  // message: MVNO with name filter.
+  // verify: All the missing fields are carried over to the MVNO from MNO.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200201");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200201");
+
+  PopulateMNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, MVNOOverridesInformation) {
+  // match by: MNO matches by MCCMNC, MVNO by name.
+  // verify: All information is correctly loaded.
+  //         The MVNO in this case overrides the information provided by MNO.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+
+  PopulateMVNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, NoUpdatesBeforeMNOMatch) {
+  // message: MVNO.
+  // - do not match MNO with mccmnc/name
+  // - on different updates, verify no events.
+  ExpectEventCount(0);
+  UpdateMCCMNC("200999");  // No match.
+  UpdateOperatorName("name200001");  // matches MNO
+  UpdateOperatorName("name200101");  // matches MVNO filter.
+  UpdateSID("200999");  // No match.
+  VerifyEventCount();
+  VerifyNoMatch();
+}
+
+TEST_P(MobileOperatorInfoDataTest, UserUpdatesOverrideMVNO) {
+  // - match MVNO.
+  // - send updates to properties and verify events are raised and values of
+  //   updated properties override the ones provided by the database.
+  string imsi {"2009991234512345"};
+  string iccid {"200999123456789"};
+  string olp_url {"url@url.com"};
+  string olp_method {"POST"};
+  string olp_post_data {"data"};
+
+  // Determine MVNO.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+
+  // Send updates.
+  ExpectEventCount(1);
+  UpdateOnlinePortal(olp_url, olp_method, olp_post_data);
+  UpdateIMSI(imsi);
+  // No event raised because imsi is not exposed.
+  UpdateICCID(iccid);
+  // No event raised because ICCID is not exposed.
+
+  VerifyEventCount();
+
+  // Update our expectations.
+  PopulateMVNOData();
+  olp_list_.push_back({olp_url, olp_method, olp_post_data});
+
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, CachedUserUpdatesOverrideMVNO) {
+  // message: MVNO.
+  // - First send updates that don't identify an MNO.
+  // - Then identify an MNO and MVNO.
+  // - verify that all the earlier updates are cached, and override the MVNO
+  //   information.
+  string imsi {"2009991234512345"};
+  string iccid {"200999123456789"};
+  string sid {"200999"};
+  string olp_url {"url@url.com"};
+  string olp_method {"POST"};
+  string olp_post_data {"data"};
+
+  // Send updates.
+  ExpectEventCount(0);
+  UpdateSID(sid);
+  UpdateOnlinePortal(olp_url, olp_method, olp_post_data);
+  UpdateIMSI(imsi);
+  UpdateICCID(iccid);
+  VerifyEventCount();
+
+  // Determine MVNO.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+
+  // Update our expectations.
+  PopulateMVNOData();
+  sid_ = sid;
+  sid_list_.push_back(sid);
+  olp_list_.push_back({olp_url, olp_method, olp_post_data});
+
+  VerifyDatabaseData();
+  VerifyUserData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, RedundantUserUpdatesMVNO) {
+  // - match MVNO.
+  // - send redundant updates to properties.
+  // - Verify no events, no updates to properties.
+
+  // Identify MVNO.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+
+  // Send redundant updates.
+  // TODO(pprabhu)
+  // |UpdateOnlinePortal| leads to an event because this is the first time this
+  // value are set *by the user*. Although the values from the database were the
+  // same, we did not use those values for filters.  It would be ideal to not
+  // raise these redundant events (since no public information about the object
+  // changed), but I haven't invested in doing so yet.
+  ExpectEventCount(1);
+  UpdateOperatorName(operator_info_->operator_name());
+  UpdateOnlinePortal("someother@random.com", "GET", "");
+  VerifyEventCount();
+  PopulateMVNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, RedundantCachedUpdatesMVNO) {
+  // message: MVNO.
+  // - First send updates that don't identify MVNO, but match the data.
+  // - Then idenityf an MNO and MVNO.
+  // - verify that redundant information occurs only once.
+
+  // Send redundant updates.
+  ExpectEventCount(2);
+  UpdateSID(operator_info_->sid());
+  UpdateOperatorName(operator_info_->operator_name());
+  UpdateOnlinePortal("someother@random.com", "GET", "");
+
+  // Identify MVNO.
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+
+  PopulateMVNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, ResetClearsInformation) {
+  // Repeatedly reset the object and check M[V]NO identification and data.
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200201");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200201");
+  PopulateMNOData();
+  VerifyDatabaseData();
+
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyEventCount();
+  VerifyMVNOWithUUID("uuid200101");
+  PopulateMVNOData();
+  VerifyDatabaseData();
+
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("200001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid200001");
+  PopulateMNOData();
+  VerifyDatabaseData();
+}
+
+TEST_P(MobileOperatorInfoDataTest, FilteredOLP) {
+  // We only check basic filter matching, using the fact that the regex matching
+  // code is shared with the MVNO filtering, and is already well tested.
+  // (1) None of the filters match.
+  ExpectEventCount(1);
+  UpdateMCCMNC("200001");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid200001");
+
+  ASSERT_EQ(1, operator_info_->olp_list().size());
+  // Just check that the filtered OLPs are not in the list.
+  EXPECT_NE("olp@mccmnc", operator_info_->olp_list()[0].url);
+  EXPECT_NE("olp@sid", operator_info_->olp_list()[0].url);
+
+  // (2) MCCMNC filter matches.
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateMCCMNC("200003");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid200001");
+
+  ASSERT_EQ(2, operator_info_->olp_list().size());
+  EXPECT_NE("olp@sid", operator_info_->olp_list()[0].url);
+  bool found_olp_by_mccmnc = false;
+  for (const auto &olp : operator_info_->olp_list()) {
+    found_olp_by_mccmnc |= ("olp@mccmnc" == olp.url);
+  }
+  EXPECT_TRUE(found_olp_by_mccmnc);
+
+  // (3) SID filter matches.
+  ExpectEventCount(1);
+  operator_info_->Reset();
+  VerifyEventCount();
+  VerifyNoMatch();
+
+  ExpectEventCount(1);
+  UpdateSID("200345");
+  VerifyEventCount();
+  VerifyMNOWithUUID("uuid200001");
+
+  ASSERT_EQ(2, operator_info_->olp_list().size());
+  EXPECT_NE("olp@mccmnc", operator_info_->olp_list()[0].url);
+  bool found_olp_by_sid = false;
+  for (const auto &olp : operator_info_->olp_list()) {
+    found_olp_by_sid |= ("olp@sid" == olp.url);
+  }
+  EXPECT_TRUE(found_olp_by_sid);
+}
+
+class MobileOperatorInfoObserverTest : public MobileOperatorInfoMainTest {
+ public:
+  MobileOperatorInfoObserverTest() : MobileOperatorInfoMainTest() {}
+
+  // Same as |MobileOperatorInfoMainTest::SetUp|, except that we don't add a
+  // default observer.
+  virtual void SetUp() {
+    operator_info_->ClearDatabasePaths();
+    AddDatabase(mobile_operator_db::data_test,
+                arraysize(mobile_operator_db::data_test));
+    operator_info_->Init();
+  }
+
+ protected:
+  // ///////////////////////////////////////////////////////////////////////////
+  // Data.
+  MockMobileOperatorInfoObserver second_observer_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MobileOperatorInfoObserverTest);
+};
+
+TEST_P(MobileOperatorInfoObserverTest, NoObserver) {
+  // - Don't add any observers, and then cause an MVNO update to occur.
+  // - Verify no crash.
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+}
+
+TEST_P(MobileOperatorInfoObserverTest, MultipleObservers) {
+  // - Add two observers, and then cause an MVNO update to occur.
+  // - Verify both observers are notified.
+  operator_info_->AddObserver(&observer_);
+  operator_info_->AddObserver(&second_observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(2);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyMVNOWithUUID("uuid200101");
+
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_P(MobileOperatorInfoObserverTest, LateObserver) {
+  // - Add one observer, and verify it gets an MVNO update.
+  operator_info_->AddObserver(&observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(2);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(0);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyMVNOWithUUID("uuid200101");
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(1);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(0);
+  operator_info_->Reset();
+  VerifyNoMatch();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+
+  // - Add another observer, verify both get an MVNO update.
+  operator_info_->AddObserver(&second_observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(2);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyMVNOWithUUID("uuid200101");
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(1);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(1);
+  operator_info_->Reset();
+  VerifyNoMatch();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+
+  // - Remove an observer, verify it no longer gets updates.
+  operator_info_->RemoveObserver(&observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(0);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(2);
+  UpdateMCCMNC("200001");
+  UpdateOperatorName("name200101");
+  VerifyMVNOWithUUID("uuid200101");
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+
+  EXPECT_CALL(observer_, OnOperatorChanged()).Times(0);
+  EXPECT_CALL(second_observer_, OnOperatorChanged()).Times(1);
+  operator_info_->Reset();
+  VerifyNoMatch();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(&observer_);
+  Mock::VerifyAndClearExpectations(&second_observer_);
+}
+
+INSTANTIATE_TEST_CASE_P(MobileOperatorInfoMainTestInstance,
+                        MobileOperatorInfoMainTest,
+                        Values(kEventCheckingPolicyStrict,
+                               kEventCheckingPolicyNonStrict));
+INSTANTIATE_TEST_CASE_P(MobileOperatorInfoDataTestInstance,
+                        MobileOperatorInfoDataTest,
+                        Values(kEventCheckingPolicyStrict,
+                               kEventCheckingPolicyNonStrict));
+// It only makes sense to do strict checking here.
+INSTANTIATE_TEST_CASE_P(MobileOperatorInfoObserverTestInstance,
+                        MobileOperatorInfoObserverTest,
+                        Values(kEventCheckingPolicyStrict));
+}  // namespace shill
diff --git a/cellular/mock_cellular.cc b/cellular/mock_cellular.cc
new file mode 100644
index 0000000..509e7d4
--- /dev/null
+++ b/cellular/mock_cellular.cc
@@ -0,0 +1,30 @@
+// 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/mock_cellular.h"
+
+#include <gmock/gmock.h>
+
+#include "shill/error.h"
+
+namespace shill {
+
+// TODO(rochberg): The cellular constructor does work.  Ought to fix
+// this so that we don't depend on passing real values in for Type.
+
+MockCellular::MockCellular(ModemInfo *modem_info,
+                           const std::string &link_name,
+                           const std::string &address,
+                           int interface_index,
+                           Type type,
+                           const std::string &owner,
+                           const std::string &service,
+                           const std::string &path,
+                           ProxyFactory *proxy_factory)
+    : Cellular(modem_info, link_name, address, interface_index, type, owner,
+               service, path, proxy_factory) {}
+
+MockCellular::~MockCellular() {}
+
+}  // namespace shill
diff --git a/cellular/mock_cellular.h b/cellular/mock_cellular.h
new file mode 100644
index 0000000..7d57148
--- /dev/null
+++ b/cellular/mock_cellular.h
@@ -0,0 +1,46 @@
+// 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_MOCK_CELLULAR_H_
+#define SHILL_CELLULAR_MOCK_CELLULAR_H_
+
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/cellular.h"
+
+namespace shill {
+
+class MockCellular : public Cellular {
+ public:
+  MockCellular(ModemInfo *modem_info,
+               const std::string &link_name,
+               const std::string &address,
+               int interface_index,
+               Type type,
+               const std::string &owner,
+               const std::string &service,
+               const std::string &path,
+               ProxyFactory *proxy_factory);
+  ~MockCellular() override;
+
+  MOCK_METHOD1(Connect, void(Error *error));
+  MOCK_METHOD2(Disconnect, void(Error *error, const char *reason));
+  MOCK_METHOD3(OnDBusPropertiesChanged, void(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties));
+  MOCK_METHOD1(set_modem_state, void(ModemState state));
+  MOCK_METHOD0(DestroyService, void());
+  MOCK_METHOD1(StartPPP, void(const std::string &serial_device));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockCellular);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_CELLULAR_H_
diff --git a/cellular/mock_cellular_service.cc b/cellular/mock_cellular_service.cc
new file mode 100644
index 0000000..64e7d2b
--- /dev/null
+++ b/cellular/mock_cellular_service.cc
@@ -0,0 +1,23 @@
+// 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/mock_cellular_service.h"
+
+#include <chromeos/dbus/service_constants.h>
+
+using testing::ReturnRef;
+
+namespace shill {
+
+MockCellularService::MockCellularService(ModemInfo *modem_info,
+                                         const CellularRefPtr &device)
+    : CellularService(modem_info, device),
+      default_activation_state_(kActivationStateUnknown) {
+  ON_CALL(*this, activation_state())
+      .WillByDefault(ReturnRef(default_activation_state_));
+}
+
+MockCellularService::~MockCellularService() {}
+
+}  // namespace shill
diff --git a/cellular/mock_cellular_service.h b/cellular/mock_cellular_service.h
new file mode 100644
index 0000000..f78d852
--- /dev/null
+++ b/cellular/mock_cellular_service.h
@@ -0,0 +1,44 @@
+// 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_MOCK_CELLULAR_SERVICE_H_
+#define SHILL_CELLULAR_MOCK_CELLULAR_SERVICE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/cellular_service.h"
+
+namespace shill {
+
+class MockCellularService : public CellularService {
+ public:
+  MockCellularService(ModemInfo *modem_info,
+                      const CellularRefPtr &device);
+  ~MockCellularService() override;
+
+  MOCK_METHOD0(AutoConnect, void());
+  MOCK_METHOD1(SetLastGoodApn, void(const Stringmap &apn_info));
+  MOCK_METHOD0(ClearLastGoodApn, void());
+  MOCK_METHOD1(SetActivationState, void(const std::string &state));
+  MOCK_METHOD2(Connect, void(Error *error, const char *reason));
+  MOCK_METHOD2(Disconnect, void(Error *error, const char *reason));
+  MOCK_METHOD1(SetState, void(ConnectState state));
+  MOCK_METHOD1(SetFailure, void(ConnectFailure failure));
+  MOCK_METHOD1(SetFailureSilent, void(ConnectFailure failure));
+  MOCK_CONST_METHOD0(state, ConnectState());
+  MOCK_CONST_METHOD0(explicitly_disconnected, bool());
+  MOCK_CONST_METHOD0(activation_state, const std::string &());
+  MOCK_CONST_METHOD0(resume_start_time, const base::Time &());
+
+ private:
+  std::string default_activation_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockCellularService);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_CELLULAR_SERVICE_H_
diff --git a/cellular/mock_dbus_objectmanager_proxy.cc b/cellular/mock_dbus_objectmanager_proxy.cc
new file mode 100644
index 0000000..b6d9c34
--- /dev/null
+++ b/cellular/mock_dbus_objectmanager_proxy.cc
@@ -0,0 +1,24 @@
+// 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/mock_dbus_objectmanager_proxy.h"
+
+#include "shill/testing.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+
+namespace shill {
+MockDBusObjectManagerProxy::MockDBusObjectManagerProxy() {
+  ON_CALL(*this, GetManagedObjects(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockDBusObjectManagerProxy::~MockDBusObjectManagerProxy() {}
+
+void MockDBusObjectManagerProxy::IgnoreSetCallbacks() {
+  EXPECT_CALL(*this, set_interfaces_added_callback(_)).Times(AnyNumber());
+  EXPECT_CALL(*this, set_interfaces_removed_callback(_)).Times(AnyNumber());
+}
+}  // namespace shill
diff --git a/cellular/mock_dbus_objectmanager_proxy.h b/cellular/mock_dbus_objectmanager_proxy.h
new file mode 100644
index 0000000..0e50c1b
--- /dev/null
+++ b/cellular/mock_dbus_objectmanager_proxy.h
@@ -0,0 +1,35 @@
+// 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_MOCK_DBUS_OBJECTMANAGER_PROXY_H_
+#define SHILL_CELLULAR_MOCK_DBUS_OBJECTMANAGER_PROXY_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/dbus_objectmanager_proxy_interface.h"
+
+namespace shill {
+
+class MockDBusObjectManagerProxy : public DBusObjectManagerProxyInterface {
+ public:
+  MockDBusObjectManagerProxy();
+  ~MockDBusObjectManagerProxy() override;
+
+  MOCK_METHOD3(GetManagedObjects, void(Error *error,
+                                       const ManagedObjectsCallback &callback,
+                                       int timeout));
+  MOCK_METHOD1(set_interfaces_added_callback,
+      void(const InterfacesAddedSignalCallback &callback));
+  MOCK_METHOD1(set_interfaces_removed_callback,
+      void(const InterfacesRemovedSignalCallback &callback));
+  void IgnoreSetCallbacks();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockDBusObjectManagerProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_DBUS_OBJECTMANAGER_PROXY_H_
diff --git a/cellular/mock_mm1_bearer_proxy.cc b/cellular/mock_mm1_bearer_proxy.cc
new file mode 100644
index 0000000..f26be03
--- /dev/null
+++ b/cellular/mock_mm1_bearer_proxy.cc
@@ -0,0 +1,24 @@
+// 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/mock_mm1_bearer_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockBearerProxy::MockBearerProxy() {
+  ON_CALL(*this, Connect(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, Disconnect(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockBearerProxy::~MockBearerProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_bearer_proxy.h b/cellular/mock_mm1_bearer_proxy.h
new file mode 100644
index 0000000..e478967
--- /dev/null
+++ b/cellular/mock_mm1_bearer_proxy.h
@@ -0,0 +1,33 @@
+// 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_MOCK_MM1_BEARER_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_BEARER_PROXY_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_bearer_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockBearerProxy : public BearerProxyInterface {
+ public:
+  MockBearerProxy();
+  ~MockBearerProxy() override;
+
+  MOCK_METHOD3(Connect, void(Error *error,
+                             const ResultCallback &callback,
+                             int timeout));
+  MOCK_METHOD3(Disconnect, void(Error *error,
+                                const ResultCallback &callback,
+                                int timeout));
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_BEARER_PROXY_H_
diff --git a/cellular/mock_mm1_modem_location_proxy.cc b/cellular/mock_mm1_modem_location_proxy.cc
new file mode 100644
index 0000000..a62376f
--- /dev/null
+++ b/cellular/mock_mm1_modem_location_proxy.cc
@@ -0,0 +1,24 @@
+// 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/mock_mm1_modem_location_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemLocationProxy::MockModemLocationProxy() {
+  ON_CALL(*this, Setup(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, GetLocation(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemLocationProxy::~MockModemLocationProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_location_proxy.h b/cellular/mock_mm1_modem_location_proxy.h
new file mode 100644
index 0000000..3324807
--- /dev/null
+++ b/cellular/mock_mm1_modem_location_proxy.h
@@ -0,0 +1,39 @@
+// 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_MOCK_MM1_MODEM_LOCATION_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_LOCATION_PROXY_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_location_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemLocationProxy : public ModemLocationProxyInterface {
+ public:
+  MockModemLocationProxy();
+  ~MockModemLocationProxy() override;
+
+  // Inherited methods from ModemLocationProxyInterface.
+  MOCK_METHOD5(Setup, void(uint32_t sources,
+                           bool signal_location,
+                           Error *error,
+                           const ResultCallback &callback,
+                           int timeout));
+
+  MOCK_METHOD3(GetLocation, void(Error *error,
+                                 const DBusEnumValueMapCallback &callback,
+                                 int timeout));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemLocationProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_LOCATION_PROXY_H_
diff --git a/cellular/mock_mm1_modem_modem3gpp_proxy.cc b/cellular/mock_mm1_modem_modem3gpp_proxy.cc
new file mode 100644
index 0000000..ad985dc
--- /dev/null
+++ b/cellular/mock_mm1_modem_modem3gpp_proxy.cc
@@ -0,0 +1,24 @@
+// 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/mock_mm1_modem_modem3gpp_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemModem3gppProxy::MockModemModem3gppProxy() {
+  ON_CALL(*this, Register(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Scan(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemModem3gppProxy::~MockModemModem3gppProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_modem3gpp_proxy.h b/cellular/mock_mm1_modem_modem3gpp_proxy.h
new file mode 100644
index 0000000..7e9bba4
--- /dev/null
+++ b/cellular/mock_mm1_modem_modem3gpp_proxy.h
@@ -0,0 +1,38 @@
+// 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_MOCK_MM1_MODEM_MODEM3GPP_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_MODEM3GPP_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_modem3gpp_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemModem3gppProxy : public ModemModem3gppProxyInterface {
+ public:
+  MockModemModem3gppProxy();
+  ~MockModemModem3gppProxy() override;
+
+  MOCK_METHOD4(Register, void(const std::string &operator_id,
+                              Error *error,
+                              const ResultCallback &callback,
+                              int timeout));
+  MOCK_METHOD3(Scan, void(Error *error,
+                          const DBusPropertyMapsCallback &callback,
+                          int timeout));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemModem3gppProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_MODEM3GPP_PROXY_H_
diff --git a/cellular/mock_mm1_modem_modemcdma_proxy.cc b/cellular/mock_mm1_modem_modemcdma_proxy.cc
new file mode 100644
index 0000000..650afd4
--- /dev/null
+++ b/cellular/mock_mm1_modem_modemcdma_proxy.cc
@@ -0,0 +1,24 @@
+// 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/mock_mm1_modem_modemcdma_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemModemCdmaProxy::MockModemModemCdmaProxy() {
+  ON_CALL(*this, Activate(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, ActivateManual(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+}
+
+MockModemModemCdmaProxy::~MockModemModemCdmaProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_modemcdma_proxy.h b/cellular/mock_mm1_modem_modemcdma_proxy.h
new file mode 100644
index 0000000..39c2f83
--- /dev/null
+++ b/cellular/mock_mm1_modem_modemcdma_proxy.h
@@ -0,0 +1,45 @@
+// 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_MOCK_MM1_MODEM_MODEMCDMA_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_MODEMCDMA_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_modemcdma_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemModemCdmaProxy : public ModemModemCdmaProxyInterface {
+ public:
+  MockModemModemCdmaProxy();
+  ~MockModemModemCdmaProxy() override;
+
+  MOCK_METHOD4(Activate, void(
+      const std::string &carrier,
+      Error *error,
+      const ResultCallback &callback,
+      int timeout));
+
+  MOCK_METHOD4(ActivateManual, void(
+      const DBusPropertiesMap &properties,
+      Error *error,
+      const ResultCallback &callback,
+      int timeout));
+
+  MOCK_METHOD1(set_activation_state_callback,
+               void(const ActivationStateSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemModemCdmaProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_MODEMCDMA_PROXY_H_
diff --git a/cellular/mock_mm1_modem_proxy.cc b/cellular/mock_mm1_modem_proxy.cc
new file mode 100644
index 0000000..e223fbe
--- /dev/null
+++ b/cellular/mock_mm1_modem_proxy.cc
@@ -0,0 +1,38 @@
+// 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/mock_mm1_modem_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemProxy::MockModemProxy() {
+  ON_CALL(*this, Enable(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, CreateBearer(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, DeleteBearer(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Reset(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, FactoryReset(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, SetCurrentCapabilities(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, SetCurrentModes(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Command(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, SetPowerState(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+}
+
+MockModemProxy::~MockModemProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_proxy.h b/cellular/mock_mm1_modem_proxy.h
new file mode 100644
index 0000000..3fce369
--- /dev/null
+++ b/cellular/mock_mm1_modem_proxy.h
@@ -0,0 +1,76 @@
+// 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_MOCK_MM1_MODEM_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemProxy : public ModemProxyInterface {
+ public:
+  MockModemProxy();
+  ~MockModemProxy() override;
+
+  // Inherited methods from ModemProxyInterface.
+  MOCK_METHOD4(Enable, void(bool enable,
+                            Error *error,
+                            const ResultCallback &callback,
+                            int timeout));
+  MOCK_METHOD4(CreateBearer, void(const DBusPropertiesMap &properties,
+                                  Error *error,
+                                  const DBusPathCallback &callback,
+                                  int timeout));
+  MOCK_METHOD4(DeleteBearer, void(const ::DBus::Path &bearer,
+                                  Error *error,
+                                  const ResultCallback &callback,
+                                  int timeout));
+  MOCK_METHOD3(Reset, void(Error *error,
+                           const ResultCallback &callback,
+                           int timeout));
+  MOCK_METHOD4(FactoryReset, void(const std::string &code,
+                                  Error *error,
+                                  const ResultCallback &callback,
+                                  int timeout));
+  MOCK_METHOD4(SetCurrentCapabilities, void(const uint32_t &capabilities,
+                                            Error *error,
+                                            const ResultCallback &callback,
+                                            int timeout));
+  MOCK_METHOD4(SetCurrentModes,
+               void(const ::DBus::Struct<uint32_t, uint32_t> &modes,
+                    Error *error,
+                    const ResultCallback &callback,
+                    int timeout));
+  MOCK_METHOD4(SetCurrentBands, void(const std::vector<uint32_t> &bands,
+                                     Error *error,
+                                     const ResultCallback &callback,
+                                     int timeout));
+  MOCK_METHOD5(Command, void(const std::string &cmd,
+                             const uint32_t &user_timeout,
+                             Error *error,
+                             const StringCallback &callback,
+                             int timeout));
+  MOCK_METHOD4(SetPowerState, void(const uint32_t &power_state,
+                                   Error *error,
+                                   const ResultCallback &callback,
+                                   int timeout));
+  MOCK_METHOD1(set_state_changed_callback, void(
+      const ModemStateChangedSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_PROXY_H_
diff --git a/cellular/mock_mm1_modem_simple_proxy.cc b/cellular/mock_mm1_modem_simple_proxy.cc
new file mode 100644
index 0000000..d985d3a
--- /dev/null
+++ b/cellular/mock_mm1_modem_simple_proxy.cc
@@ -0,0 +1,26 @@
+// 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/mock_mm1_modem_simple_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemSimpleProxy::MockModemSimpleProxy() {
+  ON_CALL(*this, Connect(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Disconnect(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, GetStatus(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemSimpleProxy::~MockModemSimpleProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_simple_proxy.h b/cellular/mock_mm1_modem_simple_proxy.h
new file mode 100644
index 0000000..e0f1863
--- /dev/null
+++ b/cellular/mock_mm1_modem_simple_proxy.h
@@ -0,0 +1,41 @@
+// 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_MOCK_MM1_MODEM_SIMPLE_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_SIMPLE_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_simple_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemSimpleProxy : public ModemSimpleProxyInterface {
+ public:
+  MockModemSimpleProxy();
+  ~MockModemSimpleProxy() override;
+
+  MOCK_METHOD4(Connect, void(const DBusPropertiesMap &properties,
+                             Error *error,
+                             const DBusPathCallback &callback,
+                             int timeout));
+  MOCK_METHOD4(Disconnect, void(const ::DBus::Path &bearer,
+                                Error *error,
+                                const ResultCallback &callback,
+                                int timeout));
+  MOCK_METHOD3(GetStatus, void(Error *error,
+                               const DBusPropertyMapCallback &callback,
+                               int timeout));
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemSimpleProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_SIMPLE_PROXY_H_
diff --git a/cellular/mock_mm1_modem_time_proxy.cc b/cellular/mock_mm1_modem_time_proxy.cc
new file mode 100644
index 0000000..1e0f2aa
--- /dev/null
+++ b/cellular/mock_mm1_modem_time_proxy.cc
@@ -0,0 +1,22 @@
+// 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/mock_mm1_modem_time_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockModemTimeProxy::MockModemTimeProxy() {
+  ON_CALL(*this, GetNetworkTime(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemTimeProxy::~MockModemTimeProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_modem_time_proxy.h b/cellular/mock_mm1_modem_time_proxy.h
new file mode 100644
index 0000000..2dd1e09
--- /dev/null
+++ b/cellular/mock_mm1_modem_time_proxy.h
@@ -0,0 +1,36 @@
+// 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_MOCK_MM1_MODEM_TIME_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_MODEM_TIME_PROXY_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_modem_time_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockModemTimeProxy : public ModemTimeProxyInterface {
+ public:
+  MockModemTimeProxy();
+  ~MockModemTimeProxy() override;
+
+  // Inherited methods from ModemTimeProxyInterface.
+  MOCK_METHOD3(GetNetworkTime, void(Error *error,
+                                    const StringCallback &callback,
+                                    int timeout));
+
+  MOCK_METHOD1(set_network_time_changed_callback,
+               void(const NetworkTimeChangedSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemTimeProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_MODEM_TIME_PROXY_H_
diff --git a/cellular/mock_mm1_sim_proxy.cc b/cellular/mock_mm1_sim_proxy.cc
new file mode 100644
index 0000000..760e7a0
--- /dev/null
+++ b/cellular/mock_mm1_sim_proxy.cc
@@ -0,0 +1,28 @@
+// 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/mock_mm1_sim_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+namespace mm1 {
+
+MockSimProxy::MockSimProxy() {
+  ON_CALL(*this, SendPin(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, SendPuk(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, EnablePin(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, ChangePin(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+}
+
+MockSimProxy::~MockSimProxy() {}
+
+}  // namespace mm1
+}  // namespace shill
diff --git a/cellular/mock_mm1_sim_proxy.h b/cellular/mock_mm1_sim_proxy.h
new file mode 100644
index 0000000..5282439
--- /dev/null
+++ b/cellular/mock_mm1_sim_proxy.h
@@ -0,0 +1,50 @@
+// 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_MOCK_MM1_SIM_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MM1_SIM_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mm1_sim_proxy_interface.h"
+
+namespace shill {
+namespace mm1 {
+
+class MockSimProxy : public SimProxyInterface {
+ public:
+  MockSimProxy();
+  ~MockSimProxy() override;
+
+  MOCK_METHOD4(SendPin, void(const std::string &pin,
+                             Error *error,
+                             const ResultCallback &callback,
+                             int timeout));
+  MOCK_METHOD5(SendPuk, void(const std::string &puk,
+                             const std::string &pin,
+                             Error *error,
+                             const ResultCallback &callback,
+                             int timeout));
+  MOCK_METHOD5(EnablePin, void(const std::string &pin,
+                               const bool enabled,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout));
+  MOCK_METHOD5(ChangePin, void(const std::string &old_pin,
+                               const std::string &new_pin,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSimProxy);
+};
+
+}  // namespace mm1
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MM1_SIM_PROXY_H_
diff --git a/cellular/mock_mobile_operator_info.cc b/cellular/mock_mobile_operator_info.cc
new file mode 100644
index 0000000..2027a87
--- /dev/null
+++ b/cellular/mock_mobile_operator_info.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/cellular/mock_mobile_operator_info.h"
+
+#include <gmock/gmock.h>
+
+namespace shill {
+
+MockMobileOperatorInfo::MockMobileOperatorInfo(EventDispatcher *dispatcher,
+                                               const std::string &info_owner)
+    : MobileOperatorInfo(dispatcher, info_owner) {}
+
+MockMobileOperatorInfo::~MockMobileOperatorInfo() {}
+
+void MockMobileOperatorInfo::SetEmptyDefaultsForProperties() {
+  ON_CALL(*this, mccmnc()).WillByDefault(ReturnRef(empty_mccmnc_));
+  ON_CALL(*this, olp_list()).WillByDefault(ReturnRef(empty_olp_list_));
+  ON_CALL(*this, activation_code())
+      .WillByDefault(ReturnRef(empty_activation_code_));
+  ON_CALL(*this, operator_name())
+      .WillByDefault(ReturnRef(empty_operator_name_));
+  ON_CALL(*this, country())
+      .WillByDefault(ReturnRef(empty_country_));
+  ON_CALL(*this, uuid()).WillByDefault(ReturnRef(empty_uuid_));
+}
+
+}  // namespace shill
diff --git a/cellular/mock_mobile_operator_info.h b/cellular/mock_mobile_operator_info.h
new file mode 100644
index 0000000..6c65c9c
--- /dev/null
+++ b/cellular/mock_mobile_operator_info.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_CELLULAR_MOCK_MOBILE_OPERATOR_INFO_H_
+#define SHILL_CELLULAR_MOCK_MOBILE_OPERATOR_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/mobile_operator_info.h"
+
+using testing::ReturnRef;
+
+namespace shill {
+
+class MockMobileOperatorInfo : public MobileOperatorInfo {
+ public:
+  MockMobileOperatorInfo(EventDispatcher *dispatcher,
+                         const std::string &info_owner);
+  ~MockMobileOperatorInfo() override;
+
+  MOCK_CONST_METHOD0(IsMobileNetworkOperatorKnown, bool());
+
+  MOCK_CONST_METHOD0(mccmnc, const std::string &());
+  MOCK_CONST_METHOD0(olp_list,
+                     const std::vector<MobileOperatorInfo::OnlinePortal> &());
+  MOCK_CONST_METHOD0(activation_code, const std::string &());
+  MOCK_CONST_METHOD0(operator_name, const std::string &());
+  MOCK_CONST_METHOD0(country, const std::string &());
+  MOCK_CONST_METHOD0(uuid, const std::string &());
+
+  MOCK_METHOD1(UpdateMCCMNC, void(const std::string &));
+  MOCK_METHOD1(UpdateSID, void(const std::string &));
+  MOCK_METHOD1(UpdateIMSI, void(const std::string &));
+  MOCK_METHOD1(UpdateNID, void(const std::string &));
+  MOCK_METHOD1(UpdateOperatorName, void(const std::string &));
+
+  // Sets up the mock object to return empty strings/vectors etc for all
+  // propeties.
+  void SetEmptyDefaultsForProperties();
+
+ private:
+  std::string empty_mccmnc_;
+  std::vector<MobileOperatorInfo::OnlinePortal> empty_olp_list_;
+  std::string empty_activation_code_;
+  std::string empty_operator_name_;
+  std::string empty_country_;
+  std::string empty_uuid_;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MOBILE_OPERATOR_INFO_H_
diff --git a/cellular/mock_modem.cc b/cellular/mock_modem.cc
new file mode 100644
index 0000000..782c6d5
--- /dev/null
+++ b/cellular/mock_modem.cc
@@ -0,0 +1,17 @@
+// 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/mock_modem.h"
+
+namespace shill {
+
+MockModem::MockModem(const std::string &owner,
+                     const std::string &service,
+                     const std::string &path,
+                     ModemInfo *modem_info)
+    : Modem(owner, service, path, modem_info) {}
+
+MockModem::~MockModem() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem.h b/cellular/mock_modem.h
new file mode 100644
index 0000000..01b7f6c
--- /dev/null
+++ b/cellular/mock_modem.h
@@ -0,0 +1,43 @@
+// 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_MOCK_MODEM_H_
+#define SHILL_CELLULAR_MOCK_MODEM_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem.h"
+
+namespace shill {
+
+class MockModem : public Modem {
+ public:
+  MockModem(const std::string &owner,
+            const std::string &service,
+            const std::string &path,
+            ModemInfo *modem_info);
+  ~MockModem() override;
+
+  // This class only mocks the pure virtual methods; if you need a
+  // more thorough mock, know that modem_unittest.cc depends on the
+  // incompleteness of this mock.
+  MOCK_METHOD1(SetModemStateFromProperties,
+               void(const DBusPropertiesMap &properties));
+  MOCK_CONST_METHOD2(GetLinkName,
+                     bool(const DBusPropertiesMap &modem_properties,
+                          std::string *name));
+  MOCK_CONST_METHOD0(GetModemInterface,
+                     std::string(void));
+  MOCK_METHOD3(ConstructCellular, Cellular *(
+      const std::string &link_name,
+      const std::string &device_name,
+      int ifindex));
+};
+typedef ::testing::StrictMock<MockModem> StrictModem;
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_H_
diff --git a/cellular/mock_modem_cdma_proxy.cc b/cellular/mock_modem_cdma_proxy.cc
new file mode 100644
index 0000000..bceacae
--- /dev/null
+++ b/cellular/mock_modem_cdma_proxy.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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/mock_modem_cdma_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemCDMAProxy::MockModemCDMAProxy() {
+  ON_CALL(*this, Activate(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, GetRegistrationState(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetSignalQuality(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemCDMAProxy::~MockModemCDMAProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_cdma_proxy.h b/cellular/mock_modem_cdma_proxy.h
new file mode 100644
index 0000000..0086691
--- /dev/null
+++ b/cellular/mock_modem_cdma_proxy.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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_MOCK_MODEM_CDMA_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_CDMA_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_cdma_proxy_interface.h"
+
+namespace shill {
+
+class MockModemCDMAProxy : public ModemCDMAProxyInterface {
+ public:
+  MockModemCDMAProxy();
+  ~MockModemCDMAProxy() override;
+
+  MOCK_METHOD4(Activate, void(const std::string &carrier, Error *error,
+                              const ActivationResultCallback &callback,
+                              int timeout));
+  MOCK_METHOD3(GetRegistrationState,
+               void(Error *error, const RegistrationStateCallback &callback,
+                    int timeout));
+  MOCK_METHOD3(GetSignalQuality, void(Error *error,
+                                      const SignalQualityCallback &callback,
+                                      int timeout));
+  MOCK_METHOD0(MEID, const std::string());
+  MOCK_METHOD1(set_activation_state_callback,
+      void(const ActivationStateSignalCallback &callback));
+  MOCK_METHOD1(set_signal_quality_callback,
+      void(const SignalQualitySignalCallback &callback));
+  MOCK_METHOD1(set_registration_state_callback,
+      void(const RegistrationStateSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemCDMAProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_CDMA_PROXY_H_
diff --git a/cellular/mock_modem_gobi_proxy.cc b/cellular/mock_modem_gobi_proxy.cc
new file mode 100644
index 0000000..d99e750
--- /dev/null
+++ b/cellular/mock_modem_gobi_proxy.cc
@@ -0,0 +1,20 @@
+// 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/mock_modem_gobi_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemGobiProxy::MockModemGobiProxy() {
+  ON_CALL(*this, SetCarrier(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+}
+
+MockModemGobiProxy::~MockModemGobiProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_gobi_proxy.h b/cellular/mock_modem_gobi_proxy.h
new file mode 100644
index 0000000..f05c867
--- /dev/null
+++ b/cellular/mock_modem_gobi_proxy.h
@@ -0,0 +1,32 @@
+// 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_MOCK_MODEM_GOBI_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_GOBI_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_gobi_proxy_interface.h"
+
+namespace shill {
+
+class MockModemGobiProxy : public ModemGobiProxyInterface {
+ public:
+  MockModemGobiProxy();
+  ~MockModemGobiProxy() override;
+
+  MOCK_METHOD4(SetCarrier, void(const std::string &carrier,
+                                Error *error, const ResultCallback &callback,
+                                int timeout));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemGobiProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_GOBI_PROXY_H_
diff --git a/cellular/mock_modem_gsm_card_proxy.cc b/cellular/mock_modem_gsm_card_proxy.cc
new file mode 100644
index 0000000..b044628
--- /dev/null
+++ b/cellular/mock_modem_gsm_card_proxy.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 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/mock_modem_gsm_card_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemGSMCardProxy::MockModemGSMCardProxy() {
+  ON_CALL(*this, GetIMEI(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetIMSI(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetSPN(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetMSISDN(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, EnablePIN(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, SendPIN(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, SendPUK(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+  ON_CALL(*this, ChangePIN(_, _, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<2>());
+}
+
+MockModemGSMCardProxy::~MockModemGSMCardProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_gsm_card_proxy.h b/cellular/mock_modem_gsm_card_proxy.h
new file mode 100644
index 0000000..6ea2b9f
--- /dev/null
+++ b/cellular/mock_modem_gsm_card_proxy.h
@@ -0,0 +1,56 @@
+// 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_MOCK_MODEM_GSM_CARD_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_GSM_CARD_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_gsm_card_proxy_interface.h"
+
+namespace shill {
+
+class MockModemGSMCardProxy : public ModemGSMCardProxyInterface {
+ public:
+  MockModemGSMCardProxy();
+  ~MockModemGSMCardProxy() override;
+
+  MOCK_METHOD3(GetIMEI, void(Error *error,
+                             const GSMIdentifierCallback &callback,
+                             int timeout));
+  MOCK_METHOD3(GetIMSI, void(Error *error,
+                             const GSMIdentifierCallback &callback,
+                             int timeout));
+  MOCK_METHOD3(GetSPN, void(Error *error,
+                            const GSMIdentifierCallback &callback,
+                            int timeout));
+  MOCK_METHOD3(GetMSISDN, void(Error *error,
+                               const GSMIdentifierCallback &callback,
+                               int timeout));
+
+  MOCK_METHOD5(EnablePIN, void(const std::string &pin, bool enabled,
+                               Error *error, const ResultCallback &callback,
+                               int timeout));
+  MOCK_METHOD4(SendPIN, void(const std::string &pin,
+                             Error *error, const ResultCallback &callback,
+                             int timeout));
+  MOCK_METHOD5(SendPUK, void(const std::string &puk, const std::string &pin,
+                             Error *error, const ResultCallback &callback,
+                             int timeout));
+  MOCK_METHOD5(ChangePIN, void(const std::string &old_pin,
+                               const std::string &new_pin,
+                               Error *error, const ResultCallback &callback,
+                               int timeout));
+  MOCK_METHOD0(EnabledFacilityLocks, uint32_t());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemGSMCardProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_GSM_CARD_PROXY_H_
diff --git a/cellular/mock_modem_gsm_network_proxy.cc b/cellular/mock_modem_gsm_network_proxy.cc
new file mode 100644
index 0000000..8a1889a
--- /dev/null
+++ b/cellular/mock_modem_gsm_network_proxy.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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/mock_modem_gsm_network_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemGSMNetworkProxy::MockModemGSMNetworkProxy() {
+  ON_CALL(*this, GetRegistrationInfo(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetSignalQuality(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, Register(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Scan(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemGSMNetworkProxy::~MockModemGSMNetworkProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_gsm_network_proxy.h b/cellular/mock_modem_gsm_network_proxy.h
new file mode 100644
index 0000000..070f63d
--- /dev/null
+++ b/cellular/mock_modem_gsm_network_proxy.h
@@ -0,0 +1,46 @@
+// 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_MOCK_MODEM_GSM_NETWORK_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_GSM_NETWORK_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_gsm_network_proxy_interface.h"
+
+namespace shill {
+
+class MockModemGSMNetworkProxy : public ModemGSMNetworkProxyInterface {
+ public:
+  MockModemGSMNetworkProxy();
+  ~MockModemGSMNetworkProxy() override;
+
+  MOCK_METHOD3(GetRegistrationInfo,
+               void(Error *error, const RegistrationInfoCallback &callback,
+                    int timeout));
+  MOCK_METHOD3(GetSignalQuality, void(Error *error,
+                                      const SignalQualityCallback &callback,
+                                      int timeout));
+  MOCK_METHOD4(Register, void(const std::string &network_id, Error *error,
+                              const ResultCallback &callback, int timeout));
+  MOCK_METHOD3(Scan, void(Error *error, const ScanResultsCallback &callback,
+                          int timeout));
+  MOCK_METHOD0(AccessTechnology, uint32_t());
+  MOCK_METHOD1(set_signal_quality_callback,
+      void(const SignalQualitySignalCallback &callback));
+  MOCK_METHOD1(set_network_mode_callback,
+      void(const NetworkModeSignalCallback &callback));
+  MOCK_METHOD1(set_registration_info_callback,
+      void(const RegistrationInfoSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemGSMNetworkProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_GSM_NETWORK_PROXY_H_
diff --git a/cellular/mock_modem_info.cc b/cellular/mock_modem_info.cc
new file mode 100644
index 0000000..262dd0e
--- /dev/null
+++ b/cellular/mock_modem_info.cc
@@ -0,0 +1,55 @@
+// 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/mock_modem_info.h"
+
+namespace shill {
+
+MockModemInfo::MockModemInfo() :
+    ModemInfo(nullptr, nullptr, nullptr, nullptr, nullptr),
+    mock_pending_activation_store_(nullptr) {}
+
+MockModemInfo::MockModemInfo(ControlInterface *control,
+                             EventDispatcher *dispatcher,
+                             Metrics *metrics,
+                             Manager *manager,
+                             GLib *glib) :
+    ModemInfo(control, dispatcher, metrics, manager, glib),
+    mock_pending_activation_store_(nullptr) {
+  SetMockMembers();
+}
+
+MockModemInfo::~MockModemInfo() {}
+
+void MockModemInfo::SetMockMembers() {
+  // These are always replaced by mocks.
+  // Assumes ownership.
+  set_pending_activation_store(new MockPendingActivationStore());
+  mock_pending_activation_store_ =
+      static_cast<MockPendingActivationStore*>(pending_activation_store());
+  // These are replaced by mocks only if current unset in ModemInfo.
+  if (control_interface() == nullptr) {
+    mock_control_.reset(new MockControl());
+    set_control_interface(mock_control_.get());
+  }
+  if (dispatcher() == nullptr) {
+    mock_dispatcher_.reset(new MockEventDispatcher());
+    set_event_dispatcher(mock_dispatcher_.get());
+  }
+  if (metrics() == nullptr) {
+    mock_metrics_.reset(new MockMetrics(dispatcher()));
+    set_metrics(mock_metrics_.get());
+  }
+  if (glib() == nullptr) {
+    mock_glib_.reset(new MockGLib());
+    set_glib(mock_glib_.get());
+  }
+  if (manager() == nullptr) {
+    mock_manager_.reset(new MockManager(control_interface(), dispatcher(),
+                                        metrics(), glib()));
+    set_manager(mock_manager_.get());
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_info.h b/cellular/mock_modem_info.h
new file mode 100644
index 0000000..093ed76
--- /dev/null
+++ b/cellular/mock_modem_info.h
@@ -0,0 +1,82 @@
+// 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_MOCK_MODEM_INFO_H_
+#define SHILL_CELLULAR_MOCK_MODEM_INFO_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_info.h"
+#include "shill/mock_control.h"
+#include "shill/mock_event_dispatcher.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+#include "shill/mock_pending_activation_store.h"
+
+namespace shill {
+
+class MockModemInfo : public ModemInfo {
+ public:
+  MockModemInfo();
+
+  // All nullptr parameters are replaced by mock objects.
+  MockModemInfo(ControlInterface *control,
+                EventDispatcher *dispatcher,
+                Metrics *metrics,
+                Manager *manager,
+                GLib *glib);
+
+  ~MockModemInfo() override;
+
+  // Replaces data members in ModemInfo by mock objects.
+  // The following are relaced by mocks if they are nullptr: control_interface,
+  // dispatcher, metrics, manager, glib.
+  // The following are always replaced by mocks: pending_activation_store.
+  void SetMockMembers();
+
+  // Accessors for mock objects
+  MockPendingActivationStore* mock_pending_activation_store() const {
+    return mock_pending_activation_store_;
+  }
+  MockControl* mock_control_interface() const {
+    return mock_control_.get();
+  }
+  MockEventDispatcher* mock_dispatcher() const {
+    return mock_dispatcher_.get();
+  }
+  MockMetrics* mock_metrics() const {
+    return mock_metrics_.get();
+  }
+  MockManager* mock_manager() const {
+    return mock_manager_.get();
+  }
+  MockGLib* mock_glib() const {
+    return mock_glib_.get();
+  }
+
+  MOCK_METHOD0(Start, void());
+  MOCK_METHOD0(Stop, void());
+  MOCK_METHOD1(OnDeviceInfoAvailable, void(const std::string &link_name));
+
+ private:
+  std::unique_ptr<MockControl> mock_control_;
+  std::unique_ptr<MockEventDispatcher> mock_dispatcher_;
+  std::unique_ptr<MockMetrics> mock_metrics_;
+  std::unique_ptr<MockManager> mock_manager_;
+  std::unique_ptr<MockGLib> mock_glib_;
+
+  // owned by ModemInfo
+  MockPendingActivationStore *mock_pending_activation_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockModemInfo);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_INFO_H_
diff --git a/cellular/mock_modem_manager_proxy.cc b/cellular/mock_modem_manager_proxy.cc
new file mode 100644
index 0000000..459fc86
--- /dev/null
+++ b/cellular/mock_modem_manager_proxy.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2011 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/mock_modem_manager_proxy.h"
+
+namespace shill {
+
+MockModemManagerProxy::MockModemManagerProxy() {}
+
+MockModemManagerProxy::~MockModemManagerProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_manager_proxy.h b/cellular/mock_modem_manager_proxy.h
new file mode 100644
index 0000000..aca92db
--- /dev/null
+++ b/cellular/mock_modem_manager_proxy.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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_MOCK_MODEM_MANAGER_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_MANAGER_PROXY_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_manager_proxy_interface.h"
+
+namespace shill {
+
+class MockModemManagerProxy : public ModemManagerProxyInterface {
+ public:
+  MockModemManagerProxy();
+  ~MockModemManagerProxy() override;
+
+  MOCK_METHOD0(EnumerateDevices, std::vector<DBus::Path>());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemManagerProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_MANAGER_PROXY_H_
diff --git a/cellular/mock_modem_proxy.cc b/cellular/mock_modem_proxy.cc
new file mode 100644
index 0000000..47ae5f1
--- /dev/null
+++ b/cellular/mock_modem_proxy.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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/mock_modem_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemProxy::MockModemProxy() {
+  ON_CALL(*this, Enable(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+  ON_CALL(*this, Disconnect(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, GetModemInfo(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+}
+
+MockModemProxy::~MockModemProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_proxy.h b/cellular/mock_modem_proxy.h
new file mode 100644
index 0000000..ae1bade
--- /dev/null
+++ b/cellular/mock_modem_proxy.h
@@ -0,0 +1,36 @@
+// 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_MOCK_MODEM_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_PROXY_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_proxy_interface.h"
+
+namespace shill {
+
+class MockModemProxy : public ModemProxyInterface {
+ public:
+  MockModemProxy();
+  ~MockModemProxy() override;
+
+  MOCK_METHOD4(Enable, void(bool enable, Error *error,
+                            const ResultCallback &callback, int timeout));
+  MOCK_METHOD3(Disconnect, void(Error *error, const ResultCallback &callback,
+                                int timeout));
+  MOCK_METHOD3(GetModemInfo, void(Error *error,
+                                  const ModemInfoCallback &callback,
+                                  int timeout));
+  MOCK_METHOD1(set_state_changed_callback,
+               void(const ModemStateChangedSignalCallback &callback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_PROXY_H_
diff --git a/cellular/mock_modem_simple_proxy.cc b/cellular/mock_modem_simple_proxy.cc
new file mode 100644
index 0000000..9e9dc94
--- /dev/null
+++ b/cellular/mock_modem_simple_proxy.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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/mock_modem_simple_proxy.h"
+
+#include "shill/testing.h"
+
+using testing::_;
+
+namespace shill {
+
+MockModemSimpleProxy::MockModemSimpleProxy() {
+  ON_CALL(*this, GetModemStatus(_, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<0>());
+  ON_CALL(*this, Connect(_, _, _, _))
+      .WillByDefault(SetOperationFailedInArgumentAndWarn<1>());
+}
+
+MockModemSimpleProxy::~MockModemSimpleProxy() {}
+
+}  // namespace shill
diff --git a/cellular/mock_modem_simple_proxy.h b/cellular/mock_modem_simple_proxy.h
new file mode 100644
index 0000000..df7b951
--- /dev/null
+++ b/cellular/mock_modem_simple_proxy.h
@@ -0,0 +1,33 @@
+// 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_MOCK_MODEM_SIMPLE_PROXY_H_
+#define SHILL_CELLULAR_MOCK_MODEM_SIMPLE_PROXY_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "shill/cellular/modem_simple_proxy_interface.h"
+
+namespace shill {
+
+class MockModemSimpleProxy : public ModemSimpleProxyInterface {
+ public:
+  MockModemSimpleProxy();
+  ~MockModemSimpleProxy() override;
+
+  MOCK_METHOD3(GetModemStatus, void(Error *error,
+                                    const DBusPropertyMapCallback &callback,
+                                    int timeout));
+  MOCK_METHOD4(Connect, void(const DBusPropertiesMap &properties,
+                             Error *error, const ResultCallback &callback,
+                             int timeout));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockModemSimpleProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_MODEM_SIMPLE_PROXY_H_
diff --git a/cellular/mock_out_of_credits_detector.cc b/cellular/mock_out_of_credits_detector.cc
new file mode 100644
index 0000000..76e9ccf
--- /dev/null
+++ b/cellular/mock_out_of_credits_detector.cc
@@ -0,0 +1,20 @@
+// 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/mock_out_of_credits_detector.h"
+
+#include <gmock/gmock.h>
+
+namespace shill {
+
+MockOutOfCreditsDetector::MockOutOfCreditsDetector(
+    EventDispatcher *dispatcher,
+    Manager *manager,
+    Metrics *metrics,
+    CellularService *service)
+    : OutOfCreditsDetector(dispatcher, manager, metrics, service) {}
+
+MockOutOfCreditsDetector::~MockOutOfCreditsDetector() {}
+
+}  // namespace shill
diff --git a/cellular/mock_out_of_credits_detector.h b/cellular/mock_out_of_credits_detector.h
new file mode 100644
index 0000000..7fed4e5
--- /dev/null
+++ b/cellular/mock_out_of_credits_detector.h
@@ -0,0 +1,37 @@
+// 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_MOCK_OUT_OF_CREDITS_DETECTOR_H_
+#define SHILL_CELLULAR_MOCK_OUT_OF_CREDITS_DETECTOR_H_
+
+#include <gmock/gmock.h>
+
+#include "shill/cellular/out_of_credits_detector.h"
+
+namespace shill {
+
+class MockOutOfCreditsDetector : public OutOfCreditsDetector {
+ public:
+  MockOutOfCreditsDetector(EventDispatcher *dispatcher,
+                           Manager *manager,
+                           Metrics *metrics,
+                           CellularService *service);
+  ~MockOutOfCreditsDetector() override;
+
+  MOCK_METHOD0(ResetDetector, void());
+  MOCK_CONST_METHOD0(IsDetecting, bool());
+  MOCK_METHOD2(NotifyServiceStateChanged,
+               void(Service::ConnectState old_state,
+                    Service::ConnectState new_state));
+  MOCK_METHOD1(NotifySubscriptionStateChanged,
+               void(uint32_t subscription_state));
+  MOCK_CONST_METHOD0(out_of_credits, bool());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockOutOfCreditsDetector);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MOCK_OUT_OF_CREDITS_DETECTOR_H_
diff --git a/cellular/modem.cc b/cellular/modem.cc
new file mode 100644
index 0000000..3076623
--- /dev/null
+++ b/cellular/modem.cc
@@ -0,0 +1,194 @@
+// 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/modem.h"
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/logging.h"
+#include "shill/manager.h"
+#include "shill/net/rtnl_handler.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using base::Unretained;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kModem;
+static string ObjectID(Modem *m) { return m->path().c_str(); }
+}
+
+// TODO(petkov): Consider generating these in mm/mm-modem.h.
+const char Modem::kPropertyLinkName[] = "Device";
+const char Modem::kPropertyIPMethod[] = "IpMethod";
+const char Modem::kPropertyType[] = "Type";
+
+// statics
+constexpr char Modem::kFakeDevNameFormat[];
+const char Modem::kFakeDevAddress[] = "000000000000";
+const int Modem::kFakeDevInterfaceIndex = -1;
+size_t Modem::fake_dev_serial_ = 0;
+
+Modem::Modem(const string &owner,
+             const string &service,
+             const string &path,
+             ModemInfo * modem_info)
+    : owner_(owner),
+      service_(service),
+      path_(path),
+      modem_info_(modem_info),
+      type_(Cellular::kTypeInvalid),
+      pending_device_info_(false),
+      rtnl_handler_(RTNLHandler::GetInstance()),
+      proxy_factory_(ProxyFactory::GetInstance()) {
+  LOG(INFO) << "Modem created: " << owner << " at " << path;
+}
+
+Modem::~Modem() {
+  LOG(INFO) << "Modem destructed: " << owner_ << " at " << path_;
+  if (device_) {
+    device_->DestroyService();
+    modem_info_->manager()->device_info()->DeregisterDevice(device_);
+  }
+}
+
+void Modem::Init() {
+  dbus_properties_proxy_.reset(
+      proxy_factory_->CreateDBusPropertiesProxy(path(), owner()));
+  dbus_properties_proxy_->set_modem_manager_properties_changed_callback(
+      Bind(&Modem::OnModemManagerPropertiesChanged, Unretained(this)));
+  dbus_properties_proxy_->set_properties_changed_callback(
+      Bind(&Modem::OnDBusPropertiesChanged, Unretained(this)));
+}
+
+void Modem::OnDeviceInfoAvailable(const string &link_name) {
+  SLOG(this, 2) << __func__;
+  if (pending_device_info_ && link_name_ == link_name) {
+    // pending_device_info_ is only set if we've already been through
+    // CreateDeviceFromModemProperties() and saved our initial
+    // properties already
+    pending_device_info_ = false;
+    CreateDeviceFromModemProperties(initial_properties_);
+  }
+}
+
+Cellular *Modem::ConstructCellular(const string &link_name,
+                                   const string &address,
+                                   int interface_index) {
+  LOG(INFO) << "Creating a cellular device on link " << link_name
+            << " interface index " << interface_index << ".";
+  return new Cellular(modem_info_,
+                      link_name,
+                      address,
+                      interface_index,
+                      type_,
+                      owner_,
+                      service_,
+                      path_,
+                      ProxyFactory::GetInstance());
+}
+
+void Modem::CreateDeviceFromModemProperties(
+    const DBusInterfaceToProperties &properties) {
+  SLOG(this, 2) << __func__;
+
+  if (device_.get()) {
+    return;
+  }
+
+  DBusInterfaceToProperties::const_iterator properties_it =
+      properties.find(GetModemInterface());
+  if (properties_it == properties.end()) {
+    LOG(ERROR) << "Unable to find modem interface properties.";
+    return;
+  }
+
+  string mac_address;
+  int interface_index = -1;
+  if (GetLinkName(properties_it->second, &link_name_)) {
+    GetDeviceParams(&mac_address, &interface_index);
+    if (interface_index < 0) {
+      LOG(ERROR) << "Unable to create cellular device -- no interface index.";
+      return;
+    }
+    if (mac_address.empty()) {
+      // Save our properties, wait for OnDeviceInfoAvailable to be called.
+      LOG(WARNING)
+          << "No hardware address, device creation pending device info.";
+      initial_properties_ = properties;
+      pending_device_info_ = true;
+      return;
+    }
+    // Got the interface index and MAC address. Fall-through to actually
+    // creating the Cellular object.
+  } else {
+    // Probably a PPP dongle.
+    LOG(INFO) << "Cellular device without link name; assuming PPP dongle.";
+    link_name_ = base::StringPrintf(kFakeDevNameFormat, fake_dev_serial_++);
+    mac_address = kFakeDevAddress;
+    interface_index = kFakeDevInterfaceIndex;
+  }
+
+  if (modem_info_->manager()->device_info()->IsDeviceBlackListed(link_name_)) {
+    LOG(INFO) << "Not creating cellular device for blacklisted interface "
+              << link_name_ << ".";
+    return;
+  }
+
+  device_ = ConstructCellular(link_name_, mac_address, interface_index);
+  // Give the device a chance to extract any capability-specific properties.
+  for (properties_it = properties.begin(); properties_it != properties.end();
+       ++properties_it) {
+    device_->OnDBusPropertiesChanged(
+        properties_it->first, properties_it->second, vector<string>());
+  }
+
+  modem_info_->manager()->device_info()->RegisterDevice(device_);
+}
+
+bool Modem::GetDeviceParams(string *mac_address, int *interface_index) {
+  // TODO(petkov): Get the interface index from DeviceInfo, similar to the MAC
+  // address below.
+  *interface_index = rtnl_handler_->GetInterfaceIndex(link_name_);
+  if (*interface_index < 0) {
+    return false;
+  }
+
+  ByteString address_bytes;
+  if (!modem_info_->manager()->device_info()->GetMACAddress(*interface_index,
+                                                            &address_bytes)) {
+    return false;
+  }
+
+  *mac_address = address_bytes.HexEncode();
+  return true;
+}
+
+void Modem::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<string> &invalidated_properties) {
+  SLOG(this, 2) << __func__;
+  SLOG(this, 3) << "PropertiesChanged signal received.";
+  if (device_.get()) {
+    device_->OnDBusPropertiesChanged(interface,
+                                      changed_properties,
+                                      invalidated_properties);
+  }
+}
+
+void Modem::OnModemManagerPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &properties) {
+  vector<string> invalidated_properties;
+  OnDBusPropertiesChanged(interface, properties, invalidated_properties);
+}
+
+}  // namespace shill
diff --git a/cellular/modem.h b/cellular/modem.h
new file mode 100644
index 0000000..b01ce77
--- /dev/null
+++ b/cellular/modem.h
@@ -0,0 +1,180 @@
+// 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_MODEM_H_
+#define SHILL_CELLULAR_MODEM_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/files/file_util.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/dbus_objectmanager_proxy_interface.h"
+#include "shill/cellular/modem_info.h"
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/refptr_types.h"
+
+namespace shill {
+
+class ProxyFactory;
+
+// Handles an instance of ModemManager.Modem and an instance of a Cellular
+// device.
+class Modem {
+ public:
+  // |owner| is the ModemManager DBus service owner (e.g., ":1.17"). |path| is
+  // the ModemManager.Modem DBus object path (e.g.,
+  // "/org/chromium/ModemManager/Gobi/0").
+  Modem(const std::string &owner,
+        const std::string &service,
+        const std::string &path,
+        ModemInfo *modem_info);
+  virtual ~Modem();
+
+  // Asynchronously initializes support for the modem.
+  // If the |properties| are valid and the MAC address is present,
+  // constructs and registers a Cellular device in |device_| based on
+  // |properties|.
+  virtual void CreateDeviceFromModemProperties(
+      const DBusInterfaceToProperties &properties);
+
+  void OnDeviceInfoAvailable(const std::string &link_name);
+
+  const std::string &owner() const { return owner_; }
+  const std::string &service() const { return service_; }
+  const std::string &path() const { return path_; }
+
+  void set_type(Cellular::Type type) { type_ = type; }
+
+ protected:
+  static const char kPropertyLinkName[];
+  static const char kPropertyIPMethod[];
+  static const char kPropertyType[];
+
+  virtual void Init();
+
+  CellularRefPtr device() const { return device_; }
+
+  virtual Cellular *ConstructCellular(const std::string &link_name,
+                                      const std::string &device_name,
+                                      int interface_index);
+  virtual bool GetLinkName(const DBusPropertiesMap &properties,
+                           std::string *name) const = 0;
+  // Returns the name of the DBUS Modem interface.
+  virtual std::string GetModemInterface(void) const = 0;
+
+ 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);
+  FRIEND_TEST(ModemTest, CreateDevicePPP);
+  FRIEND_TEST(ModemTest, EarlyDeviceProperties);
+  FRIEND_TEST(ModemTest, GetDeviceParams);
+  FRIEND_TEST(ModemTest, Init);
+  FRIEND_TEST(ModemTest, PendingDevicePropertiesAndCreate);
+
+  // Constants associated with fake network devices for PPP dongles.
+  // See |fake_dev_serial_|, below, for more info.
+  static constexpr char kFakeDevNameFormat[] = "no_netdev_%zu";
+  static const char kFakeDevAddress[];
+  static const int kFakeDevInterfaceIndex;
+
+  // Find the |mac_address| and |interface_index| for the kernel
+  // network device with name |link_name|. Returns true iff both
+  // |mac_address| and |interface_index| were found. Modifies
+  // |interface_index| even on failure.
+  virtual bool GetDeviceParams(std::string *mac_address, int *interface_index);
+
+  virtual void OnDBusPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &changed_properties,
+      const std::vector<std::string> &invalidated_properties);
+  virtual void OnModemManagerPropertiesChanged(
+      const std::string &interface,
+      const DBusPropertiesMap &properties);
+
+  // A proxy to the org.freedesktop.DBusProperties interface used to obtain
+  // ModemManager.Modem properties and watch for property changes
+  std::unique_ptr<DBusPropertiesProxyInterface> dbus_properties_proxy_;
+
+  DBusInterfaceToProperties initial_properties_;
+
+  const std::string owner_;
+  const std::string service_;
+  const std::string path_;
+
+  CellularRefPtr device_;
+
+  ModemInfo *modem_info_;
+  std::string link_name_;
+  Cellular::Type type_;
+  bool pending_device_info_;
+  RTNLHandler *rtnl_handler_;
+
+  // Store cached copies of singletons for speed/ease of testing.
+  ProxyFactory *proxy_factory_;
+
+  // Serial number used to uniquify fake device names for Cellular
+  // devices that don't have network devices. (Names must be unique
+  // for D-Bus, and PPP dongles don't have network devices.)
+  static size_t fake_dev_serial_;
+
+  DISALLOW_COPY_AND_ASSIGN(Modem);
+};
+
+class ModemClassic : public Modem {
+ public:
+  ModemClassic(const std::string &owner,
+               const std::string &service,
+               const std::string &path,
+               ModemInfo *modem_info);
+  ~ModemClassic() override;
+
+  // Gathers information and passes it to CreateDeviceFromModemProperties.
+  void CreateDeviceClassic(const DBusPropertiesMap &modem_properties);
+
+ protected:
+  virtual bool GetLinkName(const DBusPropertiesMap &modem_properties,
+                           std::string *name) const;
+  virtual std::string GetModemInterface(void) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ModemClassic);
+};
+
+class Modem1 : public Modem {
+ public:
+  Modem1(const std::string &owner,
+         const std::string &service,
+         const std::string &path,
+         ModemInfo *modem_info);
+  ~Modem1() override;
+
+  // Gathers information and passes it to CreateDeviceFromModemProperties.
+  void CreateDeviceMM1(const DBusInterfaceToProperties &properties);
+
+ protected:
+  virtual bool GetLinkName(const DBusPropertiesMap &modem_properties,
+                           std::string *name) const;
+  virtual std::string GetModemInterface(void) const;
+
+ private:
+  friend class Modem1Test;
+
+  DISALLOW_COPY_AND_ASSIGN(Modem1);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_H_
diff --git a/cellular/modem_1.cc b/cellular/modem_1.cc
new file mode 100644
index 0000000..c62b56d
--- /dev/null
+++ b/cellular/modem_1.cc
@@ -0,0 +1,86 @@
+// 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/modem.h"
+
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/device_info.h"
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+Modem1::Modem1(const string &owner,
+               const string &service,
+               const string &path,
+               ModemInfo *modem_info)
+    : Modem(owner, service, path, modem_info) {}
+
+Modem1::~Modem1() {}
+
+bool Modem1::GetLinkName(const DBusPropertiesMap &modem_props,
+                         string *name) const {
+  DBusPropertiesMap::const_iterator props_it;
+  string net_port;
+
+  props_it = modem_props.find(MM_MODEM_PROPERTY_PORTS);
+  if (props_it == modem_props.end()) {
+    LOG(ERROR) << "Device missing property: " << MM_MODEM_PROPERTY_PORTS;
+    return false;
+  }
+
+  vector<DBus::Struct<string, uint32_t>> ports = props_it->second;
+  for (const auto &port_pair : ports) {
+    if (port_pair._2 == MM_MODEM_PORT_TYPE_NET) {
+      net_port = port_pair._1;
+      break;
+    }
+  }
+
+  if (net_port.empty()) {
+    LOG(ERROR) << "Could not find net port used by the device.";
+    return false;
+  }
+
+  *name = net_port;
+  return true;
+}
+
+void Modem1::CreateDeviceMM1(const DBusInterfaceToProperties &properties) {
+  Init();
+  uint32_t 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;
+  }
+  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.
+  CreateDeviceFromModemProperties(properties);
+}
+
+string Modem1::GetModemInterface(void) const {
+  return string(MM_DBUS_INTERFACE_MODEM);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_1_unittest.cc b/cellular/modem_1_unittest.cc
new file mode 100644
index 0000000..44ac752
--- /dev/null
+++ b/cellular/modem_1_unittest.cc
@@ -0,0 +1,132 @@
+// 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/modem.h"
+
+#include <base/files/scoped_temp_dir.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/cellular_capability.h"
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/dbus_property_matchers.h"
+#include "shill/event_dispatcher.h"
+#include "shill/manager.h"
+#include "shill/mock_dbus_properties_proxy.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_proxy_factory.h"
+#include "shill/net/mock_rtnl_handler.h"
+#include "shill/net/rtnl_handler.h"
+#include "shill/testing.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+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 kService[] = "org.chromium.ModemManager";
+const char kPath[] = "/org/chromium/ModemManager/Gobi/0";
+const unsigned char kAddress[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+
+}  // namespace
+
+class Modem1Test : public Test {
+ public:
+  Modem1Test()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        device_info_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                     modem_info_.metrics(), modem_info_.manager()),
+        proxy_(new MockDBusPropertiesProxy()),
+        modem_(
+            new Modem1(
+                kOwner,
+                kService,
+                kPath,
+                &modem_info_)) {}
+  virtual void SetUp();
+  virtual void TearDown();
+
+  void ReplaceSingletons() {
+    modem_->rtnl_handler_ = &rtnl_handler_;
+    modem_->proxy_factory_ = &proxy_factory_;
+  }
+
+ protected:
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  MockDeviceInfo device_info_;
+  std::unique_ptr<MockDBusPropertiesProxy> proxy_;
+  MockProxyFactory proxy_factory_;
+  std::unique_ptr<Modem1> modem_;
+  MockRTNLHandler rtnl_handler_;
+  ByteString expected_address_;
+};
+
+void Modem1Test::SetUp() {
+  EXPECT_EQ(kOwner, modem_->owner_);
+  EXPECT_EQ(kService, modem_->service_);
+  EXPECT_EQ(kPath, modem_->path_);
+  ReplaceSingletons();
+  expected_address_ = ByteString(kAddress, arraysize(kAddress));
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(kLinkName)).
+      WillRepeatedly(Return(kTestInterfaceIndex));
+
+  EXPECT_CALL(*modem_info_.mock_manager(), device_info())
+      .WillRepeatedly(Return(&device_info_));
+  EXPECT_CALL(device_info_, GetMACAddress(kTestInterfaceIndex, _)).
+      WillOnce(DoAll(SetArgumentPointee<1>(expected_address_),
+                     Return(true)));
+}
+
+void Modem1Test::TearDown() {
+  modem_.reset();
+}
+
+TEST_F(Modem1Test, CreateDeviceMM1) {
+  DBusInterfaceToProperties properties;
+  DBusPropertiesMap modem_properties;
+  DBus::Variant lock;
+  lock.writer().append_uint32(MM_MODEM_LOCK_NONE);
+  modem_properties[MM_MODEM_PROPERTY_UNLOCKREQUIRED] = lock;
+
+  DBus::Variant ports_variant;
+  DBus::MessageIter ports_message_iter = ports_variant.writer();
+  DBus::MessageIter ports_array_iter = ports_message_iter.new_array("(su)");
+  DBus::MessageIter port_struct_iter = ports_array_iter.new_struct();
+  port_struct_iter.append_string(kLinkName);
+  port_struct_iter.append_uint32(MM_MODEM_PORT_TYPE_NET);
+  ports_array_iter.close_container(port_struct_iter);
+  ports_message_iter.close_container(ports_array_iter);
+  modem_properties[MM_MODEM_PROPERTY_PORTS] = ports_variant;
+
+  properties[MM_DBUS_INTERFACE_MODEM] = modem_properties;
+
+  DBusPropertiesMap modem3gpp_properties;
+  DBus::Variant registration_state_variant;
+  registration_state_variant.writer().append_uint32(
+      MM_MODEM_3GPP_REGISTRATION_STATE_HOME);
+  modem3gpp_properties[MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE] =
+      registration_state_variant;
+  properties[MM_DBUS_INTERFACE_MODEM_MODEM3GPP] = modem3gpp_properties;
+
+  EXPECT_CALL(proxy_factory_, CreateDBusPropertiesProxy(kPath, kOwner))
+      .WillOnce(ReturnAndReleasePointee(&proxy_));
+  modem_->CreateDeviceMM1(properties);
+  EXPECT_TRUE(modem_->device().get());
+  EXPECT_TRUE(modem_->device()->capability_->IsRegistered());
+}
+
+}  // namespace shill
diff --git a/cellular/modem_cdma_proxy.cc b/cellular/modem_cdma_proxy.cc
new file mode 100644
index 0000000..3cfd275
--- /dev/null
+++ b/cellular/modem_cdma_proxy.cc
@@ -0,0 +1,155 @@
+// 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/modem_cdma_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+ModemCDMAProxy::ModemCDMAProxy(
+                               DBus::Connection *connection,
+                               const string &path,
+                               const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemCDMAProxy::~ModemCDMAProxy() {}
+
+void ModemCDMAProxy::Activate(const string &carrier, Error *error,
+                              const ActivationResultCallback &callback,
+                              int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ActivateAsync, callback,
+                     error, &CellularError::FromDBusError, timeout,
+                     carrier);
+}
+
+void ModemCDMAProxy::GetRegistrationState(
+    Error *error,
+    const RegistrationStateCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetRegistrationStateAsync,
+                     callback, error, &CellularError::FromDBusError, timeout);
+}
+
+void ModemCDMAProxy::GetSignalQuality(Error *error,
+                                      const SignalQualityCallback &callback,
+                                      int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetSignalQualityAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+const string ModemCDMAProxy::MEID() {
+  LOG(INFO) << "ModemCDMAProxy::" << __func__;
+  SLOG(&proxy_.path(), 2) << __func__;
+  try {
+    return proxy_.Meid();
+  } catch (const DBus::Error &e) {
+    LOG(FATAL) << "DBus exception: " << e.name() << ": " << e.what();
+    return string();  // Make the compiler happy.
+  }
+}
+
+void ModemCDMAProxy::set_activation_state_callback(
+    const ActivationStateSignalCallback &callback) {
+  proxy_.set_activation_state_callback(callback);
+}
+
+void ModemCDMAProxy::set_signal_quality_callback(
+    const SignalQualitySignalCallback &callback) {
+  proxy_.set_signal_quality_callback(callback);
+}
+
+void ModemCDMAProxy::set_registration_state_callback(
+    const RegistrationStateSignalCallback &callback) {
+  proxy_.set_registration_state_callback(callback);
+}
+
+ModemCDMAProxy::Proxy::Proxy(DBus::Connection *connection,
+                             const string &path,
+                             const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemCDMAProxy::Proxy::~Proxy() {}
+
+void ModemCDMAProxy::Proxy::set_activation_state_callback(
+    const ActivationStateSignalCallback &callback) {
+  activation_state_callback_ = callback;
+}
+
+void ModemCDMAProxy::Proxy::set_signal_quality_callback(
+    const SignalQualitySignalCallback &callback) {
+  signal_quality_callback_ = callback;
+}
+
+void ModemCDMAProxy::Proxy::set_registration_state_callback(
+    const RegistrationStateSignalCallback &callback) {
+  registration_state_callback_ = callback;
+}
+
+void ModemCDMAProxy::Proxy::ActivationStateChanged(
+    const uint32_t &activation_state,
+    const uint32_t &activation_error,
+    const DBusPropertiesMap &status_changes) {
+  SLOG(&path(), 2) << __func__ << "(" << activation_state << ", "
+                   << activation_error << ")";
+  activation_state_callback_.Run(activation_state,
+                                  activation_error,
+                                  status_changes);
+}
+
+void ModemCDMAProxy::Proxy::SignalQuality(const uint32_t &quality) {
+  SLOG(&path(), 2) << __func__ << "(" << quality << ")";
+  signal_quality_callback_.Run(quality);
+}
+
+void ModemCDMAProxy::Proxy::RegistrationStateChanged(
+    const uint32_t &cdma_1x_state,
+    const uint32_t &evdo_state) {
+  SLOG(&path(), 2) << __func__ << "(" << cdma_1x_state << ", "
+                   << evdo_state << ")";
+  registration_state_callback_.Run(cdma_1x_state, evdo_state);
+}
+
+void ModemCDMAProxy::Proxy::ActivateCallback(const uint32_t &status,
+                                             const DBus::Error &dberror,
+                                             void *data) {
+  SLOG(&path(), 2) << __func__ << "(" << status << ")";
+  unique_ptr<ActivationResultCallback> callback(
+      reinterpret_cast<ActivationResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(status, error);
+}
+
+void ModemCDMAProxy::Proxy::GetRegistrationStateCallback(
+    const uint32_t &state_1x, const uint32_t &state_evdo,
+    const DBus::Error &dberror, void *data) {
+  SLOG(&path(), 2) << __func__ << "(" << state_1x << ", " << state_evdo << ")";
+  unique_ptr<RegistrationStateCallback> callback(
+      reinterpret_cast<RegistrationStateCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(state_1x, state_evdo, error);
+}
+
+
+void ModemCDMAProxy::Proxy::GetSignalQualityCallback(const uint32_t &quality,
+                                                     const DBus::Error &dberror,
+                                                     void *data) {
+  SLOG(&path(), 2) << __func__ << "(" << quality << ")";
+  unique_ptr<SignalQualityCallback> callback(
+      reinterpret_cast<SignalQualityCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(quality, error);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_cdma_proxy.h b/cellular/modem_cdma_proxy.h
new file mode 100644
index 0000000..68b75d8
--- /dev/null
+++ b/cellular/modem_cdma_proxy.h
@@ -0,0 +1,97 @@
+// 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_MODEM_CDMA_PROXY_H_
+#define SHILL_CELLULAR_MODEM_CDMA_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.Modem.Cdma.h"
+#include "shill/cellular/modem_cdma_proxy_interface.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.CDMA.
+class ModemCDMAProxy : public ModemCDMAProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem.CDMA DBus object proxy at |path| owned by
+  // |service|.
+  ModemCDMAProxy(DBus::Connection *connection,
+                 const std::string &path,
+                 const std::string &service);
+  ~ModemCDMAProxy() override;
+
+  // Inherited from ModemCDMAProxyInterface.
+  virtual void Activate(const std::string &carrier, Error *error,
+                        const ActivationResultCallback &callback, int timeout);
+  virtual void GetRegistrationState(Error *error,
+                                    const RegistrationStateCallback &callback,
+                                    int timeout);
+  virtual void GetSignalQuality(Error *error,
+                                const SignalQualityCallback &callback,
+                                int timeout);
+  virtual const std::string MEID();
+
+  virtual void set_activation_state_callback(
+      const ActivationStateSignalCallback &callback);
+  virtual void set_signal_quality_callback(
+      const SignalQualitySignalCallback &callback);
+  virtual void set_registration_state_callback(
+      const RegistrationStateSignalCallback &callback);
+
+ public:
+  class Proxy
+      : public org::freedesktop::ModemManager::Modem::Cdma_proxy,
+        public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    void set_activation_state_callback(
+        const ActivationStateSignalCallback &callback);
+    void set_signal_quality_callback(
+        const SignalQualitySignalCallback &callback);
+    void set_registration_state_callback(
+        const RegistrationStateSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem::Cdma_proxy.
+    virtual void ActivationStateChanged(
+        const uint32_t &activation_state,
+        const uint32_t &activation_error,
+        const DBusPropertiesMap &status_changes);
+    virtual void SignalQuality(const uint32_t &quality);
+    virtual void RegistrationStateChanged(const uint32_t &cdma_1x_state,
+                                          const uint32_t &evdo_state);
+
+    // Method callbacks inherited from ModemManager::Modem::Cdma_proxy.
+    virtual void ActivateCallback(const uint32_t &status,
+                                  const DBus::Error &dberror, void *data);
+    virtual void GetRegistrationStateCallback(const uint32_t &state_1x,
+                                              const uint32_t &state_evdo,
+                                              const DBus::Error &error,
+                                              void *data);
+    virtual void GetSignalQualityCallback(const uint32_t &quality,
+                                          const DBus::Error &dberror,
+                                          void *data);
+
+    ActivationStateSignalCallback activation_state_callback_;
+    SignalQualitySignalCallback signal_quality_callback_;
+    RegistrationStateSignalCallback registration_state_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ModemCDMAProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_CDMA_PROXY_H_
diff --git a/cellular/modem_cdma_proxy_interface.h b/cellular/modem_cdma_proxy_interface.h
new file mode 100644
index 0000000..481b412
--- /dev/null
+++ b/cellular/modem_cdma_proxy_interface.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 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_MODEM_CDMA_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_CDMA_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+
+class Error;
+
+typedef base::Callback<void(uint32_t)> SignalQualitySignalCallback;
+typedef base::Callback<void(uint32_t, uint32_t)>
+    RegistrationStateSignalCallback;
+
+typedef base::Callback<void(uint32_t, const Error &)> ActivationResultCallback;
+typedef base::Callback<void(uint32_t, const Error &)> SignalQualityCallback;
+typedef base::Callback<void(uint32_t, uint32_t,
+                            const Error &)> RegistrationStateCallback;
+
+// These are the methods that a ModemManager.Modem.CDMA proxy must support.
+// The interface is provided so that it can be mocked in tests.
+// All calls are made asynchronously. Call completion is signalled via
+// the callbacks passed to the methods.
+class ModemCDMAProxyInterface {
+ public:
+  virtual ~ModemCDMAProxyInterface() {}
+
+  virtual void Activate(const std::string &carrier, Error *error,
+                        const ActivationResultCallback &callback,
+                        int timeout) = 0;
+  virtual void GetRegistrationState(Error *error,
+                                    const RegistrationStateCallback &callback,
+                                    int timeout) = 0;
+  virtual void GetSignalQuality(Error *error,
+                                const SignalQualityCallback &callback,
+                                int timeout) = 0;
+
+  // Properties.
+  virtual const std::string MEID() = 0;
+
+  virtual void set_activation_state_callback(
+      const ActivationStateSignalCallback &callback) = 0;
+  virtual void set_signal_quality_callback(
+      const SignalQualitySignalCallback &callback) = 0;
+  virtual void set_registration_state_callback(
+      const RegistrationStateSignalCallback &callback) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_CDMA_PROXY_INTERFACE_H_
diff --git a/cellular/modem_classic.cc b/cellular/modem_classic.cc
new file mode 100644
index 0000000..81ffb88
--- /dev/null
+++ b/cellular/modem_classic.cc
@@ -0,0 +1,63 @@
+// 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/modem.h"
+
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/cellular.h"
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+ModemClassic::ModemClassic(const string &owner,
+                           const string &service,
+                           const string &path,
+                           ModemInfo *modem_info)
+    : Modem(owner, service, path, modem_info) {}
+
+ModemClassic::~ModemClassic() {}
+
+bool ModemClassic::GetLinkName(const DBusPropertiesMap& modem_properties,
+                               string *name) const {
+  return DBusProperties::GetString(modem_properties, kPropertyLinkName, name);
+}
+
+void ModemClassic::CreateDeviceClassic(
+    const DBusPropertiesMap &modem_properties) {
+  Init();
+  uint32_t mm_type = kuint32max;
+  DBusProperties::GetUint32(modem_properties, kPropertyType, &mm_type);
+  switch (mm_type) {
+    case MM_MODEM_TYPE_CDMA:
+      set_type(Cellular::kTypeCDMA);
+      break;
+    case MM_MODEM_TYPE_GSM:
+      set_type(Cellular::kTypeGSM);
+      break;
+    default:
+      LOG(ERROR) << "Unsupported cellular modem type: " << mm_type;
+      return;
+  }
+  uint32_t ip_method = kuint32max;
+  if (!DBusProperties::GetUint32(modem_properties,
+                                 kPropertyIPMethod,
+                                 &ip_method) ||
+      ip_method != MM_MODEM_IP_METHOD_DHCP) {
+    LOG(ERROR) << "Unsupported IP method: " << ip_method;
+    return;
+  }
+
+  DBusInterfaceToProperties properties;
+  properties[MM_MODEM_INTERFACE] = modem_properties;
+  CreateDeviceFromModemProperties(properties);
+}
+
+string ModemClassic::GetModemInterface(void) const {
+  return string(MM_MODEM_INTERFACE);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_gobi_proxy.cc b/cellular/modem_gobi_proxy.cc
new file mode 100644
index 0000000..eeba73c
--- /dev/null
+++ b/cellular/modem_gobi_proxy.cc
@@ -0,0 +1,55 @@
+// 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/modem_gobi_proxy.h"
+
+#include <memory>
+
+#include <base/bind.h>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+
+using base::Bind;
+using base::Callback;
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+ModemGobiProxy::ModemGobiProxy(DBus::Connection *connection,
+                               const string &path,
+                               const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemGobiProxy::~ModemGobiProxy() {}
+
+void ModemGobiProxy::SetCarrier(const string &carrier,
+                                Error *error,
+                                const ResultCallback &callback,
+                                int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::SetCarrierAsync, callback,
+                     error, &CellularError::FromDBusError, timeout,
+                     carrier);
+}
+
+ModemGobiProxy::Proxy::Proxy(DBus::Connection *connection,
+                             const string &path,
+                             const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemGobiProxy::Proxy::~Proxy() {}
+
+void ModemGobiProxy::Proxy::SetCarrierCallback(const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_gobi_proxy.h b/cellular/modem_gobi_proxy.h
new file mode 100644
index 0000000..41157ae
--- /dev/null
+++ b/cellular/modem_gobi_proxy.h
@@ -0,0 +1,59 @@
+// 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_MODEM_GOBI_PROXY_H_
+#define SHILL_CELLULAR_MODEM_GOBI_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "shill/cellular/modem_gobi_proxy_interface.h"
+#include "shill/dbus_proxies/modem-gobi.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.Gobi.
+class ModemGobiProxy : public ModemGobiProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem.Gobi DBus object proxy at |path| owned by
+  // |service|.
+  ModemGobiProxy(DBus::Connection *connection,
+                 const std::string &path,
+                 const std::string &service);
+  ~ModemGobiProxy() override;
+
+  // Inherited from ModemGobiProxyInterface.
+  virtual void SetCarrier(const std::string &carrier,
+                          Error *error, const ResultCallback &callback,
+                          int timeout);
+
+ private:
+  class Proxy
+      : public org::chromium::ModemManager::Modem::Gobi_proxy,
+        public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem::Gobi_proxy.
+    // [None]
+
+    // Method callbacks inherited from ModemManager::Modem::Gobi_proxy.
+    virtual void SetCarrierCallback(const DBus::Error &dberror, void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemGobiProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GOBI_PROXY_H_
diff --git a/cellular/modem_gobi_proxy_interface.h b/cellular/modem_gobi_proxy_interface.h
new file mode 100644
index 0000000..dfc8cd8
--- /dev/null
+++ b/cellular/modem_gobi_proxy_interface.h
@@ -0,0 +1,30 @@
+// 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_MODEM_GOBI_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_GOBI_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+
+class Error;
+
+// These are the methods that a ModemManager.Modem.Gobi proxy must support. The
+// interface is provided so that it can be mocked in tests.  All calls are made
+// asynchronously.
+class ModemGobiProxyInterface {
+ public:
+  virtual ~ModemGobiProxyInterface() {}
+
+  virtual void SetCarrier(const std::string &carrier,
+                          Error *error, const ResultCallback &callback,
+                          int timeout) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GOBI_PROXY_INTERFACE_H_
diff --git a/cellular/modem_gsm_card_proxy.cc b/cellular/modem_gsm_card_proxy.cc
new file mode 100644
index 0000000..a60fe9c
--- /dev/null
+++ b/cellular/modem_gsm_card_proxy.cc
@@ -0,0 +1,185 @@
+// 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/modem_gsm_card_proxy.h"
+
+#include <memory>
+
+#include <base/bind.h>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+
+using base::Bind;
+using base::Callback;
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+template<typename TraceMsgT, typename CallT, typename CallbackT,
+         typename... ArgTypes>
+void ModemGSMCardProxy::BeginCall(
+    const TraceMsgT &trace_msg, const CallT &call, const CallbackT &callback,
+    Error *error, int timeout, ArgTypes... rest) {
+  BeginAsyncDBusCall(trace_msg, proxy_, call, callback, error,
+                     &CellularError::FromDBusError, timeout, rest...);
+}
+
+ModemGSMCardProxy::ModemGSMCardProxy(DBus::Connection *connection,
+                                     const string &path,
+                                     const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemGSMCardProxy::~ModemGSMCardProxy() {}
+
+void ModemGSMCardProxy::GetIMEI(Error *error,
+                                const GSMIdentifierCallback &callback,
+                                int timeout) {
+  BeginCall(__func__, &Proxy::GetImeiAsync, callback, error, timeout);
+}
+
+void ModemGSMCardProxy::GetIMSI(Error *error,
+                                const GSMIdentifierCallback &callback,
+                                int timeout) {
+  BeginCall(__func__, &Proxy::GetImsiAsync, callback, error, timeout);
+}
+
+void ModemGSMCardProxy::GetSPN(Error *error,
+                               const GSMIdentifierCallback &callback,
+                               int timeout) {
+  BeginCall(__func__, &Proxy::GetSpnAsync, callback, error, timeout);
+}
+
+void ModemGSMCardProxy::GetMSISDN(Error *error,
+                                  const GSMIdentifierCallback &callback,
+                                  int timeout) {
+  BeginCall(__func__, &Proxy::GetMsIsdnAsync, callback, error, timeout);
+}
+
+void ModemGSMCardProxy::EnablePIN(const string &pin, bool enabled,
+                                  Error *error,
+                                  const ResultCallback &callback,
+                                  int timeout) {
+  BeginCall(__func__, &Proxy::EnablePinAsync, callback, error, timeout,
+            pin, enabled);
+}
+
+void ModemGSMCardProxy::SendPIN(const string &pin,
+                                Error *error,
+                                const ResultCallback &callback,
+                                int timeout) {
+  BeginCall(__func__, &Proxy::SendPinAsync, callback, error, timeout,
+            pin);
+}
+
+void ModemGSMCardProxy::SendPUK(const string &puk, const string &pin,
+                                Error *error,
+                                const ResultCallback &callback,
+                                int timeout) {
+  BeginCall(__func__, &Proxy::SendPukAsync, callback, error, timeout,
+            puk, pin);
+}
+
+void ModemGSMCardProxy::ChangePIN(const string &old_pin,
+                                  const string &new_pin,
+                                  Error *error,
+                                  const ResultCallback &callback,
+                                  int timeout) {
+  BeginCall(__func__, &Proxy::ChangePinAsync, callback, error, timeout,
+            old_pin, new_pin);
+}
+
+uint32_t ModemGSMCardProxy::EnabledFacilityLocks() {
+  SLOG(&proxy_.path(), 2) << __func__;
+  try {
+    return proxy_.EnabledFacilityLocks();
+  } catch (const DBus::Error &e) {
+    LOG(FATAL) << "DBus exception: " << e.name() << ": " << e.what();
+    return 0;  // Make the compiler happy.
+  }
+}
+
+ModemGSMCardProxy::Proxy::Proxy(DBus::Connection *connection,
+                                const string &path,
+                                const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+void ModemGSMCardProxy::Proxy::GetIdCallback(const string &id,
+                                             const DBus::Error &dberror,
+                                             void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<GSMIdentifierCallback> callback(
+      reinterpret_cast<GSMIdentifierCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(id, error);
+}
+
+void ModemGSMCardProxy::Proxy::GetImeiCallback(const string &imei,
+                                               const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  GetIdCallback(imei, dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::GetImsiCallback(const string &imsi,
+                                               const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  GetIdCallback(imsi, dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::GetSpnCallback(const string &spn,
+                                              const DBus::Error &dberror,
+                                              void *data) {
+  SLOG(&path(), 2) << __func__;
+  GetIdCallback(spn, dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::GetMsIsdnCallback(const string &msisdn,
+                                                 const DBus::Error &dberror,
+                                                 void *data) {
+  SLOG(&path(), 2) << __func__;
+  GetIdCallback(msisdn, dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::PinCallback(const DBus::Error &dberror,
+                                           void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemGSMCardProxy::Proxy::EnablePinCallback(const DBus::Error &dberror,
+                                                 void *data) {
+  SLOG(&path(), 2) << __func__;
+  PinCallback(dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::SendPinCallback(const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  PinCallback(dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::SendPukCallback(const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  PinCallback(dberror, data);
+}
+
+void ModemGSMCardProxy::Proxy::ChangePinCallback(const DBus::Error &dberror,
+                                                 void *data) {
+  SLOG(&path(), 2) << __func__;
+  PinCallback(dberror, data);
+}
+
+ModemGSMCardProxy::Proxy::~Proxy() {}
+
+}  // namespace shill
diff --git a/cellular/modem_gsm_card_proxy.h b/cellular/modem_gsm_card_proxy.h
new file mode 100644
index 0000000..c26561b
--- /dev/null
+++ b/cellular/modem_gsm_card_proxy.h
@@ -0,0 +1,100 @@
+// 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_MODEM_GSM_CARD_PROXY_H_
+#define SHILL_CELLULAR_MODEM_GSM_CARD_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.Modem.Gsm.Card.h"
+#include "shill/cellular/modem_gsm_card_proxy_interface.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.Gsm.Card.
+class ModemGSMCardProxy : public ModemGSMCardProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem.Gsm.Card DBus
+  // object proxy at |path| owned by |service|.
+  ModemGSMCardProxy(DBus::Connection *connection,
+                    const std::string &path,
+                    const std::string &service);
+  ~ModemGSMCardProxy() override;
+
+  // Inherited from ModemGSMCardProxyInterface.
+  virtual void GetIMEI(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout);
+  virtual void GetIMSI(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout);
+  virtual void GetSPN(Error *error, const GSMIdentifierCallback &callback,
+                      int timeout);
+  virtual void GetMSISDN(Error *error, const GSMIdentifierCallback &callback,
+                         int timeout);
+  virtual void EnablePIN(const std::string &pin, bool enabled,
+                         Error *error, const ResultCallback &callback,
+                         int timeout);
+  virtual void SendPIN(const std::string &pin,
+                       Error *error, const ResultCallback &callback,
+                       int timeout);
+  virtual void SendPUK(const std::string &puk, const std::string &pin,
+                       Error *error, const ResultCallback &callback,
+                       int timeout);
+  virtual void ChangePIN(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error, const ResultCallback &callback,
+                         int timeout);
+  virtual uint32_t EnabledFacilityLocks();
+
+ private:
+  class Proxy
+      : public org::freedesktop::ModemManager::Modem::Gsm::Card_proxy,
+        public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem::Gsm::Card_proxy.
+    // [None]
+
+    // Method callbacks inherited from ModemManager::Modem::Gsm::Card_proxy.
+    virtual void GetImeiCallback(const std::string &imei,
+                                 const DBus::Error &dberror, void *data);
+    virtual void GetImsiCallback(const std::string &imsi,
+                                 const DBus::Error &dberror, void *data);
+    virtual void GetSpnCallback(const std::string &spn,
+                                 const DBus::Error &dberror, void *data);
+    virtual void GetMsIsdnCallback(const std::string &msisdn,
+                                 const DBus::Error &dberror, void *data);
+    virtual void EnablePinCallback(const DBus::Error &dberror, void *data);
+    virtual void SendPinCallback(const DBus::Error &dberror, void *data);
+    virtual void SendPukCallback(const DBus::Error &dberror, void *data);
+    virtual void ChangePinCallback(const DBus::Error &dberror, void *data);
+
+    virtual void GetIdCallback(const std::string &id,
+                               const DBus::Error &dberror, void *data);
+    virtual void PinCallback(const DBus::Error &dberror, void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  template<typename TraceMsgT, typename CallT, typename CallbackT,
+      typename... ArgTypes>
+      void BeginCall(
+          const TraceMsgT &trace_msg, const CallT &call,
+          const CallbackT &callback, Error *error, int timeout,
+          ArgTypes... rest);
+
+  DISALLOW_COPY_AND_ASSIGN(ModemGSMCardProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GSM_CARD_PROXY_H_
diff --git a/cellular/modem_gsm_card_proxy_interface.h b/cellular/modem_gsm_card_proxy_interface.h
new file mode 100644
index 0000000..df0b2e0
--- /dev/null
+++ b/cellular/modem_gsm_card_proxy_interface.h
@@ -0,0 +1,53 @@
+// 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_MODEM_GSM_CARD_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_GSM_CARD_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+
+class Error;
+typedef base::Callback<void(const std::string &,
+                            const Error &)> GSMIdentifierCallback;
+
+// These are the methods that a ModemManager.Modem.Gsm.Card proxy must
+// support. The interface is provided so that it can be mocked in tests.
+// All calls are made asynchronously.
+class ModemGSMCardProxyInterface {
+ public:
+  virtual ~ModemGSMCardProxyInterface() {}
+
+  virtual void GetIMEI(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout) = 0;
+  virtual void GetIMSI(Error *error, const GSMIdentifierCallback &callback,
+                       int timeout) = 0;
+  virtual void GetSPN(Error *error, const GSMIdentifierCallback &callback,
+                      int timeout) = 0;
+  virtual void GetMSISDN(Error *error, const GSMIdentifierCallback &callback,
+                         int timeout) = 0;
+  virtual void EnablePIN(const std::string &pin, bool enabled,
+                         Error *error, const ResultCallback &callback,
+                         int timeout) = 0;
+  virtual void SendPIN(const std::string &pin,
+                       Error *error, const ResultCallback &callback,
+                       int timeout) = 0;
+  virtual void SendPUK(const std::string &puk, const std::string &pin,
+                       Error *error, const ResultCallback &callback,
+                       int timeout) = 0;
+  virtual void ChangePIN(const std::string &old_pin,
+                         const std::string &new_pin,
+                         Error *error, const ResultCallback &callback,
+                         int timeout) = 0;
+
+  // Properties.
+  virtual uint32_t EnabledFacilityLocks() = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GSM_CARD_PROXY_INTERFACE_H_
diff --git a/cellular/modem_gsm_network_proxy.cc b/cellular/modem_gsm_network_proxy.cc
new file mode 100644
index 0000000..4edfe00
--- /dev/null
+++ b/cellular/modem_gsm_network_proxy.cc
@@ -0,0 +1,169 @@
+// 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/modem_gsm_network_proxy.h"
+
+#include <memory>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+
+using base::Callback;
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+ModemGSMNetworkProxy::ModemGSMNetworkProxy(
+    DBus::Connection *connection,
+    const string &path,
+    const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemGSMNetworkProxy::~ModemGSMNetworkProxy() {}
+
+void ModemGSMNetworkProxy::GetRegistrationInfo(
+    Error *error,
+    const RegistrationInfoCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetRegistrationInfoAsync,
+                     callback, error, &CellularError::FromDBusError, timeout);
+}
+
+void ModemGSMNetworkProxy::GetSignalQuality(
+    Error *error,
+    const SignalQualityCallback &callback,
+    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetSignalQualityAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+void ModemGSMNetworkProxy::Register(const string &network_id,
+                                    Error *error,
+                                    const ResultCallback &callback,
+                                    int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::RegisterAsync, callback,
+                     error, &CellularError::FromDBusError, timeout,
+                     network_id);
+}
+
+void ModemGSMNetworkProxy::Scan(Error *error,
+                                const ScanResultsCallback &callback,
+                                int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ScanAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+uint32_t ModemGSMNetworkProxy::AccessTechnology() {
+  SLOG(&proxy_.path(), 2) << __func__;
+  try {
+  return proxy_.AccessTechnology();
+  } catch (const DBus::Error &e) {
+    LOG(FATAL) << "DBus exception: " << e.name() << ": " << e.what();
+    return 0;  // Make the compiler happy.
+  }
+}
+
+void ModemGSMNetworkProxy::set_signal_quality_callback(
+    const SignalQualitySignalCallback &callback) {
+  proxy_.set_signal_quality_callback(callback);
+}
+
+void ModemGSMNetworkProxy::set_network_mode_callback(
+    const NetworkModeSignalCallback &callback) {
+  proxy_.set_network_mode_callback(callback);
+}
+
+void ModemGSMNetworkProxy::set_registration_info_callback(
+    const RegistrationInfoSignalCallback &callback) {
+  proxy_.set_registration_info_callback(callback);
+}
+
+ModemGSMNetworkProxy::Proxy::Proxy(DBus::Connection *connection,
+                                   const string &path,
+                                   const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemGSMNetworkProxy::Proxy::~Proxy() {}
+
+void ModemGSMNetworkProxy::Proxy::set_signal_quality_callback(
+    const SignalQualitySignalCallback &callback) {
+  signal_quality_callback_ = callback;
+}
+
+void ModemGSMNetworkProxy::Proxy::set_network_mode_callback(
+    const NetworkModeSignalCallback &callback) {
+  network_mode_callback_ = callback;
+}
+
+void ModemGSMNetworkProxy::Proxy::set_registration_info_callback(
+    const RegistrationInfoSignalCallback &callback) {
+  registration_info_callback_ = callback;
+}
+
+void ModemGSMNetworkProxy::Proxy::SignalQuality(const uint32_t &quality) {
+  SLOG(&path(), 2) << __func__ << "(" << quality << ")";
+  if (!signal_quality_callback_.is_null())
+    signal_quality_callback_.Run(quality);
+}
+
+void ModemGSMNetworkProxy::Proxy::RegistrationInfo(
+    const uint32_t &status,
+    const string &operator_code,
+    const string &operator_name) {
+  SLOG(&path(), 2) << __func__ << "(" << status << ", " << operator_code << ", "
+                 << operator_name << ")";
+  if (!registration_info_callback_.is_null())
+    registration_info_callback_.Run(status, operator_code, operator_name);
+}
+
+void ModemGSMNetworkProxy::Proxy::NetworkMode(const uint32_t &mode) {
+  SLOG(&path(), 2) << __func__ << "(" << mode << ")";
+  if (!network_mode_callback_.is_null())
+    network_mode_callback_.Run(mode);
+}
+
+void ModemGSMNetworkProxy::Proxy::RegisterCallback(const DBus::Error &dberror,
+                                                   void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemGSMNetworkProxy::Proxy::GetRegistrationInfoCallback(
+    const GSMRegistrationInfo &info, const DBus::Error &dberror, void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<RegistrationInfoCallback> callback(
+      reinterpret_cast<RegistrationInfoCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(info._1, info._2, info._3, error);
+}
+
+void ModemGSMNetworkProxy::Proxy::GetSignalQualityCallback(
+    const uint32_t &quality, const DBus::Error &dberror, void *data) {
+  SLOG(&path(), 2) << __func__ << "(" << quality << ")";
+  unique_ptr<SignalQualityCallback> callback(
+      reinterpret_cast<SignalQualityCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(quality, error);
+}
+
+void ModemGSMNetworkProxy::Proxy::ScanCallback(const GSMScanResults &results,
+                                               const DBus::Error &dberror,
+                                               void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ScanResultsCallback> callback(
+      reinterpret_cast<ScanResultsCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(results, error);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_gsm_network_proxy.h b/cellular/modem_gsm_network_proxy.h
new file mode 100644
index 0000000..15fc385
--- /dev/null
+++ b/cellular/modem_gsm_network_proxy.h
@@ -0,0 +1,96 @@
+// 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_MODEM_GSM_NETWORK_PROXY_H_
+#define SHILL_CELLULAR_MODEM_GSM_NETWORK_PROXY_H_
+
+#include <string>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.Modem.Gsm.Network.h"
+#include "shill/cellular/modem_gsm_network_proxy_interface.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.Gsm.Network.
+class ModemGSMNetworkProxy : public ModemGSMNetworkProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem.Gsm.Network DBus object proxy at |path|
+  // owned by |service|.
+  ModemGSMNetworkProxy(DBus::Connection *connection,
+                       const std::string &path,
+                       const std::string &service);
+  ~ModemGSMNetworkProxy() override;
+
+  // Inherited from ModemGSMNetworkProxyInterface.
+  virtual void GetRegistrationInfo(Error *error,
+                                   const RegistrationInfoCallback &callback,
+                                   int timeout);
+  virtual void GetSignalQuality(Error *error,
+                                const SignalQualityCallback &callback,
+                                int timeout);
+  virtual void Register(const std::string &network_id,
+                        Error *error, const ResultCallback &callback,
+                        int timeout);
+  virtual void Scan(Error *error, const ScanResultsCallback &callback,
+                    int timeout);
+  virtual uint32_t AccessTechnology();
+
+  virtual void set_signal_quality_callback(
+      const SignalQualitySignalCallback &callback);
+  virtual void set_network_mode_callback(
+      const NetworkModeSignalCallback &callback);
+  virtual void set_registration_info_callback(
+      const RegistrationInfoSignalCallback &callback);
+
+ private:
+  class Proxy
+      : public org::freedesktop::ModemManager::Modem::Gsm::Network_proxy,
+        public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    virtual void set_signal_quality_callback(
+        const SignalQualitySignalCallback &callback);
+    virtual void set_network_mode_callback(
+        const NetworkModeSignalCallback &callback);
+    virtual void set_registration_info_callback(
+        const RegistrationInfoSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem::Gsm::Network_proxy.
+    virtual void SignalQuality(const uint32_t &quality);
+    virtual void RegistrationInfo(const uint32_t &status,
+                                  const std::string &operator_code,
+                                  const std::string &operator_name);
+    virtual void NetworkMode(const uint32_t &mode);
+
+    // Method callbacks inherited from ModemManager::Modem::Gsm::Network_proxy.
+    virtual void RegisterCallback(const DBus::Error &dberror, void *data);
+    virtual void GetRegistrationInfoCallback(const GSMRegistrationInfo &info,
+                                             const DBus::Error &dberror,
+                                             void *data);
+    virtual void GetSignalQualityCallback(const uint32_t &quality,
+                                          const DBus::Error &dberror,
+                                          void *data);
+    virtual void ScanCallback(const GSMScanResults &results,
+                              const DBus::Error &dberror, void *data);
+
+    SignalQualitySignalCallback signal_quality_callback_;
+    RegistrationInfoSignalCallback registration_info_callback_;
+    NetworkModeSignalCallback network_mode_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemGSMNetworkProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GSM_NETWORK_PROXY_H_
diff --git a/cellular/modem_gsm_network_proxy_interface.h b/cellular/modem_gsm_network_proxy_interface.h
new file mode 100644
index 0000000..740e6d4
--- /dev/null
+++ b/cellular/modem_gsm_network_proxy_interface.h
@@ -0,0 +1,73 @@
+// 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_MODEM_GSM_NETWORK_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_GSM_NETWORK_PROXY_INTERFACE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <dbus-c++/types.h>
+
+#include "shill/callbacks.h"
+
+namespace shill {
+
+class Error;
+
+typedef DBus::Struct<unsigned int, std::string, std::string>
+    GSMRegistrationInfo;
+typedef std::map<std::string, std::string> GSMScanResult;
+typedef std::vector<GSMScanResult> GSMScanResults;
+
+typedef base::Callback<void(uint32_t)> SignalQualitySignalCallback;
+typedef base::Callback<void(
+    uint32_t,
+    const std::string &,
+    const std::string &)> RegistrationInfoSignalCallback;
+typedef base::Callback<void(uint32_t)> NetworkModeSignalCallback;
+
+typedef base::Callback<void(uint32_t, const Error &)> SignalQualityCallback;
+typedef base::Callback<void(uint32_t,
+                            const std::string &,
+                            const std::string &,
+                            const Error &)> RegistrationInfoCallback;
+typedef base::Callback<void(const GSMScanResults &,
+                            const Error &)> ScanResultsCallback;
+
+// These are the methods that a ModemManager.Modem.Gsm.Network proxy must
+// support. The interface is provided so that it can be mocked in tests.
+// All calls are made asynchronously.
+// XXX fixup comment to reflect new reality
+class ModemGSMNetworkProxyInterface {
+ public:
+  virtual ~ModemGSMNetworkProxyInterface() {}
+
+  virtual void GetRegistrationInfo(Error *error,
+                                   const RegistrationInfoCallback &callback,
+                                   int timeout) = 0;
+  virtual void GetSignalQuality(Error *error,
+                                const SignalQualityCallback &callback,
+                                int timeout) = 0;
+  virtual void Register(const std::string &network_id,
+                        Error *error, const ResultCallback &callback,
+                        int timeout) = 0;
+  virtual void Scan(Error *error, const ScanResultsCallback &callback,
+                    int timeout) = 0;
+
+  // Properties.
+  virtual uint32_t AccessTechnology() = 0;
+  // Signal callbacks
+  virtual void set_signal_quality_callback(
+      const SignalQualitySignalCallback &callback) = 0;
+  virtual void set_network_mode_callback(
+      const NetworkModeSignalCallback &callback) = 0;
+  virtual void set_registration_info_callback(
+      const RegistrationInfoSignalCallback &callback) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_GSM_NETWORK_PROXY_INTERFACE_H_
diff --git a/cellular/modem_info.cc b/cellular/modem_info.cc
new file mode 100644
index 0000000..04d1455
--- /dev/null
+++ b/cellular/modem_info.cc
@@ -0,0 +1,70 @@
+// 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/modem_info.h"
+
+#include <base/files/file_path.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "shill/cellular/modem_manager.h"
+#include "shill/logging.h"
+#include "shill/manager.h"
+#include "shill/pending_activation_store.h"
+
+using base::FilePath;
+using std::string;
+
+namespace shill {
+
+ModemInfo::ModemInfo(ControlInterface *control_interface,
+                     EventDispatcher *dispatcher,
+                     Metrics *metrics,
+                     Manager *manager,
+                     GLib *glib)
+    : control_interface_(control_interface),
+      dispatcher_(dispatcher),
+      metrics_(metrics),
+      manager_(manager),
+      glib_(glib) {}
+
+ModemInfo::~ModemInfo() {
+  Stop();
+}
+
+void ModemInfo::Start() {
+  pending_activation_store_.reset(new PendingActivationStore());
+  pending_activation_store_->InitStorage(manager_->glib(),
+      manager_->storage_path());
+
+  RegisterModemManager(new ModemManagerClassic(cromo::kCromoServiceName,
+                                               cromo::kCromoServicePath,
+                                               this));
+  RegisterModemManager(
+      new ModemManager1(modemmanager::kModemManager1ServiceName,
+                        modemmanager::kModemManager1ServicePath,
+                        this));
+}
+
+void ModemInfo::Stop() {
+  pending_activation_store_.reset();
+  modem_managers_.clear();
+}
+
+void ModemInfo::OnDeviceInfoAvailable(const string &link_name) {
+  for (const auto &manager : modem_managers_) {
+    manager->OnDeviceInfoAvailable(link_name);
+  }
+}
+
+void ModemInfo::set_pending_activation_store(
+    PendingActivationStore *pending_activation_store) {
+  pending_activation_store_.reset(pending_activation_store);
+}
+
+void ModemInfo::RegisterModemManager(ModemManager *manager) {
+  modem_managers_.push_back(manager);  // Passes ownership.
+  manager->Start();
+}
+
+}  // namespace shill
diff --git a/cellular/modem_info.h b/cellular/modem_info.h
new file mode 100644
index 0000000..7b886ab
--- /dev/null
+++ b/cellular/modem_info.h
@@ -0,0 +1,93 @@
+// 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_MODEM_INFO_H_
+#define SHILL_CELLULAR_MODEM_INFO_H_
+
+#include <memory>
+#include <string>
+
+#include <base/memory/scoped_vector.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace shill {
+
+class ControlInterface;
+class EventDispatcher;
+class GLib;
+class Manager;
+class Metrics;
+class ModemManager;
+class PendingActivationStore;
+
+// Manages modem managers.
+class ModemInfo {
+ public:
+  ModemInfo(ControlInterface *control,
+            EventDispatcher *dispatcher,
+            Metrics *metrics,
+            Manager *manager,
+            GLib *glib);
+  virtual ~ModemInfo();
+
+  virtual void Start();
+  virtual void Stop();
+
+  virtual void OnDeviceInfoAvailable(const std::string &link_name);
+
+  ControlInterface *control_interface() const { return control_interface_; }
+  EventDispatcher *dispatcher() const { return dispatcher_; }
+  Metrics *metrics() const { return metrics_; }
+  Manager *manager() const { return manager_; }
+  GLib *glib() const { return glib_; }
+  PendingActivationStore *pending_activation_store() const {
+    return pending_activation_store_.get();
+  }
+
+ protected:
+  // Write accessors for unit-tests.
+  void set_control_interface(ControlInterface *control) {
+    control_interface_ = control;
+  }
+  void set_event_dispatcher(EventDispatcher *dispatcher) {
+    dispatcher_ = dispatcher;
+  }
+  void set_metrics(Metrics *metrics) {
+    metrics_ = metrics;
+  }
+  void set_manager(Manager *manager) {
+    manager_ = manager;
+  }
+  void set_glib(GLib *glib) {
+    glib_ = glib;
+  }
+  void set_pending_activation_store(
+      PendingActivationStore *pending_activation_store);
+
+ private:
+  friend class ModemInfoTest;
+  FRIEND_TEST(ModemInfoTest, RegisterModemManager);
+  FRIEND_TEST(ModemInfoTest, StartStop);
+
+  typedef ScopedVector<ModemManager> ModemManagers;
+
+  // Registers and starts |manager|. Takes ownership of |manager|.
+  void RegisterModemManager(ModemManager *manager);
+  ModemManagers modem_managers_;
+
+  ControlInterface *control_interface_;
+  EventDispatcher *dispatcher_;
+  Metrics *metrics_;
+  Manager *manager_;
+  GLib *glib_;
+
+  // Post-payment activation state of the modem.
+  std::unique_ptr<PendingActivationStore> pending_activation_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemInfo);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_INFO_H_
diff --git a/cellular/modem_info_unittest.cc b/cellular/modem_info_unittest.cc
new file mode 100644
index 0000000..8224919
--- /dev/null
+++ b/cellular/modem_info_unittest.cc
@@ -0,0 +1,74 @@
+// 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/modem_info.h"
+
+#include <base/stl_util.h>
+#include <gtest/gtest.h>
+
+#include "shill/cellular/modem_manager.h"
+#include "shill/manager.h"
+#include "shill/mock_control.h"
+#include "shill/mock_dbus_service_proxy.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+
+using testing::_;
+using testing::Return;
+using testing::Test;
+
+namespace shill {
+
+class ModemInfoTest : public Test {
+ public:
+  ModemInfoTest()
+      : metrics_(&dispatcher_),
+        manager_(&control_interface_, &dispatcher_, &metrics_, &glib_),
+        dbus_service_proxy_(nullptr),
+        modem_info_(&control_interface_, &dispatcher_, &metrics_, &manager_,
+                    &glib_) {}
+
+  virtual void SetUp() {
+    manager_.dbus_manager_.reset(new DBusManager());
+    dbus_service_proxy_ = new MockDBusServiceProxy();
+    // Ownership  of |dbus_service_proxy_| is transferred to
+    // |manager_.dbus_manager_|.
+    manager_.dbus_manager_->proxy_.reset(dbus_service_proxy_);
+  }
+
+ protected:
+  MockGLib glib_;
+  MockControl control_interface_;
+  EventDispatcher dispatcher_;
+  MockMetrics metrics_;
+  MockManager manager_;
+  MockDBusServiceProxy *dbus_service_proxy_;
+  ModemInfo modem_info_;
+};
+
+TEST_F(ModemInfoTest, StartStop) {
+  EXPECT_EQ(0, modem_info_.modem_managers_.size());
+  EXPECT_CALL(*dbus_service_proxy_,
+              GetNameOwner("org.chromium.ModemManager", _, _, _));
+  EXPECT_CALL(*dbus_service_proxy_,
+              GetNameOwner("org.freedesktop.ModemManager1", _, _, _));
+  modem_info_.Start();
+  EXPECT_EQ(2, modem_info_.modem_managers_.size());
+  modem_info_.Stop();
+  EXPECT_EQ(0, modem_info_.modem_managers_.size());
+}
+
+TEST_F(ModemInfoTest, RegisterModemManager) {
+  static const char kService[] = "some.dbus.service";
+  EXPECT_CALL(*dbus_service_proxy_, GetNameOwner(kService, _, _, _));
+  modem_info_.RegisterModemManager(
+      new ModemManagerClassic(kService, "/dbus/service/path", &modem_info_));
+  ASSERT_EQ(1, modem_info_.modem_managers_.size());
+  ModemManager *manager = modem_info_.modem_managers_[0];
+  EXPECT_EQ(kService, manager->service_);
+  EXPECT_EQ(&modem_info_, manager->modem_info_);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_manager.cc b/cellular/modem_manager.cc
new file mode 100644
index 0000000..1d5ca30
--- /dev/null
+++ b/cellular/modem_manager.cc
@@ -0,0 +1,161 @@
+// 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/modem_manager.h"
+
+#include <base/stl_util.h>
+#include <mm/mm-modem.h>
+
+#include "shill/cellular/modem.h"
+#include "shill/cellular/modem_manager_proxy.h"
+#include "shill/dbus_manager.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/manager.h"
+#include "shill/proxy_factory.h"
+
+using std::string;
+using std::shared_ptr;
+using std::vector;
+
+namespace shill {
+
+ModemManager::ModemManager(const string &service,
+                           const string &path,
+                           ModemInfo *modem_info)
+    : proxy_factory_(ProxyFactory::GetInstance()),
+      service_(service),
+      path_(path),
+      modem_info_(modem_info) {}
+
+ModemManager::~ModemManager() {
+  Stop();
+}
+
+void ModemManager::Start() {
+  LOG(INFO) << "Start watching modem manager service: " << service_;
+  CHECK(!name_watcher_);
+  name_watcher_.reset(modem_info_->manager()->dbus_manager()->CreateNameWatcher(
+      service_,
+      base::Bind(&ModemManager::OnAppear, base::Unretained(this)),
+      base::Bind(&ModemManager::OnVanish, base::Unretained(this))));
+}
+
+void ModemManager::Stop() {
+  LOG(INFO) << "Stop watching modem manager service: " << service_;
+  name_watcher_.reset();
+  Disconnect();
+}
+
+void ModemManager::Connect(const string &owner) {
+  // Inheriting classes call this superclass method.
+  owner_ = owner;
+}
+
+void ModemManager::Disconnect() {
+  // Inheriting classes call this superclass method.
+  modems_.clear();
+  owner_.clear();
+}
+
+void ModemManager::OnAppear(const string &name, const string &owner) {
+  LOG(INFO) << "Modem manager " << name << " appeared. Owner: " << owner;
+  Connect(owner);
+}
+
+void ModemManager::OnVanish(const string &name) {
+  LOG(INFO) << "Modem manager " << name << " vanished.";
+  Disconnect();
+}
+
+bool ModemManager::ModemExists(const std::string &path) const {
+  CHECK(!owner_.empty());
+  if (ContainsKey(modems_, path)) {
+    LOG(INFO) << "ModemExists: " << path << " already exists.";
+    return true;
+  } else {
+    return false;
+  }
+}
+
+void ModemManager::RecordAddedModem(shared_ptr<Modem> modem) {
+  modems_[modem->path()] = modem;
+}
+
+void ModemManager::RemoveModem(const string &path) {
+  LOG(INFO) << "Remove modem: " << path;
+  CHECK(!owner_.empty());
+  modems_.erase(path);
+}
+
+void ModemManager::OnDeviceInfoAvailable(const string &link_name) {
+  for (Modems::const_iterator it = modems_.begin(); it != modems_.end(); ++it) {
+    it->second->OnDeviceInfoAvailable(link_name);
+  }
+}
+
+// ModemManagerClassic
+ModemManagerClassic::ModemManagerClassic(
+    const string &service,
+    const string &path,
+    ModemInfo *modem_info)
+    : ModemManager(service,
+                   path,
+                   modem_info) {}
+
+ModemManagerClassic::~ModemManagerClassic() {}
+
+void ModemManagerClassic::Connect(const string &supplied_owner) {
+  ModemManager::Connect(supplied_owner);
+  proxy_.reset(proxy_factory()->CreateModemManagerProxy(this, path(), owner()));
+  // TODO(petkov): Switch to asynchronous calls (crbug.com/200687).
+  vector<DBus::Path> devices = proxy_->EnumerateDevices();
+
+  for (vector<DBus::Path>::const_iterator it = devices.begin();
+       it != devices.end(); ++it) {
+    AddModemClassic(*it);
+  }
+}
+
+void ModemManagerClassic::AddModemClassic(const string &path) {
+  if (ModemExists(path)) {
+    return;
+  }
+  shared_ptr<ModemClassic> modem(new ModemClassic(owner(),
+                                                  service(),
+                                                  path,
+                                                  modem_info()));
+  RecordAddedModem(modem);
+  InitModemClassic(modem);
+}
+
+void ModemManagerClassic::Disconnect() {
+  ModemManager::Disconnect();
+  proxy_.reset();
+}
+
+void ModemManagerClassic::InitModemClassic(shared_ptr<ModemClassic> modem) {
+  // TODO(rochberg): Switch to asynchronous calls (crbug.com/200687).
+  if (modem == nullptr) {
+    return;
+  }
+
+  std::unique_ptr<DBusPropertiesProxyInterface> properties_proxy(
+      proxy_factory()->CreateDBusPropertiesProxy(modem->path(),
+                                                 modem->owner()));
+  DBusPropertiesMap properties =
+      properties_proxy->GetAll(MM_MODEM_INTERFACE);
+
+  modem->CreateDeviceClassic(properties);
+}
+
+void ModemManagerClassic::OnDeviceAdded(const string &path) {
+  AddModemClassic(path);
+}
+
+void ModemManagerClassic::OnDeviceRemoved(const string &path) {
+  RemoveModem(path);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_manager.h b/cellular/modem_manager.h
new file mode 100644
index 0000000..cd43df5
--- /dev/null
+++ b/cellular/modem_manager.h
@@ -0,0 +1,179 @@
+// 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_MODEM_MANAGER_H_
+#define SHILL_CELLULAR_MODEM_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "shill/cellular/dbus_objectmanager_proxy_interface.h"
+#include "shill/cellular/modem_info.h"
+#include "shill/dbus_properties_proxy_interface.h"
+#include "shill/glib.h"
+
+namespace shill {
+
+class DBusNameWatcher;
+class DBusObjectManagerProxyInterface;
+class DBusPropertiesProxyInterface;
+class Modem1;
+class Modem;
+class ModemClassic;
+class ModemManagerProxyInterface;
+class ProxyFactory;
+
+// Handles a modem manager service and creates and destroys modem instances.
+class ModemManager {
+ public:
+  ModemManager(const std::string &service,
+               const std::string &path,
+               ModemInfo *modem_info);
+  virtual ~ModemManager();
+
+  // Starts watching for and handling the DBus modem manager service.
+  void Start();
+
+  // Stops watching for the DBus modem manager service and destroys any
+  // associated modems.
+  void Stop();
+
+  // DBusNameWatcher callbacks.
+  void OnAppear(const std::string &name, const std::string &owner);
+  void OnVanish(const std::string &name);
+
+  void OnDeviceInfoAvailable(const std::string &link_name);
+
+ protected:
+  typedef std::map<std::string, std::shared_ptr<Modem>> Modems;
+
+  const std::string &owner() const { return owner_; }
+  const std::string &service() const { return service_; }
+  const std::string &path() const { return path_; }
+  ProxyFactory *proxy_factory() const { return proxy_factory_; }
+  ModemInfo *modem_info() const { return modem_info_; }
+
+  // Connect/Disconnect to a modem manager service.
+  // Inheriting classes must call this superclass method.
+  virtual void Connect(const std::string &owner);
+  // Inheriting classes must call this superclass method.
+  virtual void Disconnect();
+
+  bool ModemExists(const std::string &path) const;
+  // Put the modem into our modem map
+  void RecordAddedModem(std::shared_ptr<Modem> modem);
+
+  // Removes a modem on |path|.
+  void RemoveModem(const std::string &path);
+
+ private:
+  friend class ModemManagerCoreTest;
+  friend class ModemManagerClassicTest;
+  friend class ModemManager1Test;
+
+  FRIEND_TEST(ModemInfoTest, RegisterModemManager);
+  FRIEND_TEST(ModemManager1Test, AddRemoveInterfaces);
+  FRIEND_TEST(ModemManager1Test, Connect);
+  FRIEND_TEST(ModemManagerClassicTest, Connect);
+  FRIEND_TEST(ModemManagerCoreTest, AddRemoveModem);
+  FRIEND_TEST(ModemManagerCoreTest, ConnectDisconnect);
+  FRIEND_TEST(ModemManagerCoreTest, OnAppearVanish);
+  FRIEND_TEST(ModemManagerCoreTest, StartStopWithModemManagerServiceAbsent);
+  FRIEND_TEST(ModemManagerCoreTest, StartStopWithModemManagerServicePresent);
+
+  // Store cached copies of singletons for speed/ease of testing.
+  ProxyFactory *proxy_factory_;
+
+  const std::string service_;
+  const std::string path_;
+  std::unique_ptr<DBusNameWatcher> name_watcher_;
+
+  std::string owner_;  // DBus service owner.
+
+  Modems modems_;  // Maps a modem |path| to a modem instance.
+
+  ModemInfo *modem_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemManager);
+};
+
+class ModemManagerClassic : public ModemManager {
+ public:
+  ModemManagerClassic(const std::string &service,
+                      const std::string &path,
+                      ModemInfo *modem_info);
+
+  ~ModemManagerClassic() override;
+
+  // Called by our dbus proxy
+  void OnDeviceAdded(const std::string &path);
+  void OnDeviceRemoved(const std::string &path);
+
+ protected:
+  virtual void Connect(const std::string &owner);
+  virtual void Disconnect();
+
+  virtual void AddModemClassic(const std::string &path);
+  virtual void InitModemClassic(std::shared_ptr<ModemClassic> modem);
+
+ private:
+  std::unique_ptr<ModemManagerProxyInterface> proxy_;  // DBus service proxy
+  std::unique_ptr<DBusPropertiesProxyInterface> dbus_properties_proxy_;
+
+  FRIEND_TEST(ModemManagerClassicTest, Connect);
+
+  DISALLOW_COPY_AND_ASSIGN(ModemManagerClassic);
+};
+
+class ModemManager1 : public ModemManager {
+ public:
+  ModemManager1(const std::string &service,
+                const std::string &path,
+                ModemInfo *modem_info);
+
+  ~ModemManager1() override;
+
+ protected:
+  void AddModem1(const std::string &path,
+                 const DBusInterfaceToProperties &properties);
+  virtual void InitModem1(std::shared_ptr<Modem1> modem,
+                          const DBusInterfaceToProperties &properties);
+
+  // ModemManager methods
+  virtual void Connect(const std::string &owner);
+  virtual void Disconnect();
+
+  // DBusObjectManagerProxyDelegate signal methods
+  virtual void OnInterfacesAddedSignal(
+      const ::DBus::Path &object_path,
+      const DBusInterfaceToProperties &properties);
+  virtual void OnInterfacesRemovedSignal(
+      const ::DBus::Path &object_path,
+      const std::vector<std::string> &interfaces);
+
+  // DBusObjectManagerProxyDelegate method callbacks
+  virtual void OnGetManagedObjectsReply(
+      const DBusObjectsWithProperties &objects_with_properties,
+      const Error &error);
+
+ private:
+  friend class ModemManager1Test;
+  FRIEND_TEST(ModemManager1Test, Connect);
+  FRIEND_TEST(ModemManager1Test, AddRemoveInterfaces);
+
+  std::unique_ptr<DBusObjectManagerProxyInterface> proxy_;
+  base::WeakPtrFactory<ModemManager1> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemManager1);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_MANAGER_H_
diff --git a/cellular/modem_manager_1.cc b/cellular/modem_manager_1.cc
new file mode 100644
index 0000000..9caab76
--- /dev/null
+++ b/cellular/modem_manager_1.cc
@@ -0,0 +1,117 @@
+// 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/modem_manager.h"
+
+#include <base/bind.h>
+#include <base/stl_util.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/modem.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+#include "shill/proxy_factory.h"
+
+using base::Bind;
+using std::string;
+using std::shared_ptr;
+using std::vector;
+
+namespace shill {
+
+ModemManager1::ModemManager1(const string &service,
+                             const string &path,
+                             ModemInfo *modem_info)
+    : ModemManager(service,
+                   path,
+                   modem_info),
+      weak_ptr_factory_(this) {}
+
+ModemManager1::~ModemManager1() {}
+
+void ModemManager1::Connect(const string &supplied_owner) {
+  ModemManager::Connect(supplied_owner);
+  proxy_.reset(
+      proxy_factory()->CreateDBusObjectManagerProxy(path(), owner()));
+  proxy_->set_interfaces_added_callback(
+      Bind(&ModemManager1::OnInterfacesAddedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+  proxy_->set_interfaces_removed_callback(
+      Bind(&ModemManager1::OnInterfacesRemovedSignal,
+           weak_ptr_factory_.GetWeakPtr()));
+
+  // TODO(rochberg):  Make global kDBusDefaultTimeout and use it here
+  Error error;
+  proxy_->GetManagedObjects(&error,
+                            Bind(&ModemManager1::OnGetManagedObjectsReply,
+                                 weak_ptr_factory_.GetWeakPtr()),
+                            5000);
+}
+
+void ModemManager1::Disconnect() {
+  ModemManager::Disconnect();
+  proxy_.reset();
+}
+
+void ModemManager1::AddModem1(const string &path,
+                              const DBusInterfaceToProperties &properties) {
+  if (ModemExists(path)) {
+    return;
+  }
+  shared_ptr<Modem1> modem1(new Modem1(owner(),
+                                       service(),
+                                       path,
+                                       modem_info()));
+  RecordAddedModem(modem1);
+  InitModem1(modem1, properties);
+}
+
+void ModemManager1::InitModem1(shared_ptr<Modem1> modem,
+                               const DBusInterfaceToProperties &properties) {
+  if (modem == nullptr) {
+    return;
+  }
+  modem->CreateDeviceMM1(properties);
+}
+
+// signal methods
+// Also called by OnGetManagedObjectsReply
+void ModemManager1::OnInterfacesAddedSignal(
+    const ::DBus::Path &object_path,
+    const DBusInterfaceToProperties &properties) {
+  if (ContainsKey(properties, MM_DBUS_INTERFACE_MODEM)) {
+    AddModem1(object_path, properties);
+  } else {
+    LOG(ERROR) << "Interfaces added, but not modem interface.";
+  }
+}
+
+void ModemManager1::OnInterfacesRemovedSignal(
+    const ::DBus::Path &object_path,
+    const vector<string> &interfaces) {
+  LOG(INFO) << "MM1:  Removing interfaces from " << object_path;
+  if (find(interfaces.begin(),
+           interfaces.end(),
+           MM_DBUS_INTERFACE_MODEM) != interfaces.end()) {
+    RemoveModem(object_path);
+  } else {
+    // In theory, a modem could drop, say, 3GPP, but not CDMA.  In
+    // practice, we don't expect this
+    LOG(ERROR) << "Interfaces removed, but not modem interface";
+  }
+}
+
+// DBusObjectManagerProxy async method call
+void ModemManager1::OnGetManagedObjectsReply(
+    const DBusObjectsWithProperties &objects,
+    const Error &error) {
+  if (error.IsSuccess()) {
+    DBusObjectsWithProperties::const_iterator m;
+    for (m = objects.begin(); m != objects.end(); ++m) {
+      OnInterfacesAddedSignal(m->first, m->second);
+    }
+  }
+}
+
+}  // namespace shill
diff --git a/cellular/modem_manager_proxy.cc b/cellular/modem_manager_proxy.cc
new file mode 100644
index 0000000..cc017a6
--- /dev/null
+++ b/cellular/modem_manager_proxy.cc
@@ -0,0 +1,57 @@
+// 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/modem_manager_proxy.h"
+
+#include "shill/cellular/modem_manager.h"
+#include "shill/logging.h"
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kDBus;
+static string ObjectID(const DBus::Path *p) { return *p; }
+}
+
+ModemManagerProxy::ModemManagerProxy(DBus::Connection *connection,
+                                     ModemManagerClassic *manager,
+                                     const string &path,
+                                     const string &service)
+    : proxy_(connection, manager, path, service) {}
+
+ModemManagerProxy::~ModemManagerProxy() {}
+
+vector<DBus::Path> ModemManagerProxy::EnumerateDevices() {
+  SLOG(&proxy_.path(), 2) << __func__;
+  try {
+    return proxy_.EnumerateDevices();
+  } catch (const DBus::Error &e) {
+    LOG(ERROR) << "DBus exception: " << e.name() << ": " << e.what();
+  }
+  return vector<DBus::Path>();
+}
+
+ModemManagerProxy::Proxy::Proxy(DBus::Connection *connection,
+                                ModemManagerClassic *manager,
+                                const string &path,
+                                const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()),
+      manager_(manager) {}
+
+ModemManagerProxy::Proxy::~Proxy() {}
+
+void ModemManagerProxy::Proxy::DeviceAdded(const DBus::Path &device) {
+  SLOG(&path(), 2) << __func__;
+  manager_->OnDeviceAdded(device);
+}
+
+void ModemManagerProxy::Proxy::DeviceRemoved(const DBus::Path &device) {
+  SLOG(&path(), 2) << __func__;
+  manager_->OnDeviceRemoved(device);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_manager_proxy.h b/cellular/modem_manager_proxy.h
new file mode 100644
index 0000000..a8b850c
--- /dev/null
+++ b/cellular/modem_manager_proxy.h
@@ -0,0 +1,64 @@
+// 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_MODEM_MANAGER_PROXY_H_
+#define SHILL_CELLULAR_MODEM_MANAGER_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.h"
+#include "shill/cellular/modem_manager_proxy_interface.h"
+
+namespace shill {
+
+class ModemManagerClassic;
+
+// There's a single proxy per (old) ModemManager service identified by
+// its DBus |path| and owner name |service|.
+class ModemManagerProxy : public ModemManagerProxyInterface {
+ public:
+  ModemManagerProxy(DBus::Connection *connection,
+                    ModemManagerClassic *manager,
+                    const std::string &path,
+                    const std::string &service);
+  ~ModemManagerProxy() override;
+
+  // Inherited from ModemManagerProxyInterface.
+  virtual std::vector<DBus::Path> EnumerateDevices();
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          ModemManagerClassic *manager,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Signal callbacks inherited from ModemManager_proxy.
+    virtual void DeviceAdded(const DBus::Path &device);
+    virtual void DeviceRemoved(const DBus::Path &device);
+
+    // Method callbacks inherited from ModemManager_proxy.
+    // [None]
+
+    // The owner of this proxy.
+    ModemManagerClassic *manager_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemManagerProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_MANAGER_PROXY_H_
diff --git a/cellular/modem_manager_proxy_interface.h b/cellular/modem_manager_proxy_interface.h
new file mode 100644
index 0000000..a5eba33
--- /dev/null
+++ b/cellular/modem_manager_proxy_interface.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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_MODEM_MANAGER_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_MANAGER_PROXY_INTERFACE_H_
+
+#include <vector>
+
+#include <dbus-c++/types.h>
+
+namespace shill {
+
+// These are the methods that a ModemManager proxy must support. The interface
+// is provided so that it can be mocked in tests.
+class ModemManagerProxyInterface {
+ public:
+  virtual ~ModemManagerProxyInterface() {}
+
+  virtual std::vector<DBus::Path> EnumerateDevices() = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_MANAGER_PROXY_INTERFACE_H_
diff --git a/cellular/modem_manager_unittest.cc b/cellular/modem_manager_unittest.cc
new file mode 100644
index 0000000..ececf60
--- /dev/null
+++ b/cellular/modem_manager_unittest.cc
@@ -0,0 +1,297 @@
+// 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/modem_manager.h"
+
+#include <base/stl_util.h>
+#include <ModemManager/ModemManager.h>
+
+#include "shill/cellular/mock_dbus_objectmanager_proxy.h"
+#include "shill/cellular/mock_modem.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/cellular/mock_modem_manager_proxy.h"
+#include "shill/manager.h"
+#include "shill/mock_control.h"
+#include "shill/mock_dbus_service_proxy.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_proxy_factory.h"
+#include "shill/testing.h"
+
+using std::string;
+using std::shared_ptr;
+using std::vector;
+using testing::_;
+using testing::Invoke;
+using testing::Pointee;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrEq;
+using testing::Test;
+
+namespace shill {
+
+class ModemManagerTest : public Test {
+ public:
+  ModemManagerTest()
+      : manager_(&control_, &dispatcher_, nullptr, nullptr),
+        modem_info_(&control_, &dispatcher_, nullptr, &manager_, nullptr),
+        dbus_service_proxy_(nullptr) {}
+
+  virtual void SetUp() {
+    modem_.reset(new StrictModem(kOwner, kService, kModemPath, &modem_info_));
+    manager_.dbus_manager_.reset(new DBusManager());
+    dbus_service_proxy_ = new MockDBusServiceProxy();
+    // Ownership  of |dbus_service_proxy_| is transferred to
+    // |manager_.dbus_manager_|.
+    manager_.dbus_manager_->proxy_.reset(dbus_service_proxy_);
+  }
+
+ protected:
+  static const char kService[];
+  static const char kPath[];
+  static const char kOwner[];
+  static const char kModemPath[];
+
+  shared_ptr<StrictModem> modem_;
+
+  EventDispatcher dispatcher_;
+  MockControl control_;
+  MockManager manager_;
+  MockModemInfo modem_info_;
+  MockProxyFactory proxy_factory_;
+  MockDBusServiceProxy *dbus_service_proxy_;
+};
+
+const char ModemManagerTest::kService[] = "org.chromium.ModemManager";
+const char ModemManagerTest::kPath[] = "/org/chromium/ModemManager";
+const char ModemManagerTest::kOwner[] = ":1.17";
+const char ModemManagerTest::kModemPath[] = "/org/blah/Modem/blah/0";
+
+class ModemManagerCoreTest : public ModemManagerTest {
+ public:
+  ModemManagerCoreTest()
+      : ModemManagerTest(),
+        modem_manager_(kService, kPath, &modem_info_) {}
+
+ protected:
+  ModemManager modem_manager_;
+};
+
+TEST_F(ModemManagerCoreTest, StartStopWithModemManagerServiceAbsent) {
+  StringCallback get_name_owner_callback;
+  EXPECT_CALL(*dbus_service_proxy_, GetNameOwner(kService, _, _, _))
+      .WillOnce(SaveArg<2>(&get_name_owner_callback));
+  modem_manager_.Start();
+  get_name_owner_callback.Run("", Error());
+  EXPECT_EQ("", modem_manager_.owner_);
+
+  modem_manager_.Stop();
+  EXPECT_EQ("", modem_manager_.owner_);
+}
+
+TEST_F(ModemManagerCoreTest, StartStopWithModemManagerServicePresent) {
+  StringCallback get_name_owner_callback;
+  EXPECT_CALL(*dbus_service_proxy_, GetNameOwner(kService, _, _, _))
+      .WillOnce(SaveArg<2>(&get_name_owner_callback));
+  modem_manager_.Start();
+  get_name_owner_callback.Run(kOwner, Error());
+  EXPECT_EQ(kOwner, modem_manager_.owner_);
+
+  modem_manager_.Stop();
+  EXPECT_EQ("", modem_manager_.owner_);
+}
+
+TEST_F(ModemManagerCoreTest, OnAppearVanish) {
+  EXPECT_CALL(*dbus_service_proxy_, GetNameOwner(kService, _, _, _));
+  modem_manager_.Start();
+  EXPECT_EQ("", modem_manager_.owner_);
+
+  manager_.dbus_manager()->OnNameOwnerChanged(kService, "", kOwner);
+  EXPECT_EQ(kOwner, modem_manager_.owner_);
+
+  manager_.dbus_manager()->OnNameOwnerChanged(kService, kOwner, "");
+  EXPECT_EQ("", modem_manager_.owner_);
+}
+
+TEST_F(ModemManagerCoreTest, ConnectDisconnect) {
+  EXPECT_EQ("", modem_manager_.owner_);
+  modem_manager_.Connect(kOwner);
+  EXPECT_EQ(kOwner, modem_manager_.owner_);
+  EXPECT_EQ(0, modem_manager_.modems_.size());
+
+  modem_manager_.RecordAddedModem(modem_);
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+
+  modem_manager_.ModemManager::Disconnect();
+  EXPECT_EQ("", modem_manager_.owner_);
+  EXPECT_EQ(0, modem_manager_.modems_.size());
+}
+
+TEST_F(ModemManagerCoreTest, AddRemoveModem) {
+  modem_manager_.Connect(kOwner);
+  EXPECT_FALSE(modem_manager_.ModemExists(kModemPath));
+
+  // Remove non-existent modem path.
+  modem_manager_.RemoveModem(kModemPath);
+  EXPECT_FALSE(modem_manager_.ModemExists(kModemPath));
+
+  modem_manager_.RecordAddedModem(modem_);
+  EXPECT_TRUE(modem_manager_.ModemExists(kModemPath));
+
+  // Add an already added modem.
+  modem_manager_.RecordAddedModem(modem_);
+  EXPECT_TRUE(modem_manager_.ModemExists(kModemPath));
+
+  modem_manager_.RemoveModem(kModemPath);
+  EXPECT_FALSE(modem_manager_.ModemExists(kModemPath));
+
+  // Remove an already removed modem path.
+  modem_manager_.RemoveModem(kModemPath);
+  EXPECT_FALSE(modem_manager_.ModemExists(kModemPath));
+}
+
+class ModemManagerClassicMockInit : public ModemManagerClassic {
+ public:
+  ModemManagerClassicMockInit(const string &service,
+                              const string &path,
+                              ModemInfo *modem_info_) :
+      ModemManagerClassic(service, path, modem_info_) {}
+
+  MOCK_METHOD1(InitModemClassic, void(shared_ptr<ModemClassic>));
+};
+
+class ModemManagerClassicTest : public ModemManagerTest {
+ public:
+  ModemManagerClassicTest()
+      : ModemManagerTest(),
+        modem_manager_(kService, kPath, &modem_info_),
+        proxy_(new MockModemManagerProxy()) {}
+
+ protected:
+  virtual void SetUp() {
+    modem_manager_.proxy_factory_ = &proxy_factory_;
+  }
+
+  virtual void TearDown() {
+    modem_manager_.proxy_factory_ = nullptr;
+  }
+
+  ModemManagerClassicMockInit modem_manager_;
+  std::unique_ptr<MockModemManagerProxy> proxy_;
+};
+
+TEST_F(ModemManagerClassicTest, Connect) {
+  EXPECT_EQ("", modem_manager_.owner_);
+
+  EXPECT_CALL(proxy_factory_, CreateModemManagerProxy(_, kPath, kOwner))
+      .WillOnce(ReturnAndReleasePointee(&proxy_));
+  EXPECT_CALL(*proxy_, EnumerateDevices())
+      .WillOnce(Return(vector<DBus::Path>(1, kModemPath)));
+
+  EXPECT_CALL(modem_manager_,
+              InitModemClassic(
+                  Pointee(Field(&Modem::path_, StrEq(kModemPath)))));
+
+  modem_manager_.Connect(kOwner);
+  EXPECT_EQ(kOwner, modem_manager_.owner_);
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+  ASSERT_TRUE(ContainsKey(modem_manager_.modems_, kModemPath));
+}
+
+
+class ModemManager1MockInit : public ModemManager1 {
+ public:
+  ModemManager1MockInit(const string &service,
+                        const string &path,
+                        ModemInfo *modem_info_) :
+      ModemManager1(service, path, modem_info_) {}
+  MOCK_METHOD2(InitModem1, void(shared_ptr<Modem1>,
+                                const DBusInterfaceToProperties &));
+};
+
+
+class ModemManager1Test : public ModemManagerTest {
+ public:
+  ModemManager1Test()
+      : ModemManagerTest(),
+        modem_manager_(kService, kPath, &modem_info_),
+        proxy_(new MockDBusObjectManagerProxy()) {}
+
+ protected:
+  virtual void SetUp() {
+    modem_manager_.proxy_factory_ = &proxy_factory_;
+    proxy_->IgnoreSetCallbacks();
+  }
+
+  virtual void TearDown() {
+    modem_manager_.proxy_factory_ = nullptr;
+  }
+
+  void Connect(const DBusObjectsWithProperties &expected_objects) {
+    EXPECT_CALL(proxy_factory_, CreateDBusObjectManagerProxy(kPath, kOwner))
+        .WillOnce(ReturnAndReleasePointee(&proxy_));
+    EXPECT_CALL(*proxy_, set_interfaces_added_callback(_));
+    EXPECT_CALL(*proxy_, set_interfaces_removed_callback(_));
+    ManagedObjectsCallback get_managed_objects_callback;
+    EXPECT_CALL(*proxy_, GetManagedObjects(_, _, _))
+        .WillOnce(SaveArg<1>(&get_managed_objects_callback));
+    modem_manager_.Connect(kOwner);
+    get_managed_objects_callback.Run(expected_objects, Error());
+  }
+
+  static DBusObjectsWithProperties GetModemWithProperties() {
+    DBusPropertiesMap o_fd_mm1_modem;
+
+    DBusInterfaceToProperties properties;
+    properties[MM_DBUS_INTERFACE_MODEM] = o_fd_mm1_modem;
+
+    DBusObjectsWithProperties objects_with_properties;
+    objects_with_properties[kModemPath] = properties;
+
+    return objects_with_properties;
+  }
+
+  ModemManager1MockInit modem_manager_;
+  std::unique_ptr<MockDBusObjectManagerProxy> proxy_;
+  MockProxyFactory proxy_factory_;
+};
+
+TEST_F(ModemManager1Test, Connect) {
+  Connect(GetModemWithProperties());
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+  EXPECT_TRUE(ContainsKey(modem_manager_.modems_, kModemPath));
+}
+
+TEST_F(ModemManager1Test, AddRemoveInterfaces) {
+  // Have nothing come back from GetManagedObjects
+  Connect(DBusObjectsWithProperties());
+  EXPECT_EQ(0, modem_manager_.modems_.size());
+
+  // Add an object that doesn't have a modem interface.  Nothing should be added
+  EXPECT_CALL(modem_manager_, InitModem1(_, _)).Times(0);
+  modem_manager_.OnInterfacesAddedSignal(kModemPath,
+                                         DBusInterfaceToProperties());
+  EXPECT_EQ(0, modem_manager_.modems_.size());
+
+  // Actually add a modem
+  EXPECT_CALL(modem_manager_, InitModem1(_, _)).Times(1);
+  modem_manager_.OnInterfacesAddedSignal(kModemPath,
+                                         GetModemWithProperties()[kModemPath]);
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+
+  // Remove an irrelevant interface
+  vector<string> not_including_modem_interface;
+  not_including_modem_interface.push_back("not.a.modem.interface");
+  modem_manager_.OnInterfacesRemovedSignal(kModemPath,
+                                           not_including_modem_interface);
+  EXPECT_EQ(1, modem_manager_.modems_.size());
+
+  // Remove the modem
+  vector<string> with_modem_interface;
+  with_modem_interface.push_back(MM_DBUS_INTERFACE_MODEM);
+  modem_manager_.OnInterfacesRemovedSignal(kModemPath, with_modem_interface);
+  EXPECT_EQ(0, modem_manager_.modems_.size());
+}
+
+}  // namespace shill
diff --git a/cellular/modem_proxy.cc b/cellular/modem_proxy.cc
new file mode 100644
index 0000000..f2b5695
--- /dev/null
+++ b/cellular/modem_proxy.cc
@@ -0,0 +1,108 @@
+// 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/modem_proxy.h"
+
+#include <memory>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+
+using base::Bind;
+using base::Callback;
+using base::StringPrintf;
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+typedef Callback<void(const ModemHardwareInfo &,
+                      const Error &)> ModemInfoCallback;
+
+ModemProxy::ModemProxy(DBus::Connection *connection,
+                       const string &path,
+                       const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemProxy::~ModemProxy() {}
+
+void ModemProxy::set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback) {
+  proxy_.set_state_changed_callback(callback);
+}
+
+void ModemProxy::Enable(bool enable, Error *error,
+                        const ResultCallback &callback, int timeout) {
+  BeginAsyncDBusCall(StringPrintf("%s(%s)", __func__,
+                                  enable ? "true" : "false"),
+                     proxy_, &Proxy::EnableAsync, callback, error,
+                     &CellularError::FromDBusError, timeout, enable);
+}
+
+void ModemProxy::Disconnect(Error *error, const ResultCallback &callback,
+                            int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::DisconnectAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+void ModemProxy::GetModemInfo(Error *error,
+                              const ModemInfoCallback &callback,
+                              int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetInfoAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+ModemProxy::Proxy::Proxy(DBus::Connection *connection,
+                         const string &path,
+                         const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemProxy::Proxy::~Proxy() {}
+
+void ModemProxy::Proxy::set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback) {
+  state_changed_callback_ = callback;
+}
+
+void ModemProxy::Proxy::StateChanged(
+    const uint32_t &old, const uint32_t &_new, const uint32_t &reason) {
+  SLOG(&path(), 2) << __func__ << "(" << old << ", " << _new << ", "
+                   << reason << ")";
+  state_changed_callback_.Run(old, _new, reason);
+}
+
+void ModemProxy::Proxy::EnableCallback(const DBus::Error &dberror, void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+void ModemProxy::Proxy::GetInfoCallback(const ModemHardwareInfo &info,
+                                        const DBus::Error &dberror,
+                                        void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ModemInfoCallback> callback(
+      reinterpret_cast<ModemInfoCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(info, error);
+}
+
+void ModemProxy::Proxy::DisconnectCallback(const DBus::Error &dberror,
+                                           void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_proxy.h b/cellular/modem_proxy.h
new file mode 100644
index 0000000..909b7d9
--- /dev/null
+++ b/cellular/modem_proxy.h
@@ -0,0 +1,73 @@
+// 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_MODEM_PROXY_H_
+#define SHILL_CELLULAR_MODEM_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.Modem.h"
+#include "shill/cellular/modem_proxy_interface.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.
+class ModemProxy : public ModemProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem DBus object proxy at |path| owned by
+  // |service|.
+  ModemProxy(DBus::Connection *connection,
+             const std::string &path,
+             const std::string &service);
+  ~ModemProxy() override;
+
+  // Inherited from ModemProxyInterface.
+  virtual void Enable(bool enable, Error *error,
+                      const ResultCallback &callback, int timeout);
+  virtual void Disconnect(Error *error, const ResultCallback &callback,
+                          int timeout);
+  virtual void GetModemInfo(Error *error, const ModemInfoCallback &callback,
+                            int timeout);
+
+  virtual void set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager::Modem_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+    void set_state_changed_callback(
+        const ModemStateChangedSignalCallback &callback);
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem_proxy.
+    virtual void StateChanged(
+        const uint32_t &old, const uint32_t &_new, const uint32_t &reason);
+
+    // Method callbacks inherited from ModemManager::Modem_proxy.
+    virtual void EnableCallback(const DBus::Error &dberror, void *data);
+    virtual void GetInfoCallback(const ModemHardwareInfo &info,
+                                 const DBus::Error &dberror, void *data);
+    virtual void DisconnectCallback(const DBus::Error &dberror, void *data);
+
+    ModemStateChangedSignalCallback state_changed_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_PROXY_H_
diff --git a/cellular/modem_proxy_interface.h b/cellular/modem_proxy_interface.h
new file mode 100644
index 0000000..77891d5
--- /dev/null
+++ b/cellular/modem_proxy_interface.h
@@ -0,0 +1,47 @@
+// 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_MODEM_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_PROXY_INTERFACE_H_
+
+#include <string>
+
+#include <dbus-c++/types.h>
+
+#include "shill/callbacks.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+
+class CallContext;
+class Error;
+
+typedef DBus::Struct<std::string, std::string, std::string> ModemHardwareInfo;
+
+typedef base::Callback<void(uint32_t, uint32_t, uint32_t)>
+    ModemStateChangedSignalCallback;
+typedef base::Callback<void(const ModemHardwareInfo &,
+                            const Error &)> ModemInfoCallback;
+
+// These are the methods that a ModemManager.Modem proxy must support. The
+// interface is provided so that it can be mocked in tests. All calls are
+// made asynchronously.
+class ModemProxyInterface {
+ public:
+  virtual ~ModemProxyInterface() {}
+
+  virtual void Enable(bool enable, Error *error,
+                      const ResultCallback &callback, int timeout) = 0;
+  virtual void Disconnect(Error *error, const ResultCallback &callback,
+                          int timeout) = 0;
+  virtual void GetModemInfo(Error *error, const ModemInfoCallback &callback,
+                            int timeout) = 0;
+
+  virtual void set_state_changed_callback(
+      const ModemStateChangedSignalCallback &callback) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_PROXY_INTERFACE_H_
diff --git a/cellular/modem_simple_proxy.cc b/cellular/modem_simple_proxy.cc
new file mode 100644
index 0000000..01bf849
--- /dev/null
+++ b/cellular/modem_simple_proxy.cc
@@ -0,0 +1,76 @@
+// 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/modem_simple_proxy.h"
+
+#include <memory>
+
+#include <base/bind.h>
+
+#include "shill/cellular/cellular_error.h"
+#include "shill/dbus_async_call_helper.h"
+#include "shill/error.h"
+#include "shill/logging.h"
+
+using base::Bind;
+using base::Callback;
+using std::string;
+using std::unique_ptr;
+
+namespace shill {
+
+typedef Callback<void(const DBusPropertiesMap &,
+                      const Error &)> ModemStatusCallback;
+
+ModemSimpleProxy::ModemSimpleProxy(DBus::Connection *connection,
+                                   const string &path,
+                                   const string &service)
+    : proxy_(connection, path, service) {}
+
+ModemSimpleProxy::~ModemSimpleProxy() {}
+
+void ModemSimpleProxy::GetModemStatus(Error *error,
+                                      const DBusPropertyMapCallback &callback,
+                                      int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::GetStatusAsync, callback,
+                     error, &CellularError::FromDBusError, timeout);
+}
+
+void ModemSimpleProxy::Connect(const DBusPropertiesMap &properties,
+                               Error *error,
+                               const ResultCallback &callback,
+                               int timeout) {
+  BeginAsyncDBusCall(__func__, proxy_, &Proxy::ConnectAsync, callback,
+                     error, &CellularError::FromDBusError, timeout,
+                     properties);
+}
+
+ModemSimpleProxy::Proxy::Proxy(DBus::Connection *connection,
+                               const string &path,
+                               const string &service)
+    : DBus::ObjectProxy(*connection, path, service.c_str()) {}
+
+ModemSimpleProxy::Proxy::~Proxy() {}
+
+void ModemSimpleProxy::Proxy::GetStatusCallback(const DBusPropertiesMap &props,
+                                                const DBus::Error &dberror,
+                                                void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<DBusPropertyMapCallback> callback(
+      reinterpret_cast<DBusPropertyMapCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(props, error);
+}
+
+void ModemSimpleProxy::Proxy::ConnectCallback(const DBus::Error &dberror,
+                                              void *data) {
+  SLOG(&path(), 2) << __func__;
+  unique_ptr<ResultCallback> callback(reinterpret_cast<ResultCallback *>(data));
+  Error error;
+  CellularError::FromDBusError(dberror, &error);
+  callback->Run(error);
+}
+
+}  // namespace shill
diff --git a/cellular/modem_simple_proxy.h b/cellular/modem_simple_proxy.h
new file mode 100644
index 0000000..6056a24
--- /dev/null
+++ b/cellular/modem_simple_proxy.h
@@ -0,0 +1,64 @@
+// 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_MODEM_SIMPLE_PROXY_H_
+#define SHILL_CELLULAR_MODEM_SIMPLE_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "dbus_proxies/org.freedesktop.ModemManager.Modem.Simple.h"
+#include "shill/cellular/modem_simple_proxy_interface.h"
+
+namespace shill {
+
+// A proxy to (old) ModemManager.Modem.Simple.
+class ModemSimpleProxy : public ModemSimpleProxyInterface {
+ public:
+  // Constructs a ModemManager.Modem.Simple DBus object proxy at
+  // |path| owned by |service|.
+  ModemSimpleProxy(DBus::Connection *connection,
+                   const std::string &path,
+                   const std::string &service);
+  ~ModemSimpleProxy() override;
+
+  // Inherited from ModemSimpleProxyInterface.
+  virtual void GetModemStatus(Error *error,
+                              const DBusPropertyMapCallback &callback,
+                              int timeout);
+  virtual void Connect(const DBusPropertiesMap &properties,
+                       Error *error,
+                       const ResultCallback &callback,
+                       int timeout);
+
+ private:
+  class Proxy : public org::freedesktop::ModemManager::Modem::Simple_proxy,
+                public DBus::ObjectProxy {
+   public:
+    Proxy(DBus::Connection *connection,
+          const std::string &path,
+          const std::string &service);
+    ~Proxy() override;
+
+   private:
+    // Signal callbacks inherited from ModemManager::Modem::Simple_proxy.
+    // [None]
+
+    // Method callbacks inherited from ModemManager::Modem::Simple_proxy.
+    virtual void GetStatusCallback(const DBusPropertiesMap &props,
+                                   const DBus::Error &dberror, void *data);
+    virtual void ConnectCallback(const DBus::Error &dberror, void *data);
+
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModemSimpleProxy);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_SIMPLE_PROXY_H_
diff --git a/cellular/modem_simple_proxy_interface.h b/cellular/modem_simple_proxy_interface.h
new file mode 100644
index 0000000..6fd35e9
--- /dev/null
+++ b/cellular/modem_simple_proxy_interface.h
@@ -0,0 +1,32 @@
+// 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_MODEM_SIMPLE_PROXY_INTERFACE_H_
+#define SHILL_CELLULAR_MODEM_SIMPLE_PROXY_INTERFACE_H_
+
+#include "shill/callbacks.h"
+#include "shill/dbus_properties.h"
+
+namespace shill {
+
+class Error;
+
+// These are the methods that a ModemManager.Modem.Simple proxy must
+// support. The interface is provided so that it can be mocked in tests.
+// All calls are made asynchronously.
+class ModemSimpleProxyInterface {
+ public:
+  virtual ~ModemSimpleProxyInterface() {}
+
+  virtual void GetModemStatus(Error *error,
+                              const DBusPropertyMapCallback &callback,
+                              int timeout) = 0;
+  virtual void Connect(const DBusPropertiesMap &properties,
+                       Error *error, const ResultCallback &callback,
+                       int timeout) = 0;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_MODEM_SIMPLE_PROXY_INTERFACE_H_
diff --git a/cellular/modem_unittest.cc b/cellular/modem_unittest.cc
new file mode 100644
index 0000000..e0434fa
--- /dev/null
+++ b/cellular/modem_unittest.cc
@@ -0,0 +1,278 @@
+// 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/modem.h"
+
+#include <vector>
+
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <mm/mm-modem.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include "shill/cellular/cellular.h"
+#include "shill/cellular/cellular_capability_gsm.h"
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_modem.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/dbus_property_matchers.h"
+#include "shill/event_dispatcher.h"
+#include "shill/manager.h"
+#include "shill/mock_device_info.h"
+#include "shill/net/mock_rtnl_handler.h"
+#include "shill/net/rtnl_handler.h"
+#include "shill/proxy_factory.h"
+
+using std::string;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::Test;
+
+namespace shill {
+
+namespace {
+
+const int kTestInterfaceIndex = 5;
+const char kLinkName[] = "usb0";
+const char kOwner[] = ":1.18";
+const char kService[] = "org.chromium.ModemManager";
+const char kPath[] = "/org/chromium/ModemManager/Gobi/0";
+const unsigned char kAddress[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+const char kAddressAsString[] = "000102030405";
+
+}  // namespace
+
+class ModemTest : public Test {
+ public:
+  ModemTest()
+      : modem_info_(nullptr, &dispatcher_, nullptr, nullptr, nullptr),
+        device_info_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                     modem_info_.metrics(), modem_info_.manager()),
+        modem_(
+            new StrictModem(
+                kOwner,
+                kService,
+                kPath,
+                &modem_info_)) {}
+  virtual void SetUp();
+  virtual void TearDown();
+
+  void ReplaceSingletons() {
+    modem_->rtnl_handler_ = &rtnl_handler_;
+  }
+
+ protected:
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  MockDeviceInfo device_info_;
+  std::unique_ptr<StrictModem> modem_;
+  MockRTNLHandler rtnl_handler_;
+  ByteString expected_address_;
+};
+
+void ModemTest::SetUp() {
+  EXPECT_EQ(kOwner, modem_->owner_);
+  EXPECT_EQ(kService, modem_->service_);
+  EXPECT_EQ(kPath, modem_->path_);
+  ReplaceSingletons();
+  expected_address_ = ByteString(kAddress, arraysize(kAddress));
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(kLinkName)).
+      WillRepeatedly(Return(kTestInterfaceIndex));
+
+  EXPECT_CALL(*modem_info_.mock_manager(), device_info())
+      .WillRepeatedly(Return(&device_info_));
+}
+
+void ModemTest::TearDown() {
+  modem_.reset();
+}
+
+TEST_F(ModemTest, PendingDevicePropertiesAndCreate) {
+  static const char kSentinel[] = "sentinel";
+  static const uint32_t kSentinelValue = 17;
+
+  DBusInterfaceToProperties properties;
+  properties[MM_MODEM_INTERFACE][kSentinel].writer().append_uint32(
+      kSentinelValue);
+
+  EXPECT_CALL(*modem_, GetLinkName(_, _)).WillRepeatedly(DoAll(
+      SetArgumentPointee<1>(string(kLinkName)),
+      Return(true)));
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(StrEq(kLinkName))).
+      WillRepeatedly(Return(kTestInterfaceIndex));
+
+  // The first time we call CreateDeviceFromModemProperties,
+  // GetMACAddress will fail.
+  EXPECT_CALL(device_info_, GetMACAddress(kTestInterfaceIndex, _)).
+      WillOnce(Return(false));
+  EXPECT_CALL(*modem_, GetModemInterface()).
+      WillRepeatedly(Return(MM_MODEM_INTERFACE));
+  modem_->CreateDeviceFromModemProperties(properties);
+  EXPECT_FALSE(modem_->device_.get());
+
+  // On the second time, we allow GetMACAddress to succeed.  Now we
+  // expect a device to be built
+  EXPECT_CALL(device_info_, GetMACAddress(kTestInterfaceIndex, _)).
+      WillOnce(DoAll(SetArgumentPointee<1>(expected_address_),
+                     Return(true)));
+
+  // modem will take ownership
+  MockCellular *cellular = new MockCellular(
+      &modem_info_,
+      kLinkName,
+      kAddressAsString,
+      kTestInterfaceIndex,
+      Cellular::kTypeCDMA,
+      kOwner,
+      kService,
+      kPath,
+      ProxyFactory::GetInstance());
+
+  EXPECT_CALL(*modem_,
+              ConstructCellular(StrEq(kLinkName),
+                                StrEq(kAddressAsString),
+                                kTestInterfaceIndex)).
+      WillOnce(Return(cellular));
+
+  EXPECT_CALL(*cellular, OnDBusPropertiesChanged(
+      _,
+      HasDBusPropertyWithValueU32(kSentinel, kSentinelValue),
+      _));
+  EXPECT_CALL(device_info_, RegisterDevice(_));
+  modem_->OnDeviceInfoAvailable(kLinkName);
+
+  EXPECT_TRUE(modem_->device_.get());
+
+  // Add expectations for the eventual |modem_| destruction.
+  EXPECT_CALL(*cellular, DestroyService());
+  EXPECT_CALL(device_info_, DeregisterDevice(_));
+}
+
+TEST_F(ModemTest, EarlyDeviceProperties) {
+  // OnDeviceInfoAvailable called before
+  // CreateDeviceFromModemProperties: Do nothing
+  modem_->OnDeviceInfoAvailable(kLinkName);
+  EXPECT_FALSE(modem_->device_.get());
+}
+
+TEST_F(ModemTest, CreateDeviceEarlyFailures) {
+  DBusInterfaceToProperties properties;
+
+  EXPECT_CALL(*modem_, ConstructCellular(_, _, _)).Times(0);
+  EXPECT_CALL(*modem_, GetModemInterface()).
+      WillRepeatedly(Return(MM_MODEM_INTERFACE));
+
+  // No modem interface properties:  no device created
+  modem_->CreateDeviceFromModemProperties(properties);
+  EXPECT_FALSE(modem_->device_.get());
+
+  properties[MM_MODEM_INTERFACE] = DBusPropertiesMap();
+
+  // Link name, but no ifindex: no device created
+  EXPECT_CALL(*modem_, GetLinkName(_, _)).WillOnce(DoAll(
+      SetArgumentPointee<1>(string(kLinkName)),
+      Return(true)));
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(StrEq(kLinkName))).WillOnce(
+      Return(-1));
+  modem_->CreateDeviceFromModemProperties(properties);
+  EXPECT_FALSE(modem_->device_.get());
+
+  // The params are good, but the device is blacklisted.
+  EXPECT_CALL(*modem_, GetLinkName(_, _)).WillOnce(DoAll(
+      SetArgumentPointee<1>(string(kLinkName)),
+      Return(true)));
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(StrEq(kLinkName)))
+      .WillOnce(Return(kTestInterfaceIndex));
+  EXPECT_CALL(device_info_, GetMACAddress(kTestInterfaceIndex, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(expected_address_),
+                      Return(true)));
+  EXPECT_CALL(device_info_, IsDeviceBlackListed(kLinkName))
+      .WillRepeatedly(Return(true));
+  modem_->CreateDeviceFromModemProperties(properties);
+  EXPECT_FALSE(modem_->device_.get());
+
+  // No link name: see CreateDevicePPP.
+}
+
+TEST_F(ModemTest, CreateDevicePPP) {
+  DBusInterfaceToProperties properties;
+  properties[MM_MODEM_INTERFACE] = DBusPropertiesMap();
+
+  string dev_name(
+      base::StringPrintf(Modem::kFakeDevNameFormat, Modem::fake_dev_serial_));
+
+  // |modem_| will take ownership.
+  MockCellular *cellular = new MockCellular(
+      &modem_info_,
+      dev_name,
+      Modem::kFakeDevAddress,
+      Modem::kFakeDevInterfaceIndex,
+      Cellular::kTypeUniversal,
+      kOwner,
+      kService,
+      kPath,
+      ProxyFactory::GetInstance());
+
+  EXPECT_CALL(*modem_, GetModemInterface()).
+      WillRepeatedly(Return(MM_MODEM_INTERFACE));
+  // No link name: assumed to be a PPP dongle.
+  EXPECT_CALL(*modem_, GetLinkName(_, _)).WillOnce(Return(false));
+  EXPECT_CALL(*modem_,
+              ConstructCellular(dev_name,
+                                StrEq(Modem::kFakeDevAddress),
+                                Modem::kFakeDevInterfaceIndex)).
+      WillOnce(Return(cellular));
+  EXPECT_CALL(device_info_, RegisterDevice(_));
+
+  modem_->CreateDeviceFromModemProperties(properties);
+  EXPECT_TRUE(modem_->device_.get());
+
+  // Add expectations for the eventual |modem_| destruction.
+  EXPECT_CALL(*cellular, DestroyService());
+  EXPECT_CALL(device_info_, DeregisterDevice(_));
+}
+
+TEST_F(ModemTest, GetDeviceParams) {
+  string mac_address;
+  int interface_index = 2;
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(_)).WillOnce(Return(-1));
+  EXPECT_CALL(device_info_, GetMACAddress(_, _)).Times(AnyNumber())
+      .WillRepeatedly(Return(false));
+  EXPECT_FALSE(modem_->GetDeviceParams(&mac_address, &interface_index));
+  EXPECT_EQ(-1, interface_index);
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(_)).WillOnce(Return(-2));
+  EXPECT_CALL(device_info_, GetMACAddress(_, _)).Times(AnyNumber())
+      .WillRepeatedly(Return(false));
+  EXPECT_FALSE(modem_->GetDeviceParams(&mac_address, &interface_index));
+  EXPECT_EQ(-2, interface_index);
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(_)).WillOnce(Return(1));
+  EXPECT_CALL(device_info_, GetMACAddress(_, _)).WillOnce(Return(false));
+  EXPECT_FALSE(modem_->GetDeviceParams(&mac_address, &interface_index));
+  EXPECT_EQ(1, interface_index);
+
+  EXPECT_CALL(rtnl_handler_, GetInterfaceIndex(_)).WillOnce(Return(2));
+  EXPECT_CALL(device_info_, GetMACAddress(2, _)).
+      WillOnce(DoAll(SetArgumentPointee<1>(expected_address_),
+                     Return(true)));
+  EXPECT_TRUE(modem_->GetDeviceParams(&mac_address, &interface_index));
+  EXPECT_EQ(2, interface_index);
+  EXPECT_EQ(kAddressAsString, mac_address);
+}
+
+TEST_F(ModemTest, RejectPPPModem) {
+  // TODO(rochberg):  Port this to ModemClassic
+}
+
+}  // namespace shill
diff --git a/cellular/no_out_of_credits_detector.h b/cellular/no_out_of_credits_detector.h
new file mode 100644
index 0000000..8958796
--- /dev/null
+++ b/cellular/no_out_of_credits_detector.h
@@ -0,0 +1,41 @@
+// 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_NO_OUT_OF_CREDITS_DETECTOR_H_
+#define SHILL_CELLULAR_NO_OUT_OF_CREDITS_DETECTOR_H_
+
+#include <base/macros.h>
+
+#include "shill/cellular/out_of_credits_detector.h"
+
+namespace shill {
+
+// This object performs no out-of-credits detection.
+class NoOutOfCreditsDetector : public OutOfCreditsDetector {
+ public:
+  NoOutOfCreditsDetector(EventDispatcher *dispatcher,
+                         Manager *manager,
+                         Metrics *metrics,
+                         CellularService *service)
+      : OutOfCreditsDetector(dispatcher, manager, metrics, service) {}
+  ~NoOutOfCreditsDetector() override {}
+
+  // Resets the detector state.
+  void ResetDetector() override {}
+  // Returns |true| if this object is busy detecting out-of-credits.
+  bool IsDetecting() const override { return false; }
+  // Notifies this object of a service state change.
+  void NotifyServiceStateChanged(
+      Service::ConnectState old_state,
+      Service::ConnectState new_state) override {}
+  // Notifies this object when the subscription state has changed.
+  void NotifySubscriptionStateChanged(uint32_t subscription_state) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoOutOfCreditsDetector);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_NO_OUT_OF_CREDITS_DETECTOR_H_
diff --git a/cellular/out_of_credits_detector.cc b/cellular/out_of_credits_detector.cc
new file mode 100644
index 0000000..fb6bac5
--- /dev/null
+++ b/cellular/out_of_credits_detector.cc
@@ -0,0 +1,81 @@
+// 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/out_of_credits_detector.h"
+
+#include <string>
+
+#include "shill/cellular/active_passive_out_of_credits_detector.h"
+#include "shill/cellular/cellular_service.h"
+#include "shill/cellular/no_out_of_credits_detector.h"
+#include "shill/cellular/subscription_state_out_of_credits_detector.h"
+#include "shill/logging.h"
+
+namespace shill {
+
+using std::string;
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularService *c) { return c->GetRpcIdentifier(); }
+}
+
+OutOfCreditsDetector::OutOfCreditsDetector(EventDispatcher *dispatcher,
+                                           Manager *manager,
+                                           Metrics *metrics,
+                                           CellularService *service)
+    : dispatcher_(dispatcher),
+      manager_(manager),
+      metrics_(metrics),
+      service_(service),
+      out_of_credits_(false) {
+}
+
+OutOfCreditsDetector::~OutOfCreditsDetector() {
+}
+
+// static
+OutOfCreditsDetector *
+OutOfCreditsDetector::CreateDetector(OOCType detector_type,
+                                     EventDispatcher *dispatcher,
+                                     Manager *manager,
+                                     Metrics *metrics,
+                                     CellularService *service) {
+  switch (detector_type) {
+    case OOCTypeActivePassive:
+      LOG(INFO) << __func__
+                << ": Using active-passive out-of-credits detection";
+      return
+          new ActivePassiveOutOfCreditsDetector(dispatcher,
+                                                manager,
+                                                metrics,
+                                                service);
+    case OOCTypeSubscriptionState:
+      LOG(INFO) << __func__
+                << ": Using subscription status out-of-credits detection";
+      return
+          new SubscriptionStateOutOfCreditsDetector(dispatcher,
+                                                    manager,
+                                                    metrics,
+                                                    service);
+    default:
+      LOG(INFO) << __func__ << ": No out-of-credits detection";
+      return
+          new NoOutOfCreditsDetector(dispatcher,
+                                     manager,
+                                     metrics,
+                                     service);
+  }
+}
+
+void OutOfCreditsDetector::ReportOutOfCredits(bool state) {
+  SLOG(service_, 2) << __func__ << ": " << state;
+  if (state == out_of_credits_) {
+    return;
+  }
+  out_of_credits_ = state;
+  service_->SignalOutOfCreditsChanged(state);
+}
+
+}  // namespace shill
diff --git a/cellular/out_of_credits_detector.h b/cellular/out_of_credits_detector.h
new file mode 100644
index 0000000..8238d9b
--- /dev/null
+++ b/cellular/out_of_credits_detector.h
@@ -0,0 +1,93 @@
+// 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_OUT_OF_CREDITS_DETECTOR_H_
+#define SHILL_CELLULAR_OUT_OF_CREDITS_DETECTOR_H_
+
+#include <base/macros.h>
+
+#include <string>
+
+#include "shill/service.h"
+
+namespace shill {
+
+class CellularService;
+class EventDispatcher;
+class Manager;
+class Metrics;
+class TrafficMonitor;
+
+// Base class for the various out-of-credits detection mechanism.
+class OutOfCreditsDetector {
+ public:
+  OutOfCreditsDetector(EventDispatcher *dispatcher,
+                       Manager *manager,
+                       Metrics *metrics,
+                       CellularService *service);
+  virtual ~OutOfCreditsDetector();
+
+  // Various types of out-of-credits detections.
+  enum OOCType {
+    // No out-of-credits detection is employed.
+    OOCTypeNone = 0,
+    // Passively monitors the traffic for TX congestion and DNS failures, then
+    // actively probe the network for TX congestion to determine if the
+    // network has entered an OOC condition.
+    OOCTypeActivePassive = 1,
+    // Use ModemManager SubscriptionState property to determine OOC condition.
+    OOCTypeSubscriptionState = 2
+  };
+
+  // Creates a specific out-of-credits detector.
+  // For OOCTypeNone, this methods returns NoOutOfCreditsDetector. For
+  // OOCTypeActivePassive, this method returns
+  // ActivePassiveOutOfCreditsDetector. For OOCTypeSubscriptionState,
+  // this method returns SubscriptionStateOutOfCreditsDetector.
+  static OutOfCreditsDetector *CreateDetector(OOCType detector_type,
+                                              EventDispatcher *dispatcher,
+                                              Manager *manager,
+                                              Metrics *metrics,
+                                              CellularService *service);
+
+  // Resets the detector state.
+  virtual void ResetDetector() = 0;
+  // Returns |true| if this object is busy detecting out-of-credits.
+  virtual bool IsDetecting() const = 0;
+  // Notifies this object of a service state change.
+  virtual void NotifyServiceStateChanged(Service::ConnectState old_state,
+                                         Service::ConnectState new_state) = 0;
+  // Notifies this object when the subscription state has changed.
+  virtual void NotifySubscriptionStateChanged(uint32_t subscription_state) = 0;
+
+  virtual bool out_of_credits() const { return out_of_credits_; }
+
+ protected:
+  FRIEND_TEST(ActivePassiveOutOfCreditsDetectorTest,
+      ConnectDisconnectLoopDetectionSkippedAlreadyOutOfCredits);
+
+  // Sets the out-of-credits state for this object and also tells the service
+  // object to signal the property change.
+  void ReportOutOfCredits(bool state);
+
+  // Property accessors reserved for subclasses.
+  EventDispatcher *dispatcher() const { return dispatcher_; }
+  Manager *manager() const { return manager_; }
+  Metrics *metrics() const { return metrics_; }
+  CellularService *service() const { return service_; }
+
+ private:
+  EventDispatcher *dispatcher_;
+  Manager *manager_;
+  Metrics *metrics_;
+  CellularService *service_;
+  // Flag indicating if the account is out-of-credits.
+  bool out_of_credits_;
+
+  DISALLOW_COPY_AND_ASSIGN(OutOfCreditsDetector);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_OUT_OF_CREDITS_DETECTOR_H_
diff --git a/cellular/subscription_state_out_of_credits_detector.cc b/cellular/subscription_state_out_of_credits_detector.cc
new file mode 100644
index 0000000..5cea687
--- /dev/null
+++ b/cellular/subscription_state_out_of_credits_detector.cc
@@ -0,0 +1,48 @@
+// 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/subscription_state_out_of_credits_detector.h"
+
+#include <string>
+
+#include "ModemManager/ModemManager.h"
+
+#include "shill/cellular/cellular_service.h"
+#include "shill/logging.h"
+
+using std::string;
+
+namespace shill {
+
+namespace Logging {
+static auto kModuleLogScope = ScopeLogger::kCellular;
+static string ObjectID(CellularService *c) { return c->GetRpcIdentifier(); }
+}
+
+SubscriptionStateOutOfCreditsDetector::SubscriptionStateOutOfCreditsDetector(
+    EventDispatcher *dispatcher,
+    Manager *manager,
+    Metrics *metrics,
+    CellularService *service)
+    : OutOfCreditsDetector(dispatcher, manager, metrics, service) {
+}
+
+SubscriptionStateOutOfCreditsDetector::
+    ~SubscriptionStateOutOfCreditsDetector() {
+}
+
+void SubscriptionStateOutOfCreditsDetector::NotifySubscriptionStateChanged(
+    uint32_t subscription_state) {
+  bool ooc = (static_cast<MMModem3gppSubscriptionState>(subscription_state) ==
+              MM_MODEM_3GPP_SUBSCRIPTION_STATE_OUT_OF_DATA);
+  if (ooc != out_of_credits()) {
+    if (ooc)
+      SLOG(service(), 2) << "Marking service out-of-credits";
+    else
+      SLOG(service(), 2) << "Marking service as not out-of-credits";
+  }
+  ReportOutOfCredits(ooc);
+}
+
+}  // namespace shill
diff --git a/cellular/subscription_state_out_of_credits_detector.h b/cellular/subscription_state_out_of_credits_detector.h
new file mode 100644
index 0000000..aed85b9
--- /dev/null
+++ b/cellular/subscription_state_out_of_credits_detector.h
@@ -0,0 +1,36 @@
+// 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_SUBSCRIPTION_STATE_OUT_OF_CREDITS_DETECTOR_H_
+#define SHILL_CELLULAR_SUBSCRIPTION_STATE_OUT_OF_CREDITS_DETECTOR_H_
+
+#include "shill/cellular/out_of_credits_detector.h"
+
+namespace shill {
+
+// Detects out-of-credits condition by using the subscription state.
+class SubscriptionStateOutOfCreditsDetector : public OutOfCreditsDetector {
+ public:
+  SubscriptionStateOutOfCreditsDetector(EventDispatcher *dispatcher,
+                                        Manager *manager,
+                                        Metrics *metrics,
+                                        CellularService *service);
+  ~SubscriptionStateOutOfCreditsDetector() override;
+
+  void ResetDetector() override {}
+  bool IsDetecting() const override { return false; }
+  void NotifyServiceStateChanged(
+      Service::ConnectState old_state,
+      Service::ConnectState new_state) override {}
+  void NotifySubscriptionStateChanged(uint32_t subscription_state) override;
+
+ private:
+  friend class SubscriptionStateOutOfCreditsDetectorTest;
+
+  DISALLOW_COPY_AND_ASSIGN(SubscriptionStateOutOfCreditsDetector);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_CELLULAR_SUBSCRIPTION_STATE_OUT_OF_CREDITS_DETECTOR_H_
diff --git a/cellular/subscription_state_out_of_credits_detector_unittest.cc b/cellular/subscription_state_out_of_credits_detector_unittest.cc
new file mode 100644
index 0000000..6720316
--- /dev/null
+++ b/cellular/subscription_state_out_of_credits_detector_unittest.cc
@@ -0,0 +1,90 @@
+// 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/subscription_state_out_of_credits_detector.h"
+
+#include <memory>
+
+#include <gtest/gtest.h>
+#include "ModemManager/ModemManager.h"
+
+#include "shill/cellular/mock_cellular.h"
+#include "shill/cellular/mock_cellular_service.h"
+#include "shill/cellular/mock_modem_info.h"
+#include "shill/event_dispatcher.h"
+#include "shill/mock_connection.h"
+#include "shill/mock_connection_health_checker.h"
+#include "shill/mock_device_info.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_proxy_factory.h"
+#include "shill/mock_traffic_monitor.h"
+
+using testing::Mock;
+using testing::NiceMock;
+
+namespace shill {
+
+class SubscriptionStateOutOfCreditsDetectorTest : public testing::Test {
+ public:
+  SubscriptionStateOutOfCreditsDetectorTest()
+      : modem_info_(nullptr, &dispatcher_, &metrics_, &manager_, nullptr),
+        device_info_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                     modem_info_.metrics(), modem_info_.manager()),
+        manager_(modem_info_.control_interface(), modem_info_.dispatcher(),
+                 modem_info_.metrics(), modem_info_.glib()),
+        metrics_(modem_info_.dispatcher()),
+        cellular_(new NiceMock<MockCellular>(&modem_info_,
+                                             "usb0",
+                                             kAddress,
+                                             3,
+                                             Cellular::kTypeCDMA,
+                                             "",
+                                             "",
+                                             "",
+                                             ProxyFactory::GetInstance())),
+        service_(new NiceMock<MockCellularService>(&modem_info_, cellular_)),
+        connection_(new NiceMock<MockConnection>(&device_info_)),
+        out_of_credits_detector_(
+            new SubscriptionStateOutOfCreditsDetector(
+                modem_info_.dispatcher(), modem_info_.manager(),
+                modem_info_.metrics(), service_)) {}
+
+  virtual void SetUp() {
+    service_->connection_ = connection_;
+    cellular_->service_ = service_;
+    service_->SetRoamingState(kRoamingStateHome);
+  }
+
+  virtual void TearDown() {
+    cellular_->service_ = nullptr;  // Break circular reference.
+  }
+
+ protected:
+  static const char kAddress[];
+
+  EventDispatcher dispatcher_;
+  MockModemInfo modem_info_;
+  NiceMock<MockDeviceInfo> device_info_;
+  NiceMock<MockManager> manager_;
+  NiceMock<MockMetrics> metrics_;
+  scoped_refptr<NiceMock<MockCellular>> cellular_;
+  scoped_refptr<NiceMock<MockCellularService>> service_;
+  scoped_refptr<NiceMock<MockConnection>> connection_;
+  std::unique_ptr<SubscriptionStateOutOfCreditsDetector>
+      out_of_credits_detector_;
+};
+
+const char
+    SubscriptionStateOutOfCreditsDetectorTest::kAddress[] = "000102030405";
+
+TEST_F(SubscriptionStateOutOfCreditsDetectorTest, OutOfCreditsDetection) {
+  out_of_credits_detector_->NotifySubscriptionStateChanged(
+      MM_MODEM_3GPP_SUBSCRIPTION_STATE_OUT_OF_DATA);
+  EXPECT_TRUE(out_of_credits_detector_->out_of_credits());
+  out_of_credits_detector_->NotifySubscriptionStateChanged(
+      MM_MODEM_3GPP_SUBSCRIPTION_STATE_PROVISIONED);
+  EXPECT_FALSE(out_of_credits_detector_->out_of_credits());
+}
+
+}  // namespace shill