1616 lines
52 KiB
C++
1616 lines
52 KiB
C++
//
|
|
// 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
|