blob: a9a4d5741068030bbea7265b5371649c63592f94 [file] [log] [blame]
//
// 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(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(benchan): Use a simpler scheme for generating the storage identifier
// once we no longer depend on it to locate a profile.
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(
FROM_HERE,
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);
Stringmaps apn_list_dict;
for (const auto& mobile_apn : operator_info->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