blob: 4452cf8ad76710e68023c587d2afdb9a37fe3084 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/cellular/cellular_capability_3gpp.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <base/bind.h>
#include <base/files/file.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <chromeos/dbus/service_constants.h>
#include <ModemManager/ModemManager.h>
#include <string>
#include <vector>
#include "shill/adaptor_interfaces.h"
#include "shill/cellular/cellular_bearer.h"
#include "shill/cellular/cellular_pco.h"
#include "shill/cellular/cellular_service.h"
#include "shill/cellular/mobile_operator_info.h"
#include "shill/cellular/modem_info.h"
#include "shill/cellular/pending_activation_store.h"
#include "shill/cellular/verizon_subscription_state.h"
#include "shill/control_interface.h"
#include "shill/dbus/dbus_properties_proxy.h"
#include "shill/device_id.h"
#include "shill/error.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/property_accessor.h"
using base::Bind;
using base::Closure;
using std::string;
using std::vector;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kCellular;
static string ObjectID(const CellularCapability3gpp* c) {
return c->cellular()->GetRpcIdentifier().value();
}
} // namespace Logging
// static
const char CellularCapability3gpp::kConnectApn[] = "apn";
const char CellularCapability3gpp::kConnectUser[] = "user";
const char CellularCapability3gpp::kConnectPassword[] = "password";
const char CellularCapability3gpp::kConnectAllowedAuth[] = "allowed-auth";
const char CellularCapability3gpp::kConnectAllowRoaming[] = "allow-roaming";
const char CellularCapability3gpp::kConnectIpType[] = "ip-type";
const int64_t CellularCapability3gpp::kEnterPinTimeoutMilliseconds = 20000;
const int64_t
CellularCapability3gpp::kRegistrationDroppedUpdateTimeoutMilliseconds =
15000;
const RpcIdentifier CellularCapability3gpp::kRootPath = RpcIdentifier("/");
const char CellularCapability3gpp::kStatusProperty[] = "status";
const char CellularCapability3gpp::kOperatorLongProperty[] = "operator-long";
const char CellularCapability3gpp::kOperatorShortProperty[] = "operator-short";
const char CellularCapability3gpp::kOperatorCodeProperty[] = "operator-code";
const char CellularCapability3gpp::kOperatorAccessTechnologyProperty[] =
"access-technology";
const int CellularCapability3gpp::kSetPowerStateTimeoutMilliseconds = 20000;
const int CellularCapability3gpp::kUnknownLockRetriesLeft = 999;
namespace {
// Range of rssi's that modem manager can report.
const double kModemManagerRssiMax = -51.0;
const double kModemManagerRssiMin = -113.0;
// Range of rssi's reported to UI. Any RSSI out of this range is clamped to the
// nearest threshold.
const double kChromeRssiMin = -103.0;
const double kChromeRssiMax = -63.0;
// Plugin strings via ModemManager.
const char kTelitMMPlugin[] = "Telit";
// This identifier is specified in the serviceproviders.prototxt file.
const char kVzwIdentifier[] = "c83d6597-dc91-4d48-a3a7-d86b80123751";
const size_t kVzwMdnLength = 10;
// Used to distinguish trogdor from other boards for signal quality indicators
const char kFilePathPlatformName[] =
"/run/chromeos-config/v1/identity/platform-name";
const char kPlatformTrogdor[] = "Trogdor";
// Keys for the entries of Profiles.
const char kProfileApn[] = "apn";
const char kProfileUsername[] = "username";
const char kProfilePassword[] = "password";
const char kProfileAuthType[] = "auth-type";
string AccessTechnologyToString(uint32_t access_technologies) {
// Order is important. Return the highest radio access technology.
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_LTE)
return kNetworkTechnologyLte;
if (access_technologies &
(MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
MM_MODEM_ACCESS_TECHNOLOGY_EVDOB))
return kNetworkTechnologyEvdo;
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT)
return kNetworkTechnology1Xrtt;
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)
return kNetworkTechnologyHspaPlus;
if (access_technologies &
(MM_MODEM_ACCESS_TECHNOLOGY_HSPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
MM_MODEM_ACCESS_TECHNOLOGY_HSDPA))
return kNetworkTechnologyHspa;
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
return kNetworkTechnologyUmts;
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_EDGE)
return kNetworkTechnologyEdge;
if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
return kNetworkTechnologyGprs;
if (access_technologies &
(MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT | MM_MODEM_ACCESS_TECHNOLOGY_GSM))
return kNetworkTechnologyGsm;
return "";
}
string AccessTechnologyToTechnologyFamily(uint32_t access_technologies) {
if (access_technologies &
(MM_MODEM_ACCESS_TECHNOLOGY_LTE | MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS |
MM_MODEM_ACCESS_TECHNOLOGY_HSPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_UMTS |
MM_MODEM_ACCESS_TECHNOLOGY_EDGE | MM_MODEM_ACCESS_TECHNOLOGY_GPRS |
MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT | MM_MODEM_ACCESS_TECHNOLOGY_GSM))
return kTechnologyFamilyGsm;
if (access_technologies &
(MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
MM_MODEM_ACCESS_TECHNOLOGY_EVDOB | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT))
return kTechnologyFamilyCdma;
return "";
}
MMBearerAllowedAuth ApnAuthenticationToMMBearerAllowedAuth(
const std::string& authentication) {
if (authentication == kApnAuthenticationPap) {
return MM_BEARER_ALLOWED_AUTH_PAP;
}
if (authentication == kApnAuthenticationChap) {
return MM_BEARER_ALLOWED_AUTH_CHAP;
}
return MM_BEARER_ALLOWED_AUTH_UNKNOWN;
}
MMBearerIpFamily IpTypeToMMBearerIpFamily(const std::string& ip_type) {
if (ip_type == kApnIpTypeV6) {
return MM_BEARER_IP_FAMILY_IPV6;
}
if (ip_type == kApnIpTypeV4V6) {
return MM_BEARER_IP_FAMILY_IPV4V6;
}
// 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 networks may not like the fact that two IPv6 addresses originated
// from the same modem within a connection session and may drop the
// connection. So make IPv4-only the default to work around the issue while
// we verify IPv6 support on different carriers.
return MM_BEARER_IP_FAMILY_IPV4;
}
std::string MMBearerAllowedAuthToApnAuthentication(
MMBearerAllowedAuth authentication) {
switch (authentication) {
case MM_BEARER_ALLOWED_AUTH_PAP:
return kApnAuthenticationPap;
case MM_BEARER_ALLOWED_AUTH_CHAP:
return kApnAuthenticationChap;
default:
return "";
}
}
bool IsRegisteredState(MMModem3gppRegistrationState state) {
return (state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING);
}
} // namespace
CellularCapability3gpp::CellularCapability3gpp(Cellular* cellular,
ModemInfo* modem_info)
: CellularCapability(cellular, modem_info),
metrics_(modem_info->manager()->metrics()),
mobile_operator_info_(
new MobileOperatorInfo(cellular->dispatcher(), "ParseScanResult")),
registration_state_(MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN),
current_capabilities_(MM_MODEM_CAPABILITY_NONE),
access_technologies_(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN),
mm_reports_wideband_rssi_(false),
resetting_(false),
subscription_state_(SubscriptionState::kUnknown),
reset_done_(false),
registration_dropped_update_timeout_milliseconds_(
kRegistrationDroppedUpdateTimeoutMilliseconds),
weak_ptr_factory_(this) {
SLOG(this, 2) << "Cellular capability constructed: 3GPP";
mobile_operator_info_->Init();
InitMmReportsWidebandRssi();
}
void CellularCapability3gpp::InitMmReportsWidebandRssi() {
const base::FilePath path(kFilePathPlatformName);
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
std::string contents;
mm_reports_wideband_rssi_ = false;
if (!file.IsValid()) {
LOG(ERROR) << "Invalid file " << path.value();
return;
}
std::vector<char> buf(file.GetLength());
int read = file.ReadAtCurrentPos(buf.data(), buf.size());
if (read < 0) {
LOG(ERROR) << "Could not read from file " << path.value();
return;
}
contents = std::string(buf.begin(), buf.begin() + read);
if (contents == kPlatformTrogdor)
mm_reports_wideband_rssi_ = true;
}
CellularCapability3gpp::~CellularCapability3gpp() = default;
KeyValueStore CellularCapability3gpp::SimLockStatusToProperty(
Error* /*error*/) {
KeyValueStore status;
string lock_type;
switch (sim_lock_status_.lock_type) {
case MM_MODEM_LOCK_SIM_PIN:
lock_type = "sim-pin";
break;
case MM_MODEM_LOCK_SIM_PUK:
lock_type = "sim-puk";
break;
default:
lock_type = "";
break;
}
status.Set<bool>(kSIMLockEnabledProperty, sim_lock_status_.enabled);
status.Set<string>(kSIMLockTypeProperty, lock_type);
status.Set<int32_t>(kSIMLockRetriesLeftProperty,
sim_lock_status_.retries_left);
return status;
}
void CellularCapability3gpp::InitProxies() {
modem_3gpp_proxy_ = control_interface()->CreateMM1ModemModem3gppProxy(
cellular()->dbus_path(), cellular()->dbus_service());
modem_proxy_ = control_interface()->CreateMM1ModemProxy(
cellular()->dbus_path(), cellular()->dbus_service());
modem_simple_proxy_ = control_interface()->CreateMM1ModemSimpleProxy(
cellular()->dbus_path(), cellular()->dbus_service());
modem_location_proxy_ = control_interface()->CreateMM1ModemLocationProxy(
cellular()->dbus_path(), cellular()->dbus_service());
dbus_properties_proxy_ = control_interface()->CreateDBusPropertiesProxy(
cellular()->dbus_path(), cellular()->dbus_service());
modem_proxy_->set_state_changed_callback(
Bind(&CellularCapability3gpp::OnModemStateChangedSignal,
weak_ptr_factory_.GetWeakPtr()));
// Do not create a SIM proxy until the device is enabled because we
// do not yet know the object path of the sim object.
// TODO(jglasgow): register callbacks
}
void CellularCapability3gpp::StartModem(Error* error,
const ResultCallback& callback) {
SLOG(this, 1) << __func__;
InitProxies();
deferred_enable_modem_callback_.Reset();
EnableModem(/*deferrable=*/true, error, callback);
}
void CellularCapability3gpp::EnableModem(bool deferrable,
Error* error,
const ResultCallback& callback) {
SLOG(this, 1) << __func__ << "(deferrable=" << deferrable << ")";
CHECK(!callback.is_null());
Error local_error(Error::kOperationInitiated);
metrics_->NotifyDeviceEnableStarted(cellular()->interface_index());
modem_proxy_->Enable(
true, &local_error,
Bind(&CellularCapability3gpp::EnableModemCompleted,
weak_ptr_factory_.GetWeakPtr(), deferrable, callback),
kTimeoutEnable);
if (local_error.IsFailure()) {
SLOG(this, 2) << __func__ << "Call to modem_proxy_->Enable() failed";
}
if (error) {
error->CopyFrom(local_error);
}
}
void CellularCapability3gpp::EnableModemCompleted(
bool deferrable, const ResultCallback& callback, const Error& error) {
SLOG(this, 1) << __func__ << "(deferrable=" << deferrable
<< ", error=" << error << ")";
// If the enable operation failed with Error::kWrongState, the modem is not
// in the expected state (i.e. disabled). If |deferrable| indicates that the
// enable operation can be deferred, we defer the operation until the modem
// goes into the expected state (see OnModemStateChangedSignal).
//
// Note that when the SIM is locked, the enable operation also fails with
// Error::kWrongState. The enable operation is deferred until the modem goes
// into the disabled state after the SIM is unlocked. We may choose not to
// defer the enable operation when the SIM is locked, but the UI needs to
// trigger the enable operation after the SIM is unlocked, which is currently
// not the case.
if (error.IsFailure()) {
if (!deferrable || error.type() != Error::kWrongState) {
callback.Run(error);
return;
}
if (deferred_enable_modem_callback_.is_null()) {
SLOG(this, 1) << "Defer enable operation.";
// The Enable operation to be deferred should not be further deferrable.
deferred_enable_modem_callback_ = Bind(
&CellularCapability3gpp::EnableModem, weak_ptr_factory_.GetWeakPtr(),
false, // non-deferrable
nullptr, callback);
}
if (IsLocationUpdateSupported()) {
ResultCallback setup_callback =
Bind(&CellularCapability3gpp::OnSetupLocationReply,
weak_ptr_factory_.GetWeakPtr());
SetupLocation(MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI, false,
setup_callback);
}
return;
}
// After modem is enabled, it should be possible to get properties
// TODO(jglasgow): handle errors from GetProperties
GetProperties();
// We expect the modem to start scanning after it has been enabled.
// Change this if this behavior is no longer the case in the future.
metrics_->NotifyDeviceEnableFinished(cellular()->interface_index());
metrics_->NotifyDeviceScanStarted(cellular()->interface_index());
callback.Run(error);
}
void CellularCapability3gpp::StopModem(Error* error,
const ResultCallback& callback) {
CHECK(!callback.is_null());
CHECK(error);
// If there is an outstanding registration change, simply ignore it since
// the service will be destroyed anyway.
if (!registration_dropped_update_callback_.IsCancelled()) {
registration_dropped_update_callback_.Cancel();
SLOG(this, 2) << __func__ << " Cancelled delayed deregister.";
}
cellular()->dispatcher()->PostTask(
FROM_HERE, Bind(&CellularCapability3gpp::Stop_Disable,
weak_ptr_factory_.GetWeakPtr(), callback));
deferred_enable_modem_callback_.Reset();
}
void CellularCapability3gpp::Stop_Disable(const ResultCallback& callback) {
SLOG(this, 3) << __func__;
Error error;
metrics_->NotifyDeviceDisableStarted(cellular()->interface_index());
modem_proxy_->Enable(false, &error,
Bind(&CellularCapability3gpp::Stop_DisableCompleted,
weak_ptr_factory_.GetWeakPtr(), callback),
kTimeoutEnable);
if (error.IsFailure())
callback.Run(error);
}
void CellularCapability3gpp::Stop_DisableCompleted(
const ResultCallback& callback, const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsSuccess()) {
// The modem has been successfully disabled, but we still need to power it
// down.
Stop_PowerDown(callback);
} else {
// An error occurred; terminate the disable sequence.
callback.Run(error);
}
}
void CellularCapability3gpp::Stop_PowerDown(const ResultCallback& callback) {
SLOG(this, 3) << __func__;
Error error;
modem_proxy_->SetPowerState(
MM_MODEM_POWER_STATE_LOW, &error,
Bind(&CellularCapability3gpp::Stop_PowerDownCompleted,
weak_ptr_factory_.GetWeakPtr(), callback),
kSetPowerStateTimeoutMilliseconds);
if (error.IsFailure())
// This really shouldn't happen, but if it does, report success,
// because a stop initiated power down is only called if the
// modem was successfully disabled, but the failure of this
// operation should still be propagated up as a successful disable.
Stop_PowerDownCompleted(callback, error);
}
// Note: if we were in the middle of powering down the modem when the
// system suspended, we might not get this event from
// ModemManager. And we might not even get a timeout from dbus-c++,
// because StartModem re-initializes proxies.
void CellularCapability3gpp::Stop_PowerDownCompleted(
const ResultCallback& callback, const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsFailure())
SLOG(this, 2) << "Ignoring error returned by SetPowerState: " << error;
// Since the disable succeeded, if power down fails, we currently fail
// silently, i.e. we need to report the disable operation as having
// succeeded.
metrics_->NotifyDeviceDisableFinished(cellular()->interface_index());
ReleaseProxies();
callback.Run(Error());
}
void CellularCapability3gpp::Connect(const KeyValueStore& properties,
Error* error,
const ResultCallback& callback) {
SLOG(this, 3) << __func__;
RpcIdentifierCallback cb = Bind(&CellularCapability3gpp::OnConnectReply,
weak_ptr_factory_.GetWeakPtr(), callback);
modem_simple_proxy_->Connect(properties, error, cb, kTimeoutConnect);
}
void CellularCapability3gpp::Disconnect(Error* error,
const ResultCallback& callback) {
SLOG(this, 3) << __func__;
if (modem_simple_proxy_) {
SLOG(this, 2) << "Disconnect all bearers.";
// If "/" is passed as the bearer path, ModemManager will disconnect all
// bearers.
modem_simple_proxy_->Disconnect(kRootPath, error, callback,
kTimeoutDisconnect);
}
}
void CellularCapability3gpp::CompleteActivation(Error* error) {
SLOG(this, 3) << __func__;
// Persist the ICCID as "Pending Activation".
// We're assuming that when this function gets called,
// |cellular()->iccid()| will be non-empty. We still check here that
// is non-empty, though something is wrong if it is empty.
const string& iccid = cellular()->iccid();
if (iccid.empty()) {
SLOG(this, 2) << "SIM identifier not available. Nothing to do.";
return;
}
modem_info()->pending_activation_store()->SetActivationState(
PendingActivationStore::kIdentifierICCID, iccid,
PendingActivationStore::kStatePending);
UpdatePendingActivationState();
SLOG(this, 2) << "Resetting modem for activation.";
ResetAfterActivation();
}
void CellularCapability3gpp::ResetAfterActivation() {
SLOG(this, 3) << __func__;
// Here the initial call to Reset might fail in rare cases. Simply ignore.
Error error;
ResultCallback callback =
Bind(&CellularCapability3gpp::OnResetAfterActivationReply,
weak_ptr_factory_.GetWeakPtr());
Reset(&error, callback);
if (error.IsFailure())
SLOG(this, 2) << "Failed to reset after activation.";
}
void CellularCapability3gpp::OnResetAfterActivationReply(const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsFailure()) {
SLOG(this, 2) << "Failed to reset after activation. Try again later.";
// TODO(armansito): Maybe post a delayed reset task?
return;
}
reset_done_ = true;
UpdatePendingActivationState();
}
void CellularCapability3gpp::UpdatePendingActivationState() {
SLOG(this, 3) << __func__;
const string& iccid = cellular()->iccid();
bool registered =
registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
// We know a service is activated if |subscription_state_| is
// SubscriptionState::kProvisioned / SubscriptionState::kOutOfCredits
// In the case that |subscription_state_| is SubscriptionState::kUnknown, we
// fallback on checking for a valid MDN.
bool activated =
((subscription_state_ == SubscriptionState::kProvisioned) ||
(subscription_state_ == SubscriptionState::kOutOfCredits)) ||
((subscription_state_ == SubscriptionState::kUnknown) && IsMdnValid());
if (activated && !iccid.empty())
modem_info()->pending_activation_store()->RemoveEntry(
PendingActivationStore::kIdentifierICCID, iccid);
CellularServiceRefPtr service = cellular()->service();
if (!service)
return;
if (service->activation_state() == kActivationStateActivated)
// Either no service or already activated. Nothing to do.
return;
// If the ICCID is not available, the following logic can be delayed until it
// becomes available.
if (iccid.empty())
return;
PendingActivationStore::State state =
modem_info()->pending_activation_store()->GetActivationState(
PendingActivationStore::kIdentifierICCID, iccid);
switch (state) {
case PendingActivationStore::kStatePending:
// Always mark the service as activating here, as the ICCID could have
// been unavailable earlier.
service->SetActivationState(kActivationStateActivating);
if (reset_done_) {
SLOG(this, 2) << "Post-payment activation reset complete.";
modem_info()->pending_activation_store()->SetActivationState(
PendingActivationStore::kIdentifierICCID, iccid,
PendingActivationStore::kStateActivated);
}
break;
case PendingActivationStore::kStateActivated:
if (registered) {
// Trigger auto connect here.
SLOG(this, 2) << "Modem has been reset at least once, try to "
<< "autoconnect to force MDN to update.";
service->AutoConnect();
}
break;
case PendingActivationStore::kStateUnknown:
// No entry exists for this ICCID. Nothing to do.
break;
default:
NOTREACHED();
}
}
string CellularCapability3gpp::GetMdnForOLP(
const MobileOperatorInfo* operator_info) const {
// TODO(benchan): This is ugly. Remove carrier specific code once we move
// mobile activation logic to carrier-specifc extensions (crbug.com/260073).
const string& mdn = cellular()->mdn();
if (!operator_info->IsMobileNetworkOperatorKnown()) {
// Can't make any carrier specific modifications.
return mdn;
}
if (operator_info->uuid() == kVzwIdentifier) {
// subscription_state_ is the definitive indicator of whether we need
// activation. The OLP expects an all zero MDN in that case.
if (subscription_state_ == SubscriptionState::kUnprovisioned ||
mdn.empty()) {
return string(kVzwMdnLength, '0');
}
if (mdn.length() > kVzwMdnLength) {
return mdn.substr(mdn.length() - kVzwMdnLength);
}
}
return mdn;
}
void CellularCapability3gpp::ReleaseProxies() {
SLOG(this, 3) << __func__;
modem_3gpp_proxy_.reset();
modem_proxy_.reset();
modem_location_proxy_.reset();
modem_simple_proxy_.reset();
dbus_properties_proxy_.reset();
// |sim_proxy_| is managed through OnSimPathChanged() and thus shouldn't be
// cleared here in order to keep it in sync with |sim_path_|.
}
bool CellularCapability3gpp::AreProxiesInitialized() const {
return (modem_3gpp_proxy_.get() && modem_proxy_.get() &&
modem_simple_proxy_.get() && sim_proxy_.get() &&
modem_location_proxy_.get() && dbus_properties_proxy_.get());
}
void CellularCapability3gpp::UpdateServiceActivationState() {
CellularServiceRefPtr service = cellular()->service();
if (!service)
return;
service->NotifySubscriptionStateChanged(subscription_state_);
const string& iccid = cellular()->iccid();
string activation_state;
PendingActivationStore::State state =
modem_info()->pending_activation_store()->GetActivationState(
PendingActivationStore::kIdentifierICCID, iccid);
if ((subscription_state_ == SubscriptionState::kUnknown ||
subscription_state_ == SubscriptionState::kUnprovisioned) &&
!iccid.empty() && state == PendingActivationStore::kStatePending) {
activation_state = kActivationStateActivating;
} else if (IsServiceActivationRequired()) {
activation_state = kActivationStateNotActivated;
} else {
activation_state = kActivationStateActivated;
}
service->SetActivationState(activation_state);
}
void CellularCapability3gpp::OnServiceCreated() {
// ModemManager might have issued some property updates before the service
// object was created to receive the udpates, so we explicitly refresh the
// properties here.
GetProperties();
cellular()->service()->SetActivationType(CellularService::kActivationTypeOTA);
UpdateServiceActivationState();
// Make sure that the network technology is set when the service gets
// created, just in case.
cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
}
void CellularCapability3gpp::SetupConnectProperties(KeyValueStore* properties) {
apn_try_list_ = cellular()->BuildApnTryList();
FillConnectPropertyMap(properties);
}
void CellularCapability3gpp::FillConnectPropertyMap(KeyValueStore* properties) {
properties->Set<bool>(kConnectAllowRoaming,
cellular()->IsRoamingAllowedOrRequired());
if (apn_try_list_.empty())
return;
// Leave the APN at the front of the list, so that it can be recorded
// if the connect attempt succeeds.
Stringmap apn_info = apn_try_list_.front();
SLOG(this, 2) << __func__ << ": Using APN " << apn_info[kApnProperty];
properties->Set<string>(kConnectApn, apn_info[kApnProperty]);
if (base::Contains(apn_info, kApnUsernameProperty))
properties->Set<string>(kConnectUser, apn_info[kApnUsernameProperty]);
if (base::Contains(apn_info, kApnPasswordProperty))
properties->Set<string>(kConnectPassword, apn_info[kApnPasswordProperty]);
if (base::Contains(apn_info, kApnAuthenticationProperty)) {
MMBearerAllowedAuth allowed_auth = ApnAuthenticationToMMBearerAllowedAuth(
apn_info[kApnAuthenticationProperty]);
if (allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN)
properties->Set<uint32_t>(kConnectAllowedAuth, allowed_auth);
}
if (base::Contains(apn_info, kApnIpTypeProperty)) {
properties->Set<uint32_t>(
kConnectIpType, IpTypeToMMBearerIpFamily(apn_info[kApnIpTypeProperty]));
}
}
void CellularCapability3gpp::OnConnectReply(const ResultCallback& callback,
const RpcIdentifier& bearer,
const Error& error) {
SLOG(this, 3) << __func__ << "(" << error << ")";
CellularServiceRefPtr service = cellular()->service();
if (!service) {
// The service could have been deleted before our Connect() request
// completes if the modem was enabled and then quickly disabled.
apn_try_list_.clear();
} else if (error.IsFailure()) {
service->ClearLastGoodApn();
// The APN that was just tried (and failed) is still at the
// front of the list, about to be removed. If the list is empty
// after that, try one last time without an APN. This may succeed
// with some modems in some cases.
if (RetriableConnectError(error) && !apn_try_list_.empty()) {
apn_try_list_.pop_front();
SLOG(this, 2) << "Connect failed with invalid APN, "
<< apn_try_list_.size() << " remaining APNs to try";
KeyValueStore props;
FillConnectPropertyMap(&props);
Error error;
Connect(props, &error, callback);
return;
}
} else {
if (!apn_try_list_.empty()) {
service->SetLastGoodApn(apn_try_list_.front());
apn_try_list_.clear();
}
SLOG(this, 2) << "Connected bearer " << bearer.value();
}
if (!callback.is_null())
callback.Run(error);
UpdatePendingActivationState();
}
void CellularCapability3gpp::FillInitialEpsBearerPropertyMap(
KeyValueStore* properties) {
std::deque<Stringmap> apn_list = cellular()->BuildApnTryList();
auto apn_info = apn_list.end();
// keep only 'attach APN'
for (apn_info = apn_list.begin(); apn_info != apn_list.end(); apn_info++) {
if (base::Contains(*apn_info, kApnAttachProperty))
break;
}
if (apn_info == apn_list.end()) {
SLOG(this, 2) << __func__ << ": no Attach APN.";
return;
}
SLOG(this, 2) << __func__ << ": Using APN " << (*apn_info)[kApnProperty];
properties->Set<string>(kConnectApn, (*apn_info)[kApnProperty]);
if (base::Contains(*apn_info, kApnUsernameProperty))
properties->Set<string>(kConnectUser, (*apn_info)[kApnUsernameProperty]);
if (base::Contains(*apn_info, kApnPasswordProperty)) {
properties->Set<string>(kConnectPassword,
(*apn_info)[kApnPasswordProperty]);
}
if (base::Contains(*apn_info, kApnAuthenticationProperty)) {
MMBearerAllowedAuth allowed_auth = ApnAuthenticationToMMBearerAllowedAuth(
(*apn_info)[kApnAuthenticationProperty]);
if (allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN)
properties->Set<uint32_t>(kConnectAllowedAuth, allowed_auth);
}
}
void CellularCapability3gpp::GetProperties() {
SLOG(this, 3) << __func__;
CHECK(dbus_properties_proxy_);
auto properties = dbus_properties_proxy_->GetAll(MM_DBUS_INTERFACE_MODEM);
OnModemPropertiesChanged(properties);
auto properties_3gpp =
dbus_properties_proxy_->GetAll(MM_DBUS_INTERFACE_MODEM_MODEM3GPP);
OnModem3gppPropertiesChanged(properties_3gpp);
}
void CellularCapability3gpp::UpdateServiceOLP() {
SLOG(this, 3) << __func__;
// OLP is based off of the Home Provider.
if (!cellular()->home_provider_info()->IsMobileNetworkOperatorKnown()) {
return;
}
const vector<MobileOperatorInfo::OnlinePortal>& olp_list =
cellular()->home_provider_info()->olp_list();
if (olp_list.empty()) {
return;
}
if (olp_list.size() > 1) {
SLOG(this, 1) << "Found multiple online portals. Choosing the first.";
}
string post_data = olp_list[0].post_data;
base::ReplaceSubstringsAfterOffset(&post_data, 0, "${iccid}",
cellular()->iccid());
base::ReplaceSubstringsAfterOffset(&post_data, 0, "${imei}",
cellular()->imei());
base::ReplaceSubstringsAfterOffset(&post_data, 0, "${imsi}",
cellular()->imsi());
base::ReplaceSubstringsAfterOffset(
&post_data, 0, "${mdn}", GetMdnForOLP(cellular()->home_provider_info()));
base::ReplaceSubstringsAfterOffset(&post_data, 0, "${min}",
cellular()->min());
cellular()->service()->SetOLP(olp_list[0].url, olp_list[0].method, post_data);
}
void CellularCapability3gpp::UpdateActiveBearer() {
SLOG(this, 3) << __func__;
// Look for the first active bearer and use its path as the connected
// one. Right now, we don't allow more than one active bearer.
active_bearer_.reset();
for (const auto& path : bearer_paths_) {
auto bearer = std::make_unique<CellularBearer>(control_interface(), path,
cellular()->dbus_service());
// The bearer object may have vanished before ModemManager updates the
// 'Bearers' property.
if (!bearer->Init())
continue;
if (!bearer->connected())
continue;
SLOG(this, 2) << "Found active bearer \"" << path.value() << "\".";
CHECK(!active_bearer_) << "Found more than one active bearer.";
active_bearer_ = std::move(bearer);
}
if (!active_bearer_)
SLOG(this, 2) << "No active bearer found.";
}
bool CellularCapability3gpp::IsServiceActivationRequired() const {
const string& iccid = cellular()->iccid();
// subscription_state_ is the definitive answer. If that does not work,
// fallback on MDN based logic.
if (subscription_state_ == SubscriptionState::kProvisioned ||
subscription_state_ == SubscriptionState::kOutOfCredits)
return false;
// We are in the process of activating, ignore all other clues from the
// network and use our own knowledge about the activation state.
if (!iccid.empty() &&
modem_info()->pending_activation_store()->GetActivationState(
PendingActivationStore::kIdentifierICCID, iccid) !=
PendingActivationStore::kStateUnknown)
return false;
// Network notification that the service needs to be activated.
if (subscription_state_ == SubscriptionState::kUnprovisioned)
return true;
// If there is no online payment portal information, it's safer to assume
// the service does not require activation.
if (!cellular()->home_provider_info()->IsMobileNetworkOperatorKnown() ||
cellular()->home_provider_info()->olp_list().empty()) {
return false;
}
// If the MDN is invalid (i.e. empty or contains only zeros), the service
// requires activation.
return !IsMdnValid();
}
bool CellularCapability3gpp::IsActivating() const {
return false;
}
bool CellularCapability3gpp::IsMdnValid() const {
const string& mdn = cellular()->mdn();
// Note that |mdn| is normalized to contain only digits in OnMdnChanged().
for (size_t i = 0; i < mdn.size(); ++i) {
if (mdn[i] != '0')
return true;
}
return false;
}
// always called from an async context
void CellularCapability3gpp::Register(const ResultCallback& callback) {
SLOG(this, 3) << __func__ << " \"" << cellular()->selected_network() << "\"";
CHECK(!callback.is_null());
Error error;
ResultCallback cb = Bind(&CellularCapability3gpp::OnRegisterReply,
weak_ptr_factory_.GetWeakPtr(), callback);
modem_3gpp_proxy_->Register(cellular()->selected_network(), &error, cb,
kTimeoutRegister);
if (error.IsFailure())
callback.Run(error);
}
void CellularCapability3gpp::RegisterOnNetwork(const string& network_id,
Error* error,
const ResultCallback& callback) {
SLOG(this, 3) << __func__ << "(" << network_id << ")";
CHECK(error);
desired_network_ = network_id;
ResultCallback cb = Bind(&CellularCapability3gpp::OnRegisterReply,
weak_ptr_factory_.GetWeakPtr(), callback);
modem_3gpp_proxy_->Register(network_id, error, cb, kTimeoutRegister);
}
void CellularCapability3gpp::OnRegisterReply(const ResultCallback& callback,
const Error& error) {
SLOG(this, 3) << __func__ << "(" << error << ")";
if (error.IsSuccess()) {
cellular()->set_selected_network(desired_network_);
desired_network_.clear();
callback.Run(error);
return;
}
// If registration on the desired network failed,
// try to register on the home network.
if (!desired_network_.empty()) {
desired_network_.clear();
cellular()->set_selected_network("");
LOG(INFO) << "Couldn't register on selected network, trying home network";
Register(callback);
return;
}
callback.Run(error);
}
bool CellularCapability3gpp::IsRegistered() const {
return IsRegisteredState(registration_state_);
}
void CellularCapability3gpp::SetUnregistered(bool searching) {
// If we're already in some non-registered state, don't override that
if (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
registration_state_ =
(searching ? MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING
: MM_MODEM_3GPP_REGISTRATION_STATE_IDLE);
}
}
void CellularCapability3gpp::RequirePin(const string& pin,
bool require,
Error* error,
const ResultCallback& callback) {
CHECK(error);
sim_proxy_->EnablePin(pin, require, error, callback, kTimeoutDefault);
}
void CellularCapability3gpp::EnterPin(const string& pin,
Error* error,
const ResultCallback& callback) {
CHECK(error);
SLOG(this, 3) << __func__;
sim_proxy_->SendPin(pin, error, callback, kEnterPinTimeoutMilliseconds);
}
void CellularCapability3gpp::UnblockPin(const string& unblock_code,
const string& pin,
Error* error,
const ResultCallback& callback) {
CHECK(error);
sim_proxy_->SendPuk(unblock_code, pin, error, callback, kTimeoutDefault);
}
void CellularCapability3gpp::ChangePin(const string& old_pin,
const string& new_pin,
Error* error,
const ResultCallback& callback) {
CHECK(error);
sim_proxy_->ChangePin(old_pin, new_pin, error, callback, kTimeoutDefault);
}
void CellularCapability3gpp::Reset(Error* error,
const ResultCallback& callback) {
SLOG(this, 3) << __func__;
CHECK(error);
if (resetting_) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInProgress,
"Already resetting");
return;
}
ResultCallback cb = Bind(&CellularCapability3gpp::OnResetReply,
weak_ptr_factory_.GetWeakPtr(), callback);
modem_proxy_->Reset(error, cb, kTimeoutReset);
if (!error->IsFailure()) {
resetting_ = true;
}
}
void CellularCapability3gpp::OnResetReply(const ResultCallback& callback,
const Error& error) {
SLOG(this, 3) << __func__;
resetting_ = false;
if (!callback.is_null())
callback.Run(error);
}
void CellularCapability3gpp::Scan(Error* error,
const ResultStringmapsCallback& callback) {
KeyValueStoresCallback cb = Bind(&CellularCapability3gpp::OnScanReply,
weak_ptr_factory_.GetWeakPtr(), callback);
modem_3gpp_proxy_->Scan(error, cb, kTimeoutScan);
}
void CellularCapability3gpp::OnScanReply(
const ResultStringmapsCallback& callback,
const ScanResults& results,
const Error& error) {
Stringmaps found_networks;
for (const auto& result : results)
found_networks.push_back(ParseScanResult(result));
callback.Run(found_networks, error);
}
Stringmap CellularCapability3gpp::ParseScanResult(const ScanResult& result) {
/* ScanResults contain the following keys:
"status"
A MMModem3gppNetworkAvailability value representing network
availability status, given as an unsigned integer (signature "u").
This key will always be present.
"operator-long"
Long-format name of operator, given as a string value (signature
"s"). If the name is unknown, this field should not be present.
"operator-short"
Short-format name of operator, given as a string value
(signature "s"). If the name is unknown, this field should not
be present.
"operator-code"
Mobile code of the operator, given as a string value (signature
"s"). Returned in the format "MCCMNC", where MCC is the
three-digit ITU E.212 Mobile Country Code and MNC is the two- or
three-digit GSM Mobile Network Code. e.g. "31026" or "310260".
"access-technology"
A MMModemAccessTechnology value representing the generic access
technology used by this mobile network, given as an unsigned
integer (signature "u").
*/
Stringmap parsed;
if (result.Contains<uint32_t>(kStatusProperty)) {
uint32_t status = result.Get<uint32_t>(kStatusProperty);
// numerical values are taken from 3GPP TS 27.007 Section 7.3.
static const char* const kStatusString[] = {
"unknown", // MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN
"available", // MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE
"current", // MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT
"forbidden", // MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN
};
parsed[kStatusProperty] = kStatusString[status];
}
// MMModemAccessTechnology
if (result.Contains<uint32_t>(kOperatorAccessTechnologyProperty)) {
parsed[kTechnologyProperty] = AccessTechnologyToString(
result.Get<uint32_t>(kOperatorAccessTechnologyProperty));
}
string operator_long, operator_short, operator_code;
if (result.Contains<string>(kOperatorLongProperty))
parsed[kLongNameProperty] = result.Get<string>(kOperatorLongProperty);
if (result.Contains<string>(kOperatorShortProperty))
parsed[kShortNameProperty] = result.Get<string>(kOperatorShortProperty);
if (result.Contains<string>(kOperatorCodeProperty))
parsed[kNetworkIdProperty] = result.Get<string>(kOperatorCodeProperty);
// If the long name is not available but the network ID is, look up the long
// name in the mobile provider database.
if ((!base::Contains(parsed, kLongNameProperty) ||
parsed[kLongNameProperty].empty()) &&
base::Contains(parsed, kNetworkIdProperty)) {
mobile_operator_info_->Reset();
mobile_operator_info_->UpdateMCCMNC(parsed[kNetworkIdProperty]);
if (mobile_operator_info_->IsMobileNetworkOperatorKnown() &&
!mobile_operator_info_->operator_name().empty()) {
parsed[kLongNameProperty] = mobile_operator_info_->operator_name();
}
}
return parsed;
}
void CellularCapability3gpp::SetInitialEpsBearer(
const KeyValueStore& properties,
Error* error,
const ResultCallback& callback) {
SLOG(this, 3) << __func__;
if (modem_3gpp_proxy_) {
modem_3gpp_proxy_->SetInitialEpsBearerSettings(properties, error, callback,
kTimeoutSetInitialEpsBearer);
} else {
SLOG(this, 3) << __func__ << " skipping, no 3GPP proxy";
}
}
void CellularCapability3gpp::OnSetInitialEpsBearerReply(const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsFailure()) {
SLOG(this, 2) << "Failed to set the 'attach APN' for the EPS bearer.";
}
}
void CellularCapability3gpp::SetupLocation(uint32_t sources,
bool signal_location,
const ResultCallback& callback) {
Error error;
modem_location_proxy_->Setup(sources, signal_location, &error, callback,
kTimeoutSetupLocation);
}
void CellularCapability3gpp::OnSetupLocationReply(const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsFailure()) {
// Not fatal: most devices already enable this when
// ModemManager starts. This failure is only likely for devices
// which don't support location gathering.
SLOG(this, 2) << "Failed to setup modem location capability.";
return;
}
}
void CellularCapability3gpp::GetLocation(const StringCallback& callback) {
BrilloAnyCallback cb = Bind(&CellularCapability3gpp::OnGetLocationReply,
weak_ptr_factory_.GetWeakPtr(), callback);
Error error;
modem_location_proxy_->GetLocation(&error, cb, kTimeoutGetLocation);
}
void CellularCapability3gpp::OnGetLocationReply(
const StringCallback& callback,
const std::map<uint32_t, brillo::Any>& results,
const Error& error) {
SLOG(this, 3) << __func__;
if (error.IsFailure()) {
SLOG(this, 2) << "Error getting location.";
return;
}
// For 3G modems we currently only care about the "MCC,MNC,LAC,CI" location
auto it = results.find(MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI);
if (it != results.end()) {
brillo::Any gpp_value = it->second;
const string& location_string = gpp_value.Get<const string>();
callback.Run(location_string, Error());
} else {
callback.Run(std::string(), Error());
}
}
bool CellularCapability3gpp::IsLocationUpdateSupported() const {
// Allow modems as they're tested / needed
return cellular()->mm_plugin() == kTelitMMPlugin;
}
CellularBearer* CellularCapability3gpp::GetActiveBearer() const {
return active_bearer_.get();
}
const std::vector<std::unique_ptr<MobileOperatorInfo::MobileAPN>>&
CellularCapability3gpp::GetProfiles() const {
return profiles_;
}
string CellularCapability3gpp::GetNetworkTechnologyString() const {
return AccessTechnologyToString(access_technologies_);
}
string CellularCapability3gpp::GetRoamingStateString() const {
switch (registration_state_) {
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
return kRoamingStateHome;
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
return kRoamingStateRoaming;
default:
break;
}
return kRoamingStateUnknown;
}
string CellularCapability3gpp::GetTypeString() const {
return AccessTechnologyToTechnologyFamily(access_technologies_);
}
void CellularCapability3gpp::OnModemPropertiesChanged(
const KeyValueStore& properties) {
// Update the bearers property before the modem state property as
// OnModemStateChanged may call UpdateActiveBearer, which reads the bearers
// property.
if (properties.Contains<RpcIdentifiers>(MM_MODEM_PROPERTY_BEARERS)) {
RpcIdentifiers bearers =
properties.Get<RpcIdentifiers>(MM_MODEM_PROPERTY_BEARERS);
OnBearersChanged(bearers);
}
// This solves a bootstrapping problem: If the modem is not yet
// enabled, there are no proxy objects associated with the capability
// object, so modem signals like StateChanged aren't seen. By monitoring
// changes to the State property via the ModemManager, we're able to
// get the initialization process started, which will result in the
// creation of the proxy objects.
//
// The first time we see the change to State (when the modem state
// is Unknown), we simply update the state, and rely on the Manager to
// enable the device when it is registered with the Manager. On subsequent
// changes to State, we need to explicitly enable the device ourselves.
if (properties.Contains<int32_t>(MM_MODEM_PROPERTY_STATE)) {
int32_t istate = properties.Get<int32_t>(MM_MODEM_PROPERTY_STATE);
Cellular::ModemState state = static_cast<Cellular::ModemState>(istate);
OnModemStateChanged(state);
}
if (properties.Contains<RpcIdentifier>(MM_MODEM_PROPERTY_SIM))
OnSimPathChanged(properties.Get<RpcIdentifier>(MM_MODEM_PROPERTY_SIM));
if (properties.Contains<uint32_t>(MM_MODEM_PROPERTY_CURRENTCAPABILITIES)) {
OnModemCurrentCapabilitiesChanged(
properties.Get<uint32_t>(MM_MODEM_PROPERTY_CURRENTCAPABILITIES));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_MANUFACTURER)) {
cellular()->set_manufacturer(
properties.Get<string>(MM_MODEM_PROPERTY_MANUFACTURER));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_MODEL)) {
cellular()->set_model_id(properties.Get<string>(MM_MODEM_PROPERTY_MODEL));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_PLUGIN)) {
cellular()->set_mm_plugin(properties.Get<string>(MM_MODEM_PROPERTY_PLUGIN));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_REVISION)) {
OnModemRevisionChanged(properties.Get<string>(MM_MODEM_PROPERTY_REVISION));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_HARDWAREREVISION)) {
OnModemHardwareRevisionChanged(
properties.Get<string>(MM_MODEM_PROPERTY_HARDWAREREVISION));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_DEVICE)) {
OnModemDevicePathChanged(properties.Get<string>(MM_MODEM_PROPERTY_DEVICE));
}
if (properties.Contains<string>(MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER)) {
cellular()->set_equipment_id(
properties.Get<string>(MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER));
}
// Unlock required and SimLock
bool lock_status_changed = false;
if (properties.Contains<uint32_t>(MM_MODEM_PROPERTY_UNLOCKREQUIRED)) {
uint32_t unlock_required =
properties.Get<uint32_t>(MM_MODEM_PROPERTY_UNLOCKREQUIRED);
OnLockTypeChanged(static_cast<MMModemLock>(unlock_required));
lock_status_changed = true;
}
// Unlock retries
if (properties.ContainsVariant(MM_MODEM_PROPERTY_UNLOCKRETRIES)) {
OnLockRetriesChanged(properties.GetVariant(MM_MODEM_PROPERTY_UNLOCKRETRIES)
.Get<LockRetryData>());
lock_status_changed = true;
}
if (lock_status_changed)
OnSimLockStatusChanged();
if (properties.Contains<uint32_t>(MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES)) {
OnAccessTechnologiesChanged(
properties.Get<uint32_t>(MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES));
}
if (properties.ContainsVariant(MM_MODEM_PROPERTY_SIGNALQUALITY)) {
SignalQuality quality =
properties.GetVariant(MM_MODEM_PROPERTY_SIGNALQUALITY)
.Get<SignalQuality>();
OnSignalQualityChanged(std::get<0>(quality));
}
if (properties.Contains<Strings>(MM_MODEM_PROPERTY_OWNNUMBERS)) {
vector<string> numbers =
properties.Get<Strings>(MM_MODEM_PROPERTY_OWNNUMBERS);
string mdn;
if (!numbers.empty())
mdn = numbers[0];
OnMdnChanged(mdn);
}
}
void CellularCapability3gpp::OnPropertiesChanged(
const string& interface, const KeyValueStore& changed_properties) {
SLOG(this, 3) << __func__ << "(" << interface << ")";
if (interface == MM_DBUS_INTERFACE_MODEM) {
OnModemPropertiesChanged(changed_properties);
}
if (interface == MM_DBUS_INTERFACE_MODEM_MODEM3GPP) {
OnModem3gppPropertiesChanged(changed_properties);
}
if (interface == MM_DBUS_INTERFACE_SIM) {
OnSimPropertiesChanged(changed_properties);
}
}
bool CellularCapability3gpp::RetriableConnectError(const Error& error) const {
return error.type() == Error::kInvalidApn;
}
bool CellularCapability3gpp::IsValidSimPath(
const RpcIdentifier& sim_path) const {
return !sim_path.value().empty() && sim_path != kRootPath;
}
string CellularCapability3gpp::NormalizeMdn(const string& mdn) const {
string normalized_mdn;
for (size_t i = 0; i < mdn.size(); ++i) {
if (base::IsAsciiDigit(mdn[i]))
normalized_mdn += mdn[i];
}
return normalized_mdn;
}
void CellularCapability3gpp::OnSimPathChanged(const RpcIdentifier& sim_path) {
if (sim_path == sim_path_)
return;
sim_proxy_ = nullptr;
if (IsValidSimPath(sim_path)) {
sim_proxy_ = control_interface()->CreateMM1SimProxy(
sim_path, cellular()->dbus_service());
}
sim_path_ = sim_path;
if (!IsValidSimPath(sim_path)) {
// Clear all data about the sim
cellular()->set_imsi("");
spn_ = "";
cellular()->set_sim_present(false);
OnSimIdentifierChanged("");
cellular()->set_eid("");
OnOperatorIdChanged("");
cellular()->home_provider_info()->Reset();
return;
}
cellular()->set_sim_present(true);
RequestSimProperties(sim_path_);
}
void CellularCapability3gpp::OnModemCurrentCapabilitiesChanged(
uint32_t current_capabilities) {
current_capabilities_ = current_capabilities;
// Only allow network scan when the modem's current capabilities support
// GSM/UMTS.
//
// TODO(benchan): We should consider having the modem plugins in ModemManager
// reporting whether network scan is supported.
cellular()->set_scanning_supported(
(current_capabilities & MM_MODEM_CAPABILITY_GSM_UMTS) != 0);
}
void CellularCapability3gpp::OnMdnChanged(const string& mdn) {
cellular()->set_mdn(NormalizeMdn(mdn));
UpdateServiceActivationState();
UpdatePendingActivationState();
}
void CellularCapability3gpp::OnModemRevisionChanged(const string& revision) {
cellular()->set_firmware_revision(revision);
}
void CellularCapability3gpp::OnModemHardwareRevisionChanged(
const string& hardware_revision) {
cellular()->set_hardware_revision(hardware_revision);
}
void CellularCapability3gpp::OnModemDevicePathChanged(const string& path) {
cellular()->set_device_id(DeviceId::CreateFromSysfs(base::FilePath(path)));
}
void CellularCapability3gpp::OnModemStateChanged(Cellular::ModemState state) {
SLOG(this, 3) << __func__ << ": " << Cellular::GetModemStateString(state);
if (state == Cellular::kModemStateConnected) {
// This assumes that ModemManager updates the Bearers list and the Bearer
// properties before changing Modem state to Connected.
SLOG(this, 2) << "Update active bearer.";
UpdateActiveBearer();
}
cellular()->OnModemStateChanged(state);
// TODO(armansito): Move the deferred enable logic to Cellular
// (See crbug.com/279499).
if (!deferred_enable_modem_callback_.is_null() &&
state == Cellular::kModemStateDisabled) {
SLOG(this, 1) << "Enabling modem after deferring.";
deferred_enable_modem_callback_.Run();
deferred_enable_modem_callback_.Reset();
}
}
void CellularCapability3gpp::OnAccessTechnologiesChanged(
uint32_t access_technologies) {
if (access_technologies_ != access_technologies) {
const string old_type_string(GetTypeString());
access_technologies_ = access_technologies;
const string new_type_string(GetTypeString());
if (new_type_string != old_type_string) {
// TODO(jglasgow): address layering violation of emitting change
// signal here for a property owned by Cellular.
cellular()->adaptor()->EmitStringChanged(kTechnologyFamilyProperty,
new_type_string);
}
if (cellular()->service().get()) {
cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
}
}
}
void CellularCapability3gpp::OnBearersChanged(const RpcIdentifiers& bearers) {
bearer_paths_ = bearers;
}
void CellularCapability3gpp::OnLockRetriesChanged(
const LockRetryData& lock_retries) {
SLOG(this, 3) << __func__;
// UI uses lock_retries to indicate the number of attempts remaining
// for enable pin/disable pin/change pin
// By default, the UI operates on PIN1, thus lock_retries should return
// number of PIN1 retries. The only exception is PUK lock, where the UI needs
// to report the number of PUK retries.
// TODO(pholla): Personalization requires the UI to display multiple locks,
// so shill needs to communicate an array of sim_lock_status (b/169615875)
auto retry_lock_type = (sim_lock_status_.lock_type == MM_MODEM_LOCK_SIM_PUK)
? MM_MODEM_LOCK_SIM_PUK
: MM_MODEM_LOCK_SIM_PIN;
auto it = lock_retries.find(retry_lock_type);
sim_lock_status_.retries_left =
(it != lock_retries.end()) ? it->second : kUnknownLockRetriesLeft;
}
void CellularCapability3gpp::OnLockTypeChanged(MMModemLock lock_type) {
SLOG(this, 3) << __func__ << ": " << lock_type;
sim_lock_status_.lock_type = lock_type;
// If the SIM is in a locked state |sim_lock_status_.enabled| might be false.
// This is because the corresponding property 'EnabledFacilityLocks' is on
// the 3GPP interface and the 3GPP interface is not available while the Modem
// is in the 'LOCKED' state.
if (lock_type != MM_MODEM_LOCK_NONE && lock_type != MM_MODEM_LOCK_UNKNOWN &&
!sim_lock_status_.enabled)
sim_lock_status_.enabled = true;
}
void CellularCapability3gpp::OnSimLockStatusChanged() {
SLOG(this, 3) << __func__;
cellular()->adaptor()->EmitKeyValueStoreChanged(
kSIMLockStatusProperty, SimLockStatusToProperty(nullptr));
if (!IsValidSimPath(sim_path_) ||
(sim_lock_status_.lock_type != MM_MODEM_LOCK_NONE &&
sim_lock_status_.lock_type != MM_MODEM_LOCK_UNKNOWN)) {
return;
}
// If the SIM is currently unlocked, assume that we need to refresh
// carrier information, since a locked SIM prevents shill from obtaining
// the necessary data to establish a connection later (e.g. IMSI).
RequestSimProperties(sim_path_);
}
void CellularCapability3gpp::OnModem3gppPropertiesChanged(
const KeyValueStore& properties) {
SLOG(this, 3) << __func__;
if (properties.Contains<string>(MM_MODEM_MODEM3GPP_PROPERTY_IMEI))
cellular()->set_imei(
properties.Get<string>(MM_MODEM_MODEM3GPP_PROPERTY_IMEI));
// Handle registration state changes as a single change
Stringmap::const_iterator it;
string operator_code;
string operator_name;
it = serving_operator_.find(kOperatorCodeKey);
if (it != serving_operator_.end())
operator_code = it->second;
it = serving_operator_.find(kOperatorNameKey);
if (it != serving_operator_.end())
operator_name = it->second;
MMModem3gppRegistrationState state = registration_state_;
bool registration_changed = false;
if (properties.Contains<uint32_t>(
MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE)) {
state = static_cast<MMModem3gppRegistrationState>(properties.Get<uint32_t>(
MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE));
registration_changed = true;
}
if (properties.Contains<string>(MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE)) {
operator_code =
properties.Get<string>(MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE);
registration_changed = true;
}
if (properties.Contains<string>(MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME)) {
operator_name =
properties.Get<string>(MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME);
registration_changed = true;
}
if (registration_changed)
On3gppRegistrationChanged(state, operator_code, operator_name);
if (properties.Contains<uint32_t>(
MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS))
OnFacilityLocksChanged(properties.Get<uint32_t>(
MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS));
if (properties.ContainsVariant(MM_MODEM_MODEM3GPP_PROPERTY_PCO)) {
OnPcoChanged(
properties.GetVariant(MM_MODEM_MODEM3GPP_PROPERTY_PCO).Get<PcoList>());
}
if (properties.ContainsVariant(MM_MODEM_MODEM3GPP_PROPERTY_PROFILES)) {
OnProfilesChanged(
properties.GetVariant(MM_MODEM_MODEM3GPP_PROPERTY_PROFILES)
.Get<Profiles>());
}
}
void CellularCapability3gpp::OnProfilesChanged(const Profiles& profiles) {
profiles_.clear();
for (const auto& profile : profiles) {
auto apn_info = std::make_unique<MobileOperatorInfo::MobileAPN>();
apn_info->apn =
brillo::GetVariantValueOrDefault<string>(profile, kProfileApn);
apn_info->username =
brillo::GetVariantValueOrDefault<string>(profile, kProfileUsername);
apn_info->password =
brillo::GetVariantValueOrDefault<string>(profile, kProfilePassword);
apn_info->authentication =
MMBearerAllowedAuthToApnAuthentication(static_cast<MMBearerAllowedAuth>(
brillo::GetVariantValueOrDefault<uint32_t>(profile,
kProfileAuthType)));
profiles_.push_back(std::move(apn_info));
}
// The cellular object may need to update the APN list now.
cellular()->OnOperatorChanged();
// Bail-out early if we don't want to setup the attach APN
if (!cellular()->use_attach_apn())
return;
// Set the new parameters for the initial EPS bearer (e.g. LTE Attach APN)
KeyValueStore properties;
Error error;
FillInitialEpsBearerPropertyMap(&properties);
ResultCallback cb = Bind(&CellularCapability3gpp::OnSetInitialEpsBearerReply,
weak_ptr_factory_.GetWeakPtr());
// If 'properties' is empty, this will clear the 'attach APN' on the modem.
SetInitialEpsBearer(properties, &error, cb);
}
void CellularCapability3gpp::On3gppRegistrationChanged(
MMModem3gppRegistrationState state,
const string& operator_code,
const string& operator_name) {
SLOG(this, 3) << __func__ << ": regstate=" << state
<< ", opercode=" << operator_code
<< ", opername=" << operator_name;
// While the modem is connected, if the state changed from a registered state
// to a non registered state, defer the state change by 15 seconds.
if (cellular()->modem_state() == Cellular::kModemStateConnected &&
IsRegistered() && !IsRegisteredState(state)) {
if (!registration_dropped_update_callback_.IsCancelled()) {
LOG(WARNING) << "Modem reported consecutive 3GPP registration drops. "
<< "Ignoring earlier notifications.";
registration_dropped_update_callback_.Cancel();
} else {
// This is not a repeated post. So, count this instance of delayed drop
// posted.
metrics_->Notify3GPPRegistrationDelayedDropPosted();
}
SLOG(this, 2) << "Posted deferred registration state update";
registration_dropped_update_callback_.Reset(Bind(
&CellularCapability3gpp::Handle3gppRegistrationChange,
weak_ptr_factory_.GetWeakPtr(), state, operator_code, operator_name));
cellular()->dispatcher()->PostDelayedTask(
FROM_HERE, registration_dropped_update_callback_.callback(),
registration_dropped_update_timeout_milliseconds_);
} else {
if (!registration_dropped_update_callback_.IsCancelled()) {
SLOG(this, 2) << "Cancelled a deferred registration state update";
registration_dropped_update_callback_.Cancel();
// If we cancelled the callback here, it means we had flaky network for a
// small duration.
metrics_->Notify3GPPRegistrationDelayedDropCanceled();
}
Handle3gppRegistrationChange(state, operator_code, operator_name);
}
}
void CellularCapability3gpp::Handle3gppRegistrationChange(
MMModem3gppRegistrationState updated_state,
const string& updated_operator_code,
const string& updated_operator_name) {
SLOG(this, 3) << __func__ << ": regstate=" << updated_state
<< ", opercode=" << updated_operator_code
<< ", opername=" << updated_operator_name;
registration_state_ = updated_state;
serving_operator_[kOperatorCodeKey] = updated_operator_code;
serving_operator_[kOperatorNameKey] = updated_operator_name;
cellular()->serving_operator_info()->UpdateMCCMNC(updated_operator_code);
cellular()->serving_operator_info()->UpdateOperatorName(
updated_operator_name);
cellular()->HandleNewRegistrationState();
// A finished callback does not qualify as a canceled callback.
// We test for a canceled callback to check for outstanding callbacks.
// So, explicitly cancel the callback here.
// Caution: Do not use any function arguments post the call to Cancel().
// Cancel() call invalidates the arguments that were copied when creating
// the callback.
registration_dropped_update_callback_.Cancel();
// If the modem registered with the network and the current ICCID is pending
// activation, then reset the modem.
UpdatePendingActivationState();
}
void CellularCapability3gpp::OnSubscriptionStateChanged(
SubscriptionState updated_subscription_state) {
SLOG(this, 3) << __func__ << ": Updated subscription state = "
<< SubscriptionStateToString(updated_subscription_state);
if (updated_subscription_state == subscription_state_)
return;
subscription_state_ = updated_subscription_state;
UpdateServiceActivationState();
UpdatePendingActivationState();
}
void CellularCapability3gpp::OnModemStateChangedSignal(int32_t old_state,
int32_t new_state,
uint32_t reason) {
Cellular::ModemState old_modem_state =
static_cast<Cellular::ModemState>(old_state);
Cellular::ModemState new_modem_state =
static_cast<Cellular::ModemState>(new_state);
SLOG(this, 3) << __func__ << "("
<< Cellular::GetModemStateString(old_modem_state) << ", "
<< Cellular::GetModemStateString(new_modem_state) << ", "
<< reason << ")";
}
void CellularCapability3gpp::OnSignalQualityChanged(uint32_t quality) {
// Shill does not query RSRP or RSRQ from ModemManager yet. For now, we will
// rely on RSSI.
// TODO(pholla): Report RSRP instead of RSSI b/173016943
double scaled_quality = 0.0;
SLOG(this, 3) << __func__
<< "mm_reports_wideband_rssi : " << mm_reports_wideband_rssi_;
if (mm_reports_wideband_rssi_) {
// Reference from android:
// https://android.googlesource.com/platform/frameworks/base.git/+/HEAD/telephony/java/android/telephony/CarrierConfigManager.java
// RSSI thresholds = Androids RSRP thresholds + 25dB (assuming no noise and
// 5Mhz channel).
// RSSI(dBm) -> UI bars mapping
// >-73 GREAT (4 bars)
// >-83 GOOD (3 bars)
// >-93 MODERATE (2 bars)
// >-inf POOR (1 bar)
// Modem manager measures signal strength in RSSI and maps it to a value in
// the range of [0-100].
double rssi = kModemManagerRssiMin +
static_cast<double>(quality) / 100.0 *
(kModemManagerRssiMax - kModemManagerRssiMin);
double clamped_rssi =
std::min(std::max(rssi, kChromeRssiMin), kChromeRssiMax);
// Chrome OS UI uses signal quality values set by this method to draw
// network icons. UI code maps |quality| to number of bars as follows:
// [1-25] 1 bar, [26-50] 2 bars, [51-75] 3 bars and [76-100] 4 bars.
// -103->-63 rssi scales to UI quality of 0->100
// i.e. UI scaled_quality = min(max(rssi,-103),-63) / 40 * 100
scaled_quality = (clamped_rssi - kChromeRssiMin) * 100 /
(kChromeRssiMax - kChromeRssiMin);
} else {
// This code path is used when we are not certain about the bandwidth for
// which RSSI is being reported. Fibocom modems may choose to return
// RSSI over 180kHz(RSRP) as RSSI.
// The mappings we desire are: [1-12] 1 bar, [13-24] 2 bars, [25-37] 3 bars
// and [38-100] 4 bars.
// A simple way to accomplish the desired mappings is to scale signal
// strength measurements by 2*x+1. For example: modem manager reports a
// signal strength of 25. After applying our scaling function chrome OS UI
// will receive a reading of 51. 51 maps to an icon with 3 bars on Chrome OS
// UI.
scaled_quality = std::min(100u, 2 * quality + 1);
}
cellular()->HandleNewSignalQuality(static_cast<uint32_t>(scaled_quality));
}
void CellularCapability3gpp::OnFacilityLocksChanged(uint32_t locks) {
bool sim_enabled = !!(locks & MM_MODEM_3GPP_FACILITY_SIM);
if (sim_lock_status_.enabled != sim_enabled) {
sim_lock_status_.enabled = sim_enabled;
OnSimLockStatusChanged();
}
}
void CellularCapability3gpp::OnPcoChanged(const PcoList& pco_list) {
SLOG(this, 3) << __func__;
for (const auto& pco_info : pco_list) {
uint32_t session_id = std::get<0>(pco_info);
bool is_complete = std::get<1>(pco_info);
vector<uint8_t> data = std::get<2>(pco_info);
SLOG(this, 3) << "PCO: session-id=" << session_id
<< ", complete=" << is_complete
<< ", data=" << base::HexEncode(data.data(), data.size())
<< "";
std::unique_ptr<CellularPco> pco = CellularPco::CreateFromRawData(data);
if (!pco) {
LOG(WARNING) << "Failed to parse PCO (session-id " << session_id << ")";
continue;
}
SubscriptionState subscription_state = SubscriptionState::kUnknown;
if (!FindVerizonSubscriptionStateFromPco(*pco, &subscription_state))
continue;
if (subscription_state != SubscriptionState::kUnknown)
OnSubscriptionStateChanged(subscription_state);
}
}
void CellularCapability3gpp::RequestSimProperties(RpcIdentifier sim_path) {
// Ownership if this proxy will be passed to the success callback so that the
// proxy is not destroyed before the asynchronous call completes.
std::unique_ptr<DBusPropertiesProxy> sim_properties_proxy =
control_interface()->CreateDBusPropertiesProxy(
sim_path, cellular()->dbus_service());
auto sim_properties = sim_properties_proxy->GetAll(MM_DBUS_INTERFACE_SIM);
OnGetSimProperties(sim_path, sim_properties);
}
void CellularCapability3gpp::OnGetSimProperties(
RpcIdentifier sim_path, const KeyValueStore& properties) {
if (sim_path == sim_path_)
OnSimPropertiesChanged(properties);
// |sim_properties_proxy| will be safely released here.
}
void CellularCapability3gpp::OnSimPropertiesChanged(
const KeyValueStore& properties) {
SLOG(this, 3) << __func__;
if (properties.Contains<string>(MM_SIM_PROPERTY_SIMIDENTIFIER)) {
OnSimIdentifierChanged(
properties.Get<string>(MM_SIM_PROPERTY_SIMIDENTIFIER));
}
if (properties.Contains<string>(MM_SIM_PROPERTY_EID)) {
cellular()->set_eid(properties.Get<string>(MM_SIM_PROPERTY_EID));
}
if (properties.Contains<string>(MM_SIM_PROPERTY_OPERATORIDENTIFIER)) {
OnOperatorIdChanged(
properties.Get<string>(MM_SIM_PROPERTY_OPERATORIDENTIFIER));
}
if (properties.Contains<string>(MM_SIM_PROPERTY_OPERATORNAME)) {
OnSpnChanged(properties.Get<string>(MM_SIM_PROPERTY_OPERATORNAME));
}
if (properties.Contains<string>(MM_SIM_PROPERTY_IMSI)) {
string imsi = properties.Get<string>(MM_SIM_PROPERTY_IMSI);
cellular()->set_imsi(imsi);
cellular()->home_provider_info()->UpdateIMSI(imsi);
// We do not obtain IMSI OTA right now. Provide the value from the SIM to
// serving operator as well, to aid in MVNO identification.
cellular()->serving_operator_info()->UpdateIMSI(imsi);
}
}
void CellularCapability3gpp::OnSpnChanged(const std::string& spn) {
spn_ = spn;
cellular()->home_provider_info()->UpdateOperatorName(spn);
}
void CellularCapability3gpp::OnSimIdentifierChanged(const string& id) {
cellular()->set_iccid(id);
cellular()->home_provider_info()->UpdateICCID(id);
// Provide ICCID to serving operator as well to aid in MVNO identification.
cellular()->serving_operator_info()->UpdateICCID(id);
UpdateServiceActivationState();
UpdatePendingActivationState();
}
void CellularCapability3gpp::OnOperatorIdChanged(const string& operator_id) {
SLOG(this, 2) << "Operator ID = '" << operator_id << "'";
cellular()->home_provider_info()->UpdateMCCMNC(operator_id);
}
} // namespace shill