chromium/chrome/browser/ash/mobile/mobile_activator.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/mobile/mobile_activator.h"

#include <algorithm>
#include <map>
#include <string>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/network_activation_handler.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_connect.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler_callbacks.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using content::BrowserThread;

namespace ash {

namespace {

// Number of times we'll try an OTASP before failing the activation process.
const int kMaxOTASPTries = 3;
// Number of times we will retry to reconnect and reload payment portal page.
const int kMaxPortalReconnectCount = 2;
// Time between connection attempts when forcing a reconnect.
const int kReconnectDelayMS = 3000;
// Retry delay after failed OTASP attempt.
const int kOTASPRetryDelay = 40000;
// Maximum amount of time we'll wait for a service to reconnect.
const int kMaxReconnectTime = 30000;

// Returns true if the device follows the simple activation flow.
bool IsSimpleActivationFlow(const NetworkState* network) {
  return (network->activation_type() == shill::kActivationTypeNonCellular ||
          network->activation_type() == shill::kActivationTypeOTA);
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
//
// MobileActivator
//
////////////////////////////////////////////////////////////////////////////////
MobileActivator::MobileActivator()
    : state_(PlanActivationState::kPageLoading),
      terminated_(true),
      connection_retry_count_(0),
      initial_OTASP_attempts_(0),
      trying_OTASP_attempts_(0),
      final_OTASP_attempts_(0),
      payment_reconnect_count_(0) {}

MobileActivator::~MobileActivator() {
  TerminateActivation();
}

MobileActivator* MobileActivator::GetInstance() {
  return base::Singleton<MobileActivator>::get();
}

void MobileActivator::TerminateActivation() {
  state_duration_timer_.Stop();
  continue_reconnect_timer_.Stop();
  reconnect_timeout_timer_.Stop();

  network_state_handler_observer_.Reset();

  meid_.clear();
  iccid_.clear();
  service_path_.clear();
  device_path_.clear();
  state_ = PlanActivationState::kPageLoading;
  terminated_ = true;
}

void MobileActivator::DefaultNetworkChanged(const NetworkState* network) {
  RefreshCellularNetworks();
}

void MobileActivator::NetworkPropertiesUpdated(const NetworkState* network) {
  if (state_ == PlanActivationState::kPageLoading)
    return;

  if (!network || network->type() != shill::kTypeCellular)
    return;

  const DeviceState* device =
      NetworkHandler::Get()->network_state_handler()->GetDeviceState(
          network->device_path());
  if (!device) {
    LOG(ERROR) << "Cellular device can't be found: " << network->device_path();
    return;
  }
  if (network->device_path() != device_path_) {
    LOG(WARNING) << "Ignoring property update for cellular service "
                 << network->path() << " on unknown device "
                 << network->device_path()
                 << " (Stored device path = " << device_path_ << ")";
    return;
  }

  // A modem reset leads to a new service path. Since we have verified that we
  // are a cellular service on a still valid stored device path, update it.
  service_path_ = network->path();

  EvaluateCellularNetwork(network);
}

void MobileActivator::OnShuttingDown() {
  network_state_handler_observer_.Reset();
}

void MobileActivator::AddObserver(MobileActivator::Observer* observer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  observers_.AddObserver(observer);
}

void MobileActivator::RemoveObserver(MobileActivator::Observer* observer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  observers_.RemoveObserver(observer);
}

void MobileActivator::InitiateActivation(const std::string& service_path) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const NetworkState* network = GetNetworkState(service_path);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path;
    return;
  }
  const DeviceState* device =
      NetworkHandler::Get()->network_state_handler()->GetDeviceState(
          network->device_path());
  if (!device) {
    LOG(ERROR) << "Cellular device can't be found: " << network->device_path();
    return;
  }

  DCHECK(network->Matches(NetworkTypePattern::Cellular()));

  terminated_ = false;
  meid_ = device->meid();
  iccid_ = device->iccid();
  service_path_ = service_path;
  device_path_ = network->device_path();

  ChangeState(network, PlanActivationState::kPageLoading,
              ActivationError::kNone);

  // We want shill to connect us after activations, so enable autoconnect.
  base::Value::Dict auto_connect_property;
  auto_connect_property.Set(shill::kAutoConnectProperty, true);
  NetworkHandler::Get()->network_configuration_handler()->SetShillProperties(
      service_path_, std::move(auto_connect_property), base::DoNothing(),
      network_handler::ErrorCallback());

  StartActivation();
}

void MobileActivator::GetPropertiesFailure(const std::string& error_name,
                                           base::Value error_data) {
  NET_LOG(ERROR) << "MobileActivator GetProperties failed for "
                 << NetworkPathId(service_path_) << " Error: " << error_name;
}

void MobileActivator::OnSetTransactionStatus(bool success) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&MobileActivator::HandleSetTransactionStatus,
                                weak_ptr_factory_.GetWeakPtr(), success));
}

void MobileActivator::HandleSetTransactionStatus(bool success) {
  // The payment is received, try to reconnect and check the status all over
  // again.
  if (success && state_ == PlanActivationState::kShowingPayment) {
    SignalCellularPlanPayment();
    const NetworkState* network = GetNetworkState(service_path_);
    if (network && IsSimpleActivationFlow(network)) {
      state_ = PlanActivationState::kDone;
      NetworkHandler::Get()->network_activation_handler()->CompleteActivation(
          network->path(), base::DoNothing(), network_handler::ErrorCallback());
    } else {
      StartOTASP();
    }
  }
}

void MobileActivator::OnPortalLoaded(bool success) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&MobileActivator::HandlePortalLoaded,
                                weak_ptr_factory_.GetWeakPtr(), success));
}

void MobileActivator::HandlePortalLoaded(bool success) {
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    ChangeState(nullptr, PlanActivationState::kError,
                ActivationError::kNoCellularService);
    return;
  }
  if (state_ == PlanActivationState::kPaymentPortalLoading ||
      state_ == PlanActivationState::kShowingPayment) {
    if (success) {
      payment_reconnect_count_ = 0;
      ChangeState(network, PlanActivationState::kShowingPayment,
                  ActivationError::kNone);
    } else {
      // There is no point in forcing reconnecting the cellular network if the
      // activation should not be done over it.
      if (network->activation_type() == shill::kActivationTypeNonCellular)
        return;

      payment_reconnect_count_++;
      if (payment_reconnect_count_ > kMaxPortalReconnectCount) {
        ChangeState(nullptr, PlanActivationState::kError,
                    ActivationError::kNoCellularService);
        return;
      }

      // Reconnect and try and load the frame again.
      ChangeState(network, PlanActivationState::kReconnecting,
                  ActivationError::kNone);
    }
  } else {
    NOTREACHED_IN_MIGRATION()
        << "Called paymentPortalLoad while in unexpected state: "
        << GetStateDescription(state_);
  }
}

void MobileActivator::StartOTASPTimer() {
  state_duration_timer_.Start(FROM_HERE, base::Milliseconds(kOTASPRetryDelay),
                              this, &MobileActivator::HandleOTASPTimeout);
}

void MobileActivator::StartActivation() {
  const NetworkState* network = GetNetworkState(service_path_);
  // Check if we can start activation process.
  if (!network) {
    NetworkStateHandler::TechnologyState technology_state =
        NetworkHandler::Get()->network_state_handler()->GetTechnologyState(
            NetworkTypePattern::Cellular());
    ActivationError error = ActivationError::kActivationFailed;
    if (technology_state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
      error = ActivationError::kNoCellularDevice;
    } else if (technology_state != NetworkStateHandler::TECHNOLOGY_ENABLED) {
      error = ActivationError::kCellularDisabled;
    } else {
      error = ActivationError::kNoCellularService;
    }
    ChangeState(nullptr, PlanActivationState::kError, error);
    return;
  }

  // Start monitoring network property changes.
  network_state_handler_observer_.Observe(
      NetworkHandler::Get()->network_state_handler());

  if (network->activation_type() == shill::kActivationTypeNonCellular) {
    StartActivationOverNonCellularNetwork();
  } else if (network->activation_type() == shill::kActivationTypeOTA) {
    StartActivationOTA();
  } else if (network->activation_type() == shill::kActivationTypeOTASP) {
    StartActivationOTASP();
  }
}

void MobileActivator::StartActivationOverNonCellularNetwork() {
  // Fast forward to payment portal loading.
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  ChangeState(network,
              (network->activation_state() == shill::kActivationStateActivated)
                  ? PlanActivationState::kDone
                  : PlanActivationState::kPaymentPortalLoading,
              ActivationError::kNone);

  RefreshCellularNetworks();
}

void MobileActivator::StartActivationOTA() {
  // Connect to the network if we don't currently have access.
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  const NetworkState* default_network = GetDefaultNetwork();
  bool is_online_or_portal =
      default_network &&
      (default_network->connection_state() == shill::kStateOnline ||
       NetworkState::StateIsPortalled(default_network->connection_state()));
  if (!is_online_or_portal)
    ConnectNetwork(network);

  ChangeState(network, PlanActivationState::kPaymentPortalLoading,
              ActivationError::kNone);
  RefreshCellularNetworks();
}

void MobileActivator::StartActivationOTASP() {
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  if (HasRecentCellularPlanPayment() &&
      (network->activation_state() ==
       shill::kActivationStatePartiallyActivated)) {
    // Try to start with OTASP immediately if we have received payment recently.
    state_ = PlanActivationState::kStartOTASP;
  } else {
    state_ = PlanActivationState::kStart;
  }

  EvaluateCellularNetwork(network);
}

void MobileActivator::RetryOTASP() {
  DCHECK(state_ == PlanActivationState::kDelayOTASP);
  StartOTASP();
}

void MobileActivator::StartOTASP() {
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  ChangeState(network, PlanActivationState::kStartOTASP,
              ActivationError::kNone);
  EvaluateCellularNetwork(network);
}

void MobileActivator::HandleOTASPTimeout() {
  LOG(WARNING) << "OTASP seems to be taking too long.";
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  // We're here because one of OTASP steps is taking too long to complete.
  // Usually, this means something bad has happened below us.
  if (state_ == PlanActivationState::kInitiatingActivation) {
    ++initial_OTASP_attempts_;
    if (initial_OTASP_attempts_ <= kMaxOTASPTries) {
      ChangeState(network, PlanActivationState::kReconnecting,
                  ActivationError::kActivationFailed);
      return;
    }
  } else if (state_ == PlanActivationState::kTryingOTASP) {
    ++trying_OTASP_attempts_;
    if (trying_OTASP_attempts_ <= kMaxOTASPTries) {
      ChangeState(network, PlanActivationState::kReconnecting,
                  ActivationError::kActivationFailed);
      return;
    }
  } else if (state_ == PlanActivationState::kOTASP) {
    ++final_OTASP_attempts_;
    if (final_OTASP_attempts_ <= kMaxOTASPTries) {
      // Give the portal time to propagate all those magic bits.
      ChangeState(network, PlanActivationState::kDelayOTASP,
                  ActivationError::kActivationFailed);
      return;
    }
  } else {
    LOG(ERROR) << "OTASP timed out from a non-OTASP wait state?";
  }
  LOG(ERROR) << "OTASP failed too many times; aborting.";
  ChangeState(network, PlanActivationState::kError,
              ActivationError::kActivationFailed);
}

void MobileActivator::ConnectNetwork(const NetworkState* network) {
  NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
      network->path(), base::DoNothing(), network_handler::ErrorCallback(),
      false /* check_error_state */, ConnectCallbackMode::ON_STARTED);
}

void MobileActivator::ForceReconnect(const NetworkState* network,
                                     PlanActivationState next_state) {
  DCHECK(network);
  // Store away our next destination for when we complete.
  post_reconnect_state_ = next_state;
  UMA_HISTOGRAM_COUNTS_1M("Cellular.ActivationRetry", 1);
  // First, disconnect...
  VLOG(1) << "Disconnecting from " << network->path();
  // Explicit service Disconnect()s disable autoconnect on the service until
  // Connect() is called on the service again.  Hence this dance to explicitly
  // call Connect().
  NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork(
      network->path(), base::DoNothing(), network_handler::ErrorCallback());
  // Keep trying to connect until told otherwise.
  continue_reconnect_timer_.Stop();
  continue_reconnect_timer_.Start(FROM_HERE,
                                  base::Milliseconds(kReconnectDelayMS), this,
                                  &MobileActivator::ContinueConnecting);
  // If we don't ever connect again, we're going to call this a failure.
  reconnect_timeout_timer_.Stop();
  reconnect_timeout_timer_.Start(FROM_HERE,
                                 base::Milliseconds(kMaxReconnectTime), this,
                                 &MobileActivator::ReconnectTimedOut);
}

void MobileActivator::ReconnectTimedOut() {
  LOG(ERROR) << "Ending activation attempt after failing to reconnect.";
  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  ChangeState(network, PlanActivationState::kError,
              ActivationError::kActivationFailed);
}

void MobileActivator::ContinueConnecting() {
  const NetworkState* network = GetNetworkState(service_path_);
  if (network && network->IsConnectedState()) {
    if (NetworkState::StateIsPortalled(network->connection_state()) &&
        network->GetError() == shill::kErrorDNSLookupFailed) {
      // It isn't an error to be in a restricted pool, but if DNS doesn't work,
      // then we're not getting traffic through at all.  Just disconnect and
      // try again.
      NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork(
          network->path(), base::DoNothing(), network_handler::ErrorCallback());
      return;
    }
    // Stop this callback
    continue_reconnect_timer_.Stop();
    EvaluateCellularNetwork(network);
  } else {
    LOG(WARNING) << "Connect failed, will try again in a little bit.";
    if (network) {
      VLOG(1) << "Connecting to: " << network->path();
      NetworkConnect::Get()->ConnectToNetworkId(network->guid());
    }
  }
}

void MobileActivator::RefreshCellularNetworks() {
  if (state_ == PlanActivationState::kPageLoading ||
      state_ == PlanActivationState::kDone ||
      state_ == PlanActivationState::kError) {
    return;
  }

  const NetworkState* network = GetNetworkState(service_path_);
  if (!network) {
    LOG(WARNING) << "Cellular service can't be found: " << service_path_;
    return;
  }

  if (IsSimpleActivationFlow(network)) {
    bool waiting = (state_ == PlanActivationState::kWaitingForConnection);
    // We're only interested in whether or not we have access to the payment
    // portal (regardless of which network we use to access it), so check
    // the default network connection state. The default network is the network
    // used to route default traffic. Also, note that we can access the
    // payment portal over a cellular network in the portalled state.
    const NetworkState* default_network = GetDefaultNetwork();
    bool is_online_or_portal =
        default_network &&
        (default_network->connection_state() == shill::kStateOnline ||
         (default_network->type() == shill::kTypeCellular &&
          NetworkState::StateIsPortalled(default_network->connection_state())));
    if (waiting && is_online_or_portal) {
      ChangeState(network, post_reconnect_state_, ActivationError::kNone);
    } else if (!waiting && !is_online_or_portal) {
      ChangeState(network, PlanActivationState::kWaitingForConnection,
                  ActivationError::kNone);
    }
  }

  EvaluateCellularNetwork(network);
}

const NetworkState* MobileActivator::GetNetworkState(
    const std::string& service_path) {
  return NetworkHandler::Get()->network_state_handler()->GetNetworkState(
      service_path);
}

const NetworkState* MobileActivator::GetDefaultNetwork() {
  return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
}

void MobileActivator::EvaluateCellularNetwork(const NetworkState* network) {
  if (terminated_) {
    LOG(ERROR) << "Tried to run MobileActivator state machine while "
               << "terminated.";
    return;
  }

  if (!network) {
    LOG(WARNING) << "Cellular service lost";
    return;
  }

  LOG(WARNING) << "Cellular:\n  service state=" << network->connection_state()
               << "\n  ui=" << GetStateDescription(state_)
               << "\n  activation=" << network->activation_state()
               << "\n  error=" << network->GetError()
               << "\n  setvice_path=" << network->path()
               << "\n  connected=" << network->IsConnectedState();

  // If the network is activated over non cellular network or OTA, the
  // activator state does not depend on the network's own state.
  if (IsSimpleActivationFlow(network))
    return;

  PlanActivationState new_state = PickNextState(network);
  ActivationError error = new_state == PlanActivationState::kError
                              ? ActivationError::kActivationFailed
                              : ActivationError::kNone;
  ChangeState(network, new_state, error);
}

MobileActivator::PlanActivationState MobileActivator::PickNextState(
    const NetworkState* network) const {
  if (!network->IsConnectedState())
    return PickNextOfflineState(network);
  else
    return PickNextOnlineState(network);
}

MobileActivator::PlanActivationState MobileActivator::PickNextOfflineState(
    const NetworkState* network) const {
  PlanActivationState new_state = state_;
  const std::string& activation = network->activation_state();
  switch (state_) {
    case PlanActivationState::kPaymentPortalLoading:
    case PlanActivationState::kShowingPayment:
      if (!IsSimpleActivationFlow(network))
        new_state = PlanActivationState::kReconnecting;
      break;
    case PlanActivationState::kStart:
      if (activation == shill::kActivationStateActivated) {
        if (NetworkState::StateIsPortalled(network->connection_state()))
          new_state = PlanActivationState::kPaymentPortalLoading;
        else
          new_state = PlanActivationState::kDone;
      } else if (activation == shill::kActivationStatePartiallyActivated) {
        new_state = PlanActivationState::kTryingOTASP;
      } else {
        new_state = PlanActivationState::kInitiatingActivation;
      }
      break;
    default:
      VLOG(1) << "Waiting for cellular service to connect.";
      break;
  }
  return new_state;
}

MobileActivator::PlanActivationState MobileActivator::PickNextOnlineState(
    const NetworkState* network) const {
  PlanActivationState new_state = state_;
  const std::string& activation = network->activation_state();
  switch (state_) {
    case PlanActivationState::kStart:
      if (activation == shill::kActivationStateActivated) {
        if (network->connection_state() == shill::kStateOnline)
          new_state = PlanActivationState::kDone;
        else
          new_state = PlanActivationState::kPaymentPortalLoading;
      } else if (activation == shill::kActivationStatePartiallyActivated) {
        new_state = PlanActivationState::kTryingOTASP;
      } else {
        new_state = PlanActivationState::kInitiatingActivation;
      }
      break;
    case PlanActivationState::kStartOTASP: {
      if (activation == shill::kActivationStatePartiallyActivated) {
        new_state = PlanActivationState::kOTASP;
      } else if (activation == shill::kActivationStateActivated) {
        new_state = PlanActivationState::kReconnecting;
      } else {
        LOG(WARNING) << "Unexpected activation state for device "
                     << network->path();
      }
      break;
    }
    case PlanActivationState::kDelayOTASP:
      // Just ignore any changes until the OTASP retry timer kicks in.
      break;
    case PlanActivationState::kInitiatingActivation: {
      if (activation == shill::kActivationStateActivated ||
          activation == shill::kActivationStatePartiallyActivated) {
        new_state = PlanActivationState::kStart;
      } else if (activation == shill::kActivationStateNotActivated ||
                 activation == shill::kActivationStateActivating) {
        // Wait in this state until activation state changes.
      } else {
        LOG(WARNING) << "Unknown transition";
      }
      break;
    }
    case PlanActivationState::kOTASP:
    case PlanActivationState::kTryingOTASP:
      if (activation == shill::kActivationStateNotActivated ||
          activation == shill::kActivationStateActivating) {
        VLOG(1) << "Waiting for the OTASP to finish and the service to "
                << "come back online";
      } else if (activation == shill::kActivationStateActivated) {
        new_state = PlanActivationState::kDone;
      } else {
        new_state = PlanActivationState::kPaymentPortalLoading;
      }
      break;
    case PlanActivationState::kReconnectingPayment:
      if (!NetworkState::StateIsPortalled(network->connection_state()) &&
          activation == shill::kActivationStateActivated)
        // We're not portalled, and we're already activated, so we're online!
        new_state = PlanActivationState::kDone;
      else
        new_state = PlanActivationState::kPaymentPortalLoading;
      break;
    // Initial state
    case PlanActivationState::kPageLoading:
      break;
    // Just ignore all signals until the site confirms payment.
    case PlanActivationState::kPaymentPortalLoading:
    case PlanActivationState::kShowingPayment:
    case PlanActivationState::kWaitingForConnection:
      break;
    // Go where we decided earlier.
    case PlanActivationState::kReconnecting:
      new_state = post_reconnect_state_;
      break;
    // Activation completed/failed, ignore network changes.
    case PlanActivationState::kDone:
    case PlanActivationState::kError:
      break;
  }

  return new_state;
}

// Debugging helper function, will take it out at the end.
const char* MobileActivator::GetStateDescription(PlanActivationState state) {
  switch (state) {
    case PlanActivationState::kPageLoading:
      return "PAGE_LOADING";
    case PlanActivationState::kStart:
      return "ACTIVATION_START";
    case PlanActivationState::kInitiatingActivation:
      return "INITIATING_ACTIVATION";
    case PlanActivationState::kTryingOTASP:
      return "TRYING_OTASP";
    case PlanActivationState::kPaymentPortalLoading:
      return "PAYMENT_PORTAL_LOADING";
    case PlanActivationState::kShowingPayment:
      return "SHOWING_PAYMENT";
    case PlanActivationState::kReconnectingPayment:
      return "RECONNECTING_PAYMENT";
    case PlanActivationState::kDelayOTASP:
      return "DELAY_OTASP";
    case PlanActivationState::kStartOTASP:
      return "START_OTASP";
    case PlanActivationState::kOTASP:
      return "OTASP";
    case PlanActivationState::kDone:
      return "DONE";
    case PlanActivationState::kError:
      return "ERROR";
    case PlanActivationState::kReconnecting:
      return "RECONNECTING";
    case PlanActivationState::kWaitingForConnection:
      return "WAITING FOR CONNECTION";
  }
  return "UNKNOWN";
}

void MobileActivator::CompleteActivation() {
  // Remove observers, we are done with this page.
  network_state_handler_observer_.Reset();
}

bool MobileActivator::RunningActivation() const {
  return !(state_ == PlanActivationState::kDone ||
           state_ == PlanActivationState::kError ||
           state_ == PlanActivationState::kPageLoading);
}

void MobileActivator::ChangeState(const NetworkState* network,
                                  PlanActivationState new_state,
                                  ActivationError error) {
  // Report an error, by transitioning into a kError state with
  // a "no service" error instead, if no network state is available (e.g. the
  // cellular service no longer exists) when we are transitioning into certain
  // plan activation state.
  if (!network) {
    switch (new_state) {
      case PlanActivationState::kInitiatingActivation:
      case PlanActivationState::kTryingOTASP:
      case PlanActivationState::kOTASP:
      case PlanActivationState::kDone:
        new_state = PlanActivationState::kError;
        error = ActivationError::kNoCellularService;
        break;
      default:
        break;
    }
  }

  static bool first_time = true;
  VLOG(1) << "Activation state flip old = " << GetStateDescription(state_)
          << ", new = " << GetStateDescription(new_state);
  if (state_ == new_state && !first_time)
    return;
  first_time = false;
  VLOG(1) << "Transitioning...";

  // Kill all the possible timers and callbacks we might have outstanding.
  state_duration_timer_.Stop();
  continue_reconnect_timer_.Stop();
  reconnect_timeout_timer_.Stop();
  const PlanActivationState old_state = state_;
  state_ = new_state;

  // Signal to observers layer that the state is changing.
  for (auto& observer : observers_)
    observer.OnActivationStateChanged(network, state_, error);

  // Pick action that should happen on entering the new state.
  switch (new_state) {
    case PlanActivationState::kStart:
      break;
    case PlanActivationState::kDelayOTASP: {
      UMA_HISTOGRAM_COUNTS_1M("Cellular.RetryOTASP", 1);
      content::GetUIThreadTaskRunner({})->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&MobileActivator::RetryOTASP,
                         weak_ptr_factory_.GetWeakPtr()),
          base::Milliseconds(kOTASPRetryDelay));
      break;
    }
    case PlanActivationState::kStartOTASP:
      break;
    case PlanActivationState::kInitiatingActivation:
    case PlanActivationState::kTryingOTASP:
    case PlanActivationState::kOTASP:
      // Starts the timer waiting for activation state changes.
      // https://crbug.com/1021688.
      StartOTASPTimer();
      break;
    case PlanActivationState::kPageLoading:
      return;
    case PlanActivationState::kPaymentPortalLoading:
    case PlanActivationState::kShowingPayment:
    case PlanActivationState::kReconnectingPayment:
      // Fix for fix SSL for the walled gardens where cert chain verification
      // might not work.
      break;
    case PlanActivationState::kWaitingForConnection:
      post_reconnect_state_ = old_state;
      break;
    case PlanActivationState::kReconnecting: {
      PlanActivationState next_state = old_state;
      // Pick where we want to return to after we reconnect.
      switch (old_state) {
        case PlanActivationState::kPaymentPortalLoading:
        case PlanActivationState::kShowingPayment:
          // We decide here what to do next based on the state of the modem.
          next_state = PlanActivationState::kReconnectingPayment;
          break;
        case PlanActivationState::kInitiatingActivation:
        case PlanActivationState::kTryingOTASP:
          next_state = PlanActivationState::kStart;
          break;
        case PlanActivationState::kStartOTASP:
        case PlanActivationState::kOTASP:
          if (!network || !network->IsConnectedState()) {
            next_state = PlanActivationState::kStartOTASP;
          } else {
            // We're online, which means we've conspired with
            // PickNextOnlineState to reconnect after activation (that's the
            // only way we see this transition).  Thus, after we reconnect, we
            // should be done.
            next_state = PlanActivationState::kDone;
          }
          break;
        default:
          LOG(ERROR) << "Transitioned to RECONNECTING from an unexpected "
                     << "state.";
          break;
      }
      if (network)
        ForceReconnect(network, next_state);
      break;
    }
    case PlanActivationState::kDone:
      DCHECK(network);
      CompleteActivation();
      break;
    case PlanActivationState::kError:
      CompleteActivation();
      UMA_HISTOGRAM_COUNTS_1M("Cellular.PlanFailed", 1);
      break;
    default:
      break;
  }
}

void MobileActivator::SignalCellularPlanPayment() {
  DCHECK(!HasRecentCellularPlanPayment());
  cellular_plan_payment_time_ = base::Time::Now();
}

bool MobileActivator::HasRecentCellularPlanPayment() const {
  const int kRecentPlanPaymentHours = 6;
  return (base::Time::Now() - cellular_plan_payment_time_).InHours() <
         kRecentPlanPaymentHours;
}

}  // namespace ash