shill: cellular: Implement CDMA activation over non-cellular.

Added OTASP activation logic over a non-cellular network to
CellularCapabilityUniversalCDMA.

BUG=chromium:221698,chromium:217324
TEST=1. Build and run unit tests.
     2. - Acquire an unactivated Gobi3k (this can be achieved on an
          activated modem by cancelling the account and factory
          resetting the modem).
        - Reimage the Chromebook (with the above Gobi3k) with an image
          which was compiled with 'USE="-gobi qmi"'.
        - If the carrier is not already set to Verizon Wireless, switch
          firmware to the Verizon firmware.
        - Verify that the modem supported by ModemManager and not cromo. The
          modem properties should be exposed and the CDMA
          ActivationState property should be 'not-activated'. All of this can
          be verified using mmcli.
        - The Chrome OS UI should display the cellular network as 'not
          activated'. Use the UI to activate the service.
        - Once payment is done, service should be marked as
          "Activating". /var/cache/shill/activating_iccid_store.profile
          should have a new entry under "[meid_list]" set to "1".
        - Monitor /var/log/net.log (with a debug scope of at least
          "cellular" and level "-3"). Cellular activation related
          messages should be logged. ModemManager should be in the
          process of "automatic activation" and logging appropriate
          updates.
        - The modem should eventually reset. Afterwards the UI should
          pop-up a "3G network activated" dialog.

Change-Id: Iff9d1b88012dad1d3552af12d9ae7f510a988a54
Reviewed-on: https://gerrit.chromium.org/gerrit/49696
Reviewed-by: Arman Uguray <armansito@chromium.org>
Commit-Queue: Arman Uguray <armansito@chromium.org>
Tested-by: Arman Uguray <armansito@chromium.org>
diff --git a/cellular_capability_universal_cdma.cc b/cellular_capability_universal_cdma.cc
index 69e9bf1..8fb4d2f 100644
--- a/cellular_capability_universal_cdma.cc
+++ b/cellular_capability_universal_cdma.cc
@@ -11,7 +11,9 @@
 
 #include "shill/cellular_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/proxy_factory.h"
 
 #ifdef MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
@@ -44,14 +46,14 @@
     : 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(Cellular, 2) << "Cellular capability constructed: Universal CDMA";
-  // TODO(armansito): Need to expose PRL version here to carry through
-  // activation. PRL version is not obtainable from ModemManager and will
-  // need to be stored in a special place (such as CellularOperatorInfo).
+  // TODO(armansito): Update PRL for activation over cellular.
   // See crbug.com/197330.
 }
 
@@ -60,6 +62,9 @@
   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();
 }
 
@@ -73,34 +78,137 @@
     const string &carrier,
     Error *error,
     const ResultCallback &callback) {
-  // TODO(armansito): Implement activation.
+  // 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) {
-  // TODO(armansito): Implement activation.
-  OnUnsupportedOperation(__func__, error);
+  SLOG(Cellular, 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::DisconnectCleanup() {
+void CellularCapabilityUniversalCDMA::ActivateAutomatic() {
+  if (activation_code_.empty()) {
+    SLOG(Cellular, 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, meid());
+  if (state == PendingActivationStore::kStatePending) {
+    SLOG(Cellular, 2) << "There's already a pending activation. Ignoring.";
+    return;
+  }
+  if (state == PendingActivationStore::kStateActivated) {
+    SLOG(Cellular, 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,
+      meid(),
+      PendingActivationStore::kStatePending);
+
+  // Initiate OTA activation.
+  ResultCallback activation_callback =
+    Bind(&CellularCapabilityUniversalCDMA::OnActivateReply,
+         weak_cdma_ptr_factory_.GetWeakPtr(),
+         ResultCallback());
+  // TODO(armansito): Read the activation code from CellularOperatorInfo
+  Error error;
+  modem_cdma_proxy_->Activate(
+      activation_code_, &error, activation_callback, kTimeoutActivate);
+}
+
+void CellularCapabilityUniversalCDMA::UpdatePendingActivationState() {
   SLOG(Cellular, 2) << __func__;
-  // TODO(armansito): Handle activation logic here.
+  if (IsActivated()) {
+    SLOG(Cellular, 3) << "CDMA service activated. Clear store.";
+    modem_info()->pending_activation_store()->RemoveEntry(
+        PendingActivationStore::kIdentifierMEID, meid());
+    return;
+  }
+  PendingActivationStore::State state =
+      modem_info()->pending_activation_store()->GetActivationState(
+          PendingActivationStore::kIdentifierMEID, meid());
+  if (IsActivating() && state != PendingActivationStore::kStateFailureRetry) {
+    SLOG(Cellular, 3) << "OTA activation in progress. Nothing to do.";
+    return;
+  }
+  switch (state) {
+    case PendingActivationStore::kStateFailureRetry:
+      SLOG(Cellular, 3) << "OTA activation failed. Scheduling a retry.";
+      cellular()->dispatcher()->PostTask(
+          Bind(&CellularCapabilityUniversalCDMA::ActivateAutomatic,
+               weak_cdma_ptr_factory_.GetWeakPtr()));
+      break;
+    case PendingActivationStore::kStateActivated:
+      SLOG(Cellular, 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 (!modem_info()->cellular_operator_info())
+    return false;
+
+  const CellularService::OLP *olp =
+      modem_info()->cellular_operator_info()->GetOLPBySID(UintToString(sid_));
+  if (!olp)
+    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(Cellular, 2) << __func__;
   // TODO (armansito): Set storage identifier here  based on the superclass
   // implementation.
-  bool activation_required = IsServiceActivationRequired();
-  cellular()->service()->SetActivationState(
-      activation_required ?
-      flimflam::kActivationStateNotActivated :
-      flimflam::kActivationStateActivated);
-  cellular()->service()->SetActivateOverNonCellularNetwork(activation_required);
+  UpdateServiceActivationStateProperty();
   UpdateServingOperator();
+  HandleNewActivationStatus(MM_CDMA_ACTIVATION_ERROR_NONE);
+  UpdatePendingActivationState();
   UpdateOLP();
 }
 
+void CellularCapabilityUniversalCDMA::UpdateServiceActivationStateProperty() {
+  bool activation_required = IsServiceActivationRequired();
+  cellular()->service()->SetActivateOverNonCellularNetwork(activation_required);
+  string activation_state;
+  if (IsActivating())
+      activation_state = flimflam::kActivationStateActivating;
+  else if (activation_required)
+      activation_state = flimflam::kActivationStateNotActivated;
+  else
+      activation_state = flimflam::kActivationStateActivated;
+  cellular()->service()->SetActivationState(activation_state);
+}
+
 void CellularCapabilityUniversalCDMA::UpdateOLP() {
   SLOG(Cellular,2) << __func__;
   if (!modem_info()->cellular_operator_info())
@@ -119,7 +227,8 @@
   ReplaceSubstringsAfterOffset(&post_data, 0, "${mdn}", mdn());
   ReplaceSubstringsAfterOffset(&post_data, 0, "${meid}", meid());
   olp.SetPostData(post_data);
-  cellular()->service()->SetOLP(olp);
+  if (cellular()->service().get())
+    cellular()->service()->SetOLP(olp);
 }
 
 void CellularCapabilityUniversalCDMA::GetProperties() {
@@ -181,6 +290,8 @@
   provider_.SetCode(sid);
   provider_.SetCountry(provider->country());
 
+  activation_code_ = provider->activation_code();
+
   // TODO(armansito): The CDMA interface only returns information about the
   // current serving carrier, so for now both the home provider and the
   // serving operator will be the same in case of roaming. We should figure
@@ -198,6 +309,99 @@
   }
 }
 
+void CellularCapabilityUniversalCDMA::OnActivationStateChangedSignal(
+    uint32 activation_state,
+    uint32 activation_error,
+    const DBusPropertiesMap &status_changes) {
+  SLOG(Cellular, 2) << __func__;
+
+  activation_state_ =
+      static_cast<MMModemCdmaActivationState>(activation_state);
+
+  string value;
+  if (DBusProperties::GetString(status_changes, "mdn", &value))
+    set_mdn(value);
+  if (DBusProperties::GetString(status_changes, "min", &value))
+    set_min(value);
+
+  SLOG(Cellular, 2) << "Activation state: "
+                    << GetActivationStateString(activation_state_);
+
+  HandleNewActivationStatus(activation_error);
+  UpdatePendingActivationState();
+}
+
+void CellularCapabilityUniversalCDMA::OnActivateReply(
+    const ResultCallback &callback,
+    const Error &error) {
+  SLOG(Cellular, 2) << __func__;
+  if (error.IsSuccess()) {
+    LOG(INFO) << "Activation completed successfully.";
+    modem_info()->pending_activation_store()->SetActivationState(
+        PendingActivationStore::kIdentifierMEID,
+        meid(),
+        PendingActivationStore::kStateActivated);
+  } else {
+    LOG(ERROR) << "Activation failed with error: " << error;
+    modem_info()->pending_activation_store()->SetActivationState(
+        PendingActivationStore::kIdentifierMEID,
+        meid(),
+        PendingActivationStore::kStateFailureRetry);
+  }
+  UpdatePendingActivationState();
+  callback.Run(error);
+}
+
+void CellularCapabilityUniversalCDMA::HandleNewActivationStatus(uint32 error) {
+  SLOG(Cellular, 2) << __func__ << "(" << error << ")";
+  if (!cellular()->service().get()) {
+    LOG(ERROR) << "In " << __func__ << "(): service is null.";
+    return;
+  }
+  SLOG(Cellular, 2) << "Activation State: " << activation_state_;
+  cellular()->service()->SetActivationState(
+      GetActivationStateString(activation_state_));
+  cellular()->service()->set_error(GetActivationErrorString(error));
+  UpdateOLP();
+}
+
+// static
+string CellularCapabilityUniversalCDMA::GetActivationStateString(
+    uint32 state) {
+  switch (state) {
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATED:
+      return flimflam::kActivationStateActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING:
+      return flimflam::kActivationStateActivating;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED:
+      return flimflam::kActivationStateNotActivated;
+    case MM_MODEM_CDMA_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
+      return flimflam::kActivationStatePartiallyActivated;
+    default:
+      return flimflam::kActivationStateUnknown;
+  }
+}
+
+// static
+string CellularCapabilityUniversalCDMA::GetActivationErrorString(
+    uint32 error) {
+  switch (error) {
+    case MM_CDMA_ACTIVATION_ERROR_WRONG_RADIO_INTERFACE:
+      return flimflam::kErrorNeedEvdo;
+    case MM_CDMA_ACTIVATION_ERROR_ROAMING:
+      return flimflam::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 flimflam::kErrorOtaspFailed;
+    case MM_CDMA_ACTIVATION_ERROR_NONE:
+      return "";
+    case MM_CDMA_ACTIVATION_ERROR_NO_SIGNAL:
+    default:
+      return flimflam::kErrorActivationFailed;
+  }
+}
+
 void CellularCapabilityUniversalCDMA::Register(const ResultCallback &callback) {
   // TODO(armansito): Remove once 3GPP is implemented in its own class.
 }
@@ -209,6 +413,15 @@
   // 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, meid());
+  return (state == PendingActivationStore::kStatePending) ||
+      (state == PendingActivationStore::kStateFailureRetry) ||
+      (activation_state_ == MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING);
+}
+
 bool CellularCapabilityUniversalCDMA::IsRegistered() {
   return (cdma_1x_registration_state_ ==
               MM_MODEM_CDMA_REGISTRATION_STATE_HOME ||
@@ -342,6 +555,13 @@
     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);
 }