普通文本  |  1617行  |  52.52 KB

//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#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>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__

#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_daemon.h"
#include "shill/ppp_device.h"
#include "shill/ppp_device_factory.h"
#include "shill/process_manager.h"
#include "shill/profile.h"
#include "shill/property_accessor.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& service,
                   const string& path)
    : 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_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),
      ppp_device_factory_(PPPDeviceFactory::GetInstance()),
      process_manager_(ProcessManager::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,
                                                  control_interface(),
                                                  modem_info_));
      break;
    case kTypeCDMA:
      capability_.reset(new CellularCapabilityCDMA(this,
                                                   control_interface(),
                                                   modem_info_));
      break;
    case kTypeUniversal:
      capability_.reset(new CellularCapabilityUniversal(
          this,
          control_interface(),
          modem_info_));
      break;
    case kTypeUniversalCDMA:
      capability_.reset(new CellularCapabilityUniversalCDMA(
          this,
          control_interface(),
          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(FROM_HERE, 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(FROM_HERE, error, Error::kAlreadyConnected,
                          "Already connected; connection request ignored.");
    return;
  } else if (state_ != kStateRegistered) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kNotRegistered,
                          "Modem not registered; connection request ignored.");
    return;
  }

  if (!capability_->AllowRoaming() &&
      service_->roaming_state() == kRoamingStateRoaming) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kNotOnHomeNetwork,
                          "Roaming disallowed; connection request ignored.");
    return;
  }

  KeyValueStore 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()->NotifyCellularDeviceConnectionFailure();
    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(
        FROM_HERE, 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()->NotifyCellularDeviceDisconnectionFailure();
    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::OnPropertiesChanged(
    const string& interface,
    const KeyValueStore& changed_properties,
    const vector<string>& invalidated_properties) {
  capability_->OnPropertiesChanged(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 base::StartsWith(service_name, kGenericServiceNamePrefix,
                          base::CompareCase::SENSITIVE);
}

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().
  }

  PPPDaemon::DeathCallback death_callback(Bind(&Cellular::OnPPPDied,
                                               weak_ptr_factory_.GetWeakPtr()));

  PPPDaemon::Options options;
  options.no_detach = true;
  options.no_default_route = true;
  options.use_peer_dns = true;

  is_ppp_authenticating_ = false;

  Error error;
  std::unique_ptr<ExternalTask> new_ppp_task(
      PPPDaemon::Start(modem_info_->control_interface(),
                       process_manager_,
                       weak_ptr_factory_.GetWeakPtr(),
                       options,
                       serial_device,
                       death_callback,
                       &error));
  if (new_ppp_task) {
    LOG(INFO) << "Forked pppd process.";
    ppp_task_ = std::move(new_ppp_task);
  }
}

void Cellular::StopPPP() {
  SLOG(PPP, this, 2) << __func__;
  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