shill: Support Cellular CDMA activation.

Still TODO is communicating the activation error codes to the Cellular service.
... And, of course, asynchronous calls throughout.

BUG=chromium-os:19305
TEST=unit tests

Change-Id: I3e0b4a5a8d3c4c74db35ce7f74b5d958677d8b66
Reviewed-on: http://gerrit.chromium.org/gerrit/6192
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index 1430552..989ab75 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -34,6 +34,12 @@
 
 namespace shill {
 
+const char Cellular::kActivationStateActivated[] = "activated";
+const char Cellular::kActivationStateActivating[] = "activating";
+const char Cellular::kActivationStateNotActivated[] = "not-activated";
+const char Cellular::kActivationStatePartiallyActivated[] =
+    "partially-activated";
+const char Cellular::kActivationStateUnknown[] = "unknown";
 const char Cellular::kConnectPropertyPhoneNumber[] = "number";
 const char Cellular::kPhoneNumberCDMA[] = "#777";
 const char Cellular::kPhoneNumberGSM[] = "*99#";
@@ -176,6 +182,22 @@
   return StringPrintf("CellularStateUnknown-%d", state);
 }
 
+// static
+string Cellular::GetCDMAActivationStateString(uint32 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;
+  }
+}
+
 void Cellular::SetState(State state) {
   VLOG(2) << GetStateString(state_) << " -> " << GetStateString(state);
   state_ = state;
@@ -257,11 +279,12 @@
   DBusProperties::GetString(
       properties, "firmware_revision", &firmware_revision_);
 
+  // TODO(petkov): Handle "state".
 
   if (type_ == kTypeCDMA) {
-    // TODO(petkov): Get activation_state.
+    DBusProperties::GetUint32(
+        properties, "activation_state", &cdma_.activation_state);
     DBusProperties::GetUint16(properties, "prl_version", &cdma_.prl_version);
-
     // TODO(petkov): For now, get the payment and usage URLs from ModemManager
     // to match flimflam. In the future, provide a plugin API to get these
     // directly from the modem driver.
@@ -338,6 +361,9 @@
 void Cellular::HandleNewRegistrationStateTask() {
   VLOG(2) << __func__;
   if (!IsModemRegistered()) {
+    if (state_ == kStateLinked) {
+      manager_->DeregisterService(service_);
+    }
     service_ = NULL;
     if (state_ == kStateLinked ||
         state_ == kStateConnected ||
@@ -401,11 +427,18 @@
   CHECK(!service_.get());
   service_ =
       new CellularService(control_interface_, dispatcher_, manager_, this);
-  if (type_ == kTypeCDMA) {
-    service_->set_payment_url(cdma_.payment_url);
-    service_->set_usage_url(cdma_.usage_url);
+  switch (type_) {
+    case kTypeGSM:
+      service_->set_activation_state(kActivationStateActivated);
+      break;
+    case kTypeCDMA:
+      service_->set_payment_url(cdma_.payment_url);
+      service_->set_usage_url(cdma_.usage_url);
+      HandleNewCDMAActivationState(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR);
+      break;
+    default:
+      NOTREACHED();
   }
-  // TODO(petkov): Set activation_state.
   // TODO(petkov): Set operator.
 }
 
@@ -476,6 +509,36 @@
   }
 }
 
+void Cellular::Activate(const string &carrier) {
+  CHECK_EQ(kTypeCDMA, type_);
+  // Defer connect because we may be in a dbus-c++ callback.
+  dispatcher_->PostTask(
+      task_factory_.NewRunnableMethod(&Cellular::ActivateTask, carrier));
+}
+
+void Cellular::ActivateTask(const string &carrier) {
+  VLOG(2) << __func__ << "(" << carrier << ")";
+  if (state_ != kStateEnabled && state_ != kStateRegistered) {
+    LOG(ERROR) << "Unable to activate in " << GetStateString(state_);
+    return;
+  }
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  uint32 status = cdma_proxy_->Activate(carrier);
+  if (status == MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR) {
+    cdma_.activation_state = MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING;
+  }
+  HandleNewCDMAActivationState(status);
+}
+
+void Cellular::HandleNewCDMAActivationState(uint32 error) {
+  if (!service_.get()) {
+    return;
+  }
+  service_->set_activation_state(
+      GetCDMAActivationStateString(cdma_.activation_state));
+  // TODO(petkov): Handle activation state error codes.
+}
+
 void Cellular::OnCDMAActivationStateChanged(
     uint32 activation_state,
     uint32 activation_error,
@@ -488,7 +551,8 @@
       service_.get()) {
     service_->set_payment_url(cdma_.payment_url);
   }
-  // TODO(petkov): Handle activation state updates.
+  cdma_.activation_state = activation_state;
+  HandleNewCDMAActivationState(activation_error);
 }
 
 void Cellular::OnCDMARegistrationStateChanged(uint32 state_1x,
diff --git a/cellular.h b/cellular.h
index 7e3baec..90b915e 100644
--- a/cellular.h
+++ b/cellular.h
@@ -128,6 +128,9 @@
   // Asynchronously connects the modem to the network.
   void Connect();
 
+  // Asynchronously activates the modem.
+  void Activate(const std::string &carrier);
+
   void set_modem_state(ModemState state) { modem_state_ = state; }
   ModemState modem_state() const { return modem_state_; }
 
@@ -138,7 +141,9 @@
   virtual void LinkEvent(unsigned int flags, unsigned int change);
 
  private:
+  FRIEND_TEST(CellularTest, Activate);
   FRIEND_TEST(CellularTest, Connect);
+  FRIEND_TEST(CellularTest, GetCDMAActivationStateString);
   FRIEND_TEST(CellularTest, GetCDMARegistrationState);
   FRIEND_TEST(CellularTest, GetCDMASignalQuality);
   FRIEND_TEST(CellularTest, GetModemInfo);
@@ -152,12 +157,18 @@
   FRIEND_TEST(CellularTest, StartLinked);
   FRIEND_TEST(CellularTest, StartRegister);
 
+  static const char kActivationStateActivated[];
+  static const char kActivationStateActivating[];
+  static const char kActivationStateNotActivated[];
+  static const char kActivationStatePartiallyActivated[];
+  static const char kActivationStateUnknown[];
   static const char kPhoneNumberCDMA[];
   static const char kPhoneNumberGSM[];
 
   void SetState(State state);
 
   void ConnectTask(const DBusPropertiesMap &properties);
+  void ActivateTask(const std::string &carrier);
 
   // Invoked when the modem is connected to the cellular network to transition
   // to the network-connected state and bring the network interface up.
@@ -177,6 +188,8 @@
   std::string GetTypeString() const;
   static std::string GetStateString(State state);
 
+  static std::string GetCDMAActivationStateString(uint32 state);
+
   void EnableModem();
   void GetModemStatus();
   void GetGSMProperties();
@@ -206,6 +219,8 @@
 
   void HandleNewSignalQuality(uint32 strength);
 
+  void HandleNewCDMAActivationState(uint32 error);
+
   // Returns true if the modem is registered. Note that this method looks at the
   // latest CDMA/GSM registration info obtained from the modem rather than the
   // device |state_|.
diff --git a/cellular_service.cc b/cellular_service.cc
index 654c5b4..f0cb2ab 100644
--- a/cellular_service.cc
+++ b/cellular_service.cc
@@ -51,7 +51,11 @@
 
 void CellularService::Disconnect() { }
 
-std::string CellularService::GetDeviceRpcId() {
+void CellularService::ActivateCellularModem(const string &carrier) {
+  cellular_->Activate(carrier);
+}
+
+string CellularService::GetDeviceRpcId() {
   return cellular_->GetRpcIdentifier();
 }
 
diff --git a/cellular_service.h b/cellular_service.h
index 1c75358..a16b1bb 100644
--- a/cellular_service.h
+++ b/cellular_service.h
@@ -27,8 +27,15 @@
                   const CellularRefPtr &device);
   virtual ~CellularService();
 
+  // Inherited from Service.
   virtual void Connect();
   virtual void Disconnect();
+  virtual void ActivateCellularModem(const std::string &carrier);
+
+  const std::string &activation_state() const { return activation_state_; }
+  void set_activation_state(const std::string &state) {
+    activation_state_ = state;
+  }
 
   uint8 strength() const { return strength_; }
   void set_strength(uint8 strength) { strength_ = strength; }
diff --git a/cellular_unittest.cc b/cellular_unittest.cc
index 8d551a7..4fb5a2b 100644
--- a/cellular_unittest.cc
+++ b/cellular_unittest.cc
@@ -229,6 +229,22 @@
             device_->GetStateString(Cellular::kStateLinked));
 }
 
+TEST_F(CellularTest, GetCDMAActivationStateString) {
+  EXPECT_EQ("activated",
+            device_->GetCDMAActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED));
+  EXPECT_EQ("activating",
+            device_->GetCDMAActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING));
+  EXPECT_EQ("not-activated",
+            device_->GetCDMAActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED));
+  EXPECT_EQ("partially-activated",
+            device_->GetCDMAActivationStateString(
+                MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED));
+  EXPECT_EQ("unknown", device_->GetCDMAActivationStateString(123));
+}
+
 TEST_F(CellularTest, Start) {
   EXPECT_CALL(*proxy_, Enable(true)).Times(1);
   EXPECT_CALL(*simple_proxy_, GetStatus())
@@ -409,4 +425,17 @@
   dispatcher_.DispatchPendingEvents();
 }
 
+TEST_F(CellularTest, Activate) {
+  static const char kCarrier[] = "The Cellular Carrier";
+  device_->type_ = Cellular::kTypeCDMA;
+  EXPECT_CALL(*cdma_proxy_, Activate(kCarrier))
+      .WillOnce(Return(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR));
+  device_->Activate(kCarrier);
+  device_->cdma_proxy_.reset(cdma_proxy_.release());
+  device_->state_ = Cellular::kStateEnabled;
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_EQ(MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING,
+            device_->cdma_.activation_state);
+}
+
 }  // namespace shill
diff --git a/mock_modem_cdma_proxy.h b/mock_modem_cdma_proxy.h
index fdd9d93..59b1a4c 100644
--- a/mock_modem_cdma_proxy.h
+++ b/mock_modem_cdma_proxy.h
@@ -13,6 +13,7 @@
 
 class MockModemCDMAProxy : public ModemCDMAProxyInterface {
  public:
+  MOCK_METHOD1(Activate, uint32(const std::string &carrier));
   MOCK_METHOD2(GetRegistrationState, void(uint32 *cdma_1x_state,
                                           uint32 *evdo_state));
   MOCK_METHOD0(GetSignalQuality, uint32());
diff --git a/modem_cdma_proxy.cc b/modem_cdma_proxy.cc
index 4628fb8..4a92f88 100644
--- a/modem_cdma_proxy.cc
+++ b/modem_cdma_proxy.cc
@@ -18,6 +18,10 @@
 
 ModemCDMAProxy::~ModemCDMAProxy() {}
 
+uint32 ModemCDMAProxy::Activate(const string &carrier) {
+  return proxy_.Activate(carrier);
+}
+
 void ModemCDMAProxy::GetRegistrationState(uint32 *cdma_1x_state,
                                           uint32 *evdo_state) {
   proxy_.GetRegistrationState(*cdma_1x_state, *evdo_state);
diff --git a/modem_cdma_proxy.h b/modem_cdma_proxy.h
index 33d021f..a116d9d 100644
--- a/modem_cdma_proxy.h
+++ b/modem_cdma_proxy.h
@@ -22,6 +22,7 @@
   virtual ~ModemCDMAProxy();
 
   // Inherited from ModemCDMAProxyInterface.
+  virtual uint32 Activate(const std::string &carrier);
   virtual void GetRegistrationState(uint32 *cdma_1x_state, uint32 *evdo_state);
   virtual uint32 GetSignalQuality();
 
diff --git a/modem_cdma_proxy_interface.h b/modem_cdma_proxy_interface.h
index 9a91f3e..7a333a9 100644
--- a/modem_cdma_proxy_interface.h
+++ b/modem_cdma_proxy_interface.h
@@ -5,6 +5,8 @@
 #ifndef SHILL_MODEM_CDMA_PROXY_INTERFACE_
 #define SHILL_MODEM_CDMA_PROXY_INTERFACE_
 
+#include <string>
+
 #include <base/basictypes.h>
 
 namespace shill {
@@ -15,9 +17,9 @@
  public:
   virtual ~ModemCDMAProxyInterface() {}
 
+  virtual uint32 Activate(const std::string &carrier) = 0;
   virtual void GetRegistrationState(uint32 *cdma_1x_state,
                                     uint32 *evdo_state) = 0;
-
   virtual uint32 GetSignalQuality() = 0;
 };
 
diff --git a/service.cc b/service.cc
index e0c8916..373e9b3 100644
--- a/service.cc
+++ b/service.cc
@@ -148,6 +148,10 @@
 
 Service::~Service() {}
 
+void Service::ActivateCellularModem(const std::string &carrier) {
+  NOTREACHED() << "Attempt to activate a non-cellular service?";
+}
+
 string Service::GetRpcIdentifier() const {
   return adaptor_->GetRpcIdentifier();
 }
diff --git a/service.h b/service.h
index 16bade6..be7fc0a 100644
--- a/service.h
+++ b/service.h
@@ -90,6 +90,9 @@
   virtual void Connect() = 0;
   virtual void Disconnect() = 0;
 
+  // The default implementation asserts.
+  virtual void ActivateCellularModem(const std::string &carrier);
+
   virtual bool IsActive() { return false; }
 
   // Returns a string that is guaranteed to uniquely identify this Service
diff --git a/service_dbus_adaptor.cc b/service_dbus_adaptor.cc
index d6ab4fb..1b3f644 100644
--- a/service_dbus_adaptor.cc
+++ b/service_dbus_adaptor.cc
@@ -85,8 +85,9 @@
                                    ::DBus::Error &error) {
 }
 
-void ServiceDBusAdaptor::ActivateCellularModem(const string& ,
+void ServiceDBusAdaptor::ActivateCellularModem(const string &carrier,
                                                ::DBus::Error &error) {
+  service_->ActivateCellularModem(carrier);
 }
 
 }  // namespace shill
diff --git a/service_dbus_adaptor.h b/service_dbus_adaptor.h
index a637112..51b9a31 100644
--- a/service_dbus_adaptor.h
+++ b/service_dbus_adaptor.h
@@ -52,10 +52,11 @@
   void Remove(::DBus::Error &error);
   void MoveBefore(const ::DBus::Path& , ::DBus::Error &error);
   void MoveAfter(const ::DBus::Path& , ::DBus::Error &error);
-  void ActivateCellularModem(const std::string& , ::DBus::Error &error);
+  void ActivateCellularModem(const std::string &carrier, ::DBus::Error &error);
 
  private:
   Service *service_;
+
   DISALLOW_COPY_AND_ASSIGN(ServiceDBusAdaptor);
 };