Commit 260e2831 authored by Kyle Horimoto's avatar Kyle Horimoto Committed by Commit Bot

[CrOS Cellular] Add OtaActivator class.

This CL:
(1) Adds a base OtaActivator class along with an OtaActivatorImpl
    implementation and a FakeOtaActivator for tests.
(2) Adds a test for OtaActivatorImpl.
(3) Updates some definitions from cellular_setup.mojom; specifically,
    this CL changes the ActivationResult enum.
(4) Updates cellular_setup_service_unittest.cc to reflect the changes in
    part 3 above.

Bug: 961084
Change-Id: I2d4bd9cd454014106f120144e28238f07b32a318
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1604213Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarAzeem Arshad <azeemarshad@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Auto-Submit: Kyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662907}
parent 12371733
...@@ -12,27 +12,51 @@ static_library("cellular_setup") { ...@@ -12,27 +12,51 @@ static_library("cellular_setup") {
"cellular_setup_impl.h", "cellular_setup_impl.h",
"cellular_setup_service.cc", "cellular_setup_service.cc",
"cellular_setup_service.h", "cellular_setup_service.h",
"ota_activator.cc",
"ota_activator.h",
"ota_activator_impl.cc",
"ota_activator_impl.h",
] ]
deps = [ deps = [
"//base", "//base",
"//chromeos/dbus/shill",
"//chromeos/network",
"//chromeos/services/cellular_setup/public/mojom", "//chromeos/services/cellular_setup/public/mojom",
"//dbus",
"//services/service_manager/public/cpp", "//services/service_manager/public/cpp",
"//services/service_manager/public/mojom", "//services/service_manager/public/mojom",
] ]
} }
static_library("test_support") {
testonly = true
sources = [
"fake_ota_activator.cc",
"fake_ota_activator.h",
]
deps = [
":cellular_setup",
"//base",
"//chromeos/services/cellular_setup/public/cpp:test_support",
]
}
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"cellular_setup_service_unittest.cc", "cellular_setup_service_unittest.cc",
"ota_activator_impl_unittest.cc",
] ]
deps = [ deps = [
":cellular_setup", ":cellular_setup",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//chromeos/network:test_support",
"//chromeos/services/cellular_setup/public/cpp:test_support", "//chromeos/services/cellular_setup/public/cpp:test_support",
"//services/service_manager/public/cpp/test:test_support", "//services/service_manager/public/cpp/test:test_support",
"//testing/gmock", "//testing/gmock",
......
include_rules = [ include_rules = [
"+dbus/object_path.h",
"+mojo/public/cpp/bindings", "+mojo/public/cpp/bindings",
"+services/service_manager/public", "+services/service_manager/public",
] ]
...@@ -31,6 +31,7 @@ using CarrierPortalHandlerPair = ...@@ -31,6 +31,7 @@ using CarrierPortalHandlerPair =
const char kTestCellularNetworkGuid[] = "testCellularNetworkGuid"; const char kTestCellularNetworkGuid[] = "testCellularNetworkGuid";
const char kTestPaymentUrl[] = "testPaymentUrl"; const char kTestPaymentUrl[] = "testPaymentUrl";
const char kTestPaymentPostData[] = "testPaymentPostData";
const char kTestCarrier[] = "testCarrier"; const char kTestCarrier[] = "testCarrier";
const char kTestMeid[] = "testMeid"; const char kTestMeid[] = "testMeid";
const char kTestImei[] = "testImei"; const char kTestImei[] = "testImei";
...@@ -130,7 +131,8 @@ class CellularSetupServiceTest : public testing::Test { ...@@ -130,7 +131,8 @@ class CellularSetupServiceTest : public testing::Test {
size_t num_elements_before_call = cellular_metadata_list.size(); size_t num_elements_before_call = cellular_metadata_list.size();
GetLastActivationDelegate()->OnActivationStarted( GetLastActivationDelegate()->OnActivationStarted(
mojom::CellularMetadata::New(GURL(kTestPaymentUrl), kTestCarrier, mojom::CellularMetadata::New(GURL(kTestPaymentUrl),
kTestPaymentPostData, kTestCarrier,
kTestMeid, kTestImei, kTestMdn)); kTestMeid, kTestImei, kTestMdn));
GetLastActivationDelegate().FlushForTesting(); GetLastActivationDelegate().FlushForTesting();
...@@ -222,7 +224,8 @@ TEST_F(CellularSetupServiceTest, StartActivation_Success) { ...@@ -222,7 +224,8 @@ TEST_F(CellularSetupServiceTest, StartActivation_Success) {
SendCarrierPortalStatusUpdate( SendCarrierPortalStatusUpdate(
mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment, &pair); mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment, &pair);
NotifyLastDelegateThatActivationFinished(mojom::ActivationResult::kSuccess, NotifyLastDelegateThatActivationFinished(
mojom::ActivationResult::kSuccessfullyStartedActivation,
fake_activation_delegate.get()); fake_activation_delegate.get());
} }
...@@ -259,23 +262,6 @@ TEST_F(CellularSetupServiceTest, StartActivation_ErrorDuringPayment) { ...@@ -259,23 +262,6 @@ TEST_F(CellularSetupServiceTest, StartActivation_ErrorDuringPayment) {
fake_activation_delegate.get()); fake_activation_delegate.get());
} }
TEST_F(CellularSetupServiceTest, StartActivation_TimedOut) {
auto fake_activation_delegate = std::make_unique<FakeActivationDelegate>();
CarrierPortalHandlerPair pair = CallStartActivation(
kTestCellularNetworkGuid, fake_activation_delegate.get());
NotifyLastDelegateThatActivationStarted(fake_activation_delegate.get());
SendCarrierPortalStatusUpdate(
mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser, &pair);
SendCarrierPortalStatusUpdate(
mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment, &pair);
NotifyLastDelegateThatActivationFinished(
mojom::ActivationResult::kTimedOutActivating,
fake_activation_delegate.get());
}
} // namespace cellular_setup } // namespace cellular_setup
} // namespace chromeos } // namespace chromeos
// Copyright 2019 The Chromium 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 "chromeos/services/cellular_setup/fake_ota_activator.h"
#include <utility>
namespace chromeos {
namespace cellular_setup {
FakeOtaActivator::FakeOtaActivator(base::OnceClosure on_finished_callback)
: OtaActivator(std::move(on_finished_callback)) {}
FakeOtaActivator::~FakeOtaActivator() = default;
} // namespace cellular_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_CELLULAR_SETUP_FAKE_OTA_ACTIVATOR_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_SERVICES_CELLULAR_SETUP_FAKE_OTA_ACTIVATOR_H_
#define CHROMEOS_SERVICES_CELLULAR_SETUP_FAKE_OTA_ACTIVATOR_H_
#include "base/callback_forward.h"
#include "base/macros.h"
#include "chromeos/services/cellular_setup/ota_activator.h"
#include "chromeos/services/cellular_setup/public/cpp/fake_carrier_portal_handler.h"
namespace chromeos {
namespace cellular_setup {
// Test OtaActivator implementation.
class FakeOtaActivator : public OtaActivator, public FakeCarrierPortalHandler {
public:
explicit FakeOtaActivator(base::OnceClosure on_finished_callback);
~FakeOtaActivator();
using OtaActivator::InvokeOnFinishedCallback;
private:
DISALLOW_COPY_AND_ASSIGN(FakeOtaActivator);
};
} // namespace cellular_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_CELLULAR_SETUP_FAKE_OTA_ACTIVATOR_H_
// Copyright 2019 The Chromium 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 "chromeos/services/cellular_setup/ota_activator.h"
#include <utility>
namespace chromeos {
namespace cellular_setup {
OtaActivator::OtaActivator(base::OnceClosure on_finished_callback)
: on_finished_callback_(std::move(on_finished_callback)) {}
OtaActivator::~OtaActivator() = default;
mojom::CarrierPortalHandlerPtr OtaActivator::GenerateInterfacePtr() {
mojom::CarrierPortalHandlerPtr interface_ptr;
bindings_.AddBinding(this, mojo::MakeRequest(&interface_ptr));
return interface_ptr;
}
void OtaActivator::InvokeOnFinishedCallback() {
DCHECK(on_finished_callback_);
std::move(on_finished_callback_).Run();
}
} // namespace cellular_setup
} // namespace chromeos
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_H_
#define CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_H_
#include "base/callback.h"
#include "base/macros.h"
#include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.h"
#include "mojo/public/cpp/bindings/binding_set.h"
namespace chromeos {
namespace cellular_setup {
// Activates a cellular SIM using the OTA mechanism. This class makes a single
// attempt at activation, then fires a callback on completion, regardless of
// success or failure. An OtaActivator object can only be used for one
// attempt; to perform a new activation attempt, use a separate OtaActivator
// instance.
class OtaActivator : public mojom::CarrierPortalHandler {
public:
~OtaActivator() override;
mojom::CarrierPortalHandlerPtr GenerateInterfacePtr();
protected:
explicit OtaActivator(base::OnceClosure on_finished_callback);
void InvokeOnFinishedCallback();
base::OnceClosure on_finished_callback_;
mojo::BindingSet<mojom::CarrierPortalHandler> bindings_;
DISALLOW_COPY_AND_ASSIGN(OtaActivator);
};
} // namespace cellular_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_H_
// Copyright 2019 The Chromium 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 "chromeos/services/cellular_setup/ota_activator_impl.h"
#include <sstream>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_forward.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "chromeos/dbus/shill/shill_device_client.h"
#include "chromeos/network/device_state.h"
#include "chromeos/network/network_activation_handler.h"
#include "chromeos/network/network_connection_handler.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "url/gurl.h"
namespace chromeos {
namespace cellular_setup {
namespace {
OtaActivatorImpl::Factory* g_test_factory = nullptr;
void OnModemResetError(const std::string& error_name,
const std::string& error_message) {
NET_LOG(ERROR) << "ShillDeviceClient::Reset() failed. " << error_name << ": "
<< error_message << ".";
}
void OnNetworkConnectionError(
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
NET_LOG(ERROR) << "ConnectToNetwork() failed. Error name: " << error_name;
}
} // namespace
// static
std::unique_ptr<OtaActivator> OtaActivatorImpl::Factory::Create(
mojom::ActivationDelegatePtr activation_delegate,
base::OnceClosure on_finished_callback,
NetworkStateHandler* network_state_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkActivationHandler* network_activation_handler) {
if (g_test_factory) {
return g_test_factory->BuildInstance(
std::move(activation_delegate), std::move(on_finished_callback),
network_state_handler, network_connection_handler,
network_activation_handler);
}
return base::WrapUnique(new OtaActivatorImpl(
std::move(activation_delegate), std::move(on_finished_callback),
network_state_handler, network_connection_handler,
network_activation_handler));
}
// static
void OtaActivatorImpl::Factory::SetFactoryForTesting(Factory* test_factory) {
g_test_factory = test_factory;
}
OtaActivatorImpl::Factory::~Factory() = default;
OtaActivatorImpl::OtaActivatorImpl(
mojom::ActivationDelegatePtr activation_delegate,
base::OnceClosure on_finished_callback,
NetworkStateHandler* network_state_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkActivationHandler* network_activation_handler)
: OtaActivator(std::move(on_finished_callback)),
activation_delegate_(std::move(activation_delegate)),
network_state_handler_(network_state_handler),
network_connection_handler_(network_connection_handler),
network_activation_handler_(network_activation_handler),
weak_ptr_factory_(this) {
network_state_handler_->AddObserver(this, FROM_HERE);
// If |activation_delegate_| becomes disconnected, the activation request is
// considered canceled.
activation_delegate_.set_connection_error_handler(base::BindOnce(
&OtaActivatorImpl::FinishActivationAttempt, base::Unretained(this),
mojom::ActivationResult::kFailedToActivate));
ChangeStateAndAttemptNextStep(State::kWaitingForValidSimToBecomePresent);
}
OtaActivatorImpl::~OtaActivatorImpl() {
// If this object is being deleted but it never finished the flow, consider
// this a failure.
if (state_ != State::kFinished)
FinishActivationAttempt(mojom::ActivationResult::kFailedToActivate);
}
void OtaActivatorImpl::OnCarrierPortalStatusChange(
mojom::CarrierPortalStatus status) {
if (last_carrier_portal_status_) {
NET_LOG(USER) << "OtaActivatorImpl: Carrier portal status updated. "
<< *last_carrier_portal_status_ << " => " << status;
} else {
NET_LOG(USER) << "OtaActivatorImpl: Carrier portal status updated. "
<< "Status: " << status;
}
last_carrier_portal_status_ = status;
AttemptNextActivationStep();
}
void OtaActivatorImpl::NetworkListChanged() {
AttemptNextActivationStep();
}
void OtaActivatorImpl::DeviceListChanged() {
AttemptNextActivationStep();
}
void OtaActivatorImpl::NetworkPropertiesUpdated(const NetworkState* network) {
AttemptNextActivationStep();
}
void OtaActivatorImpl::DevicePropertiesUpdated(const DeviceState* device) {
AttemptNextActivationStep();
}
void OtaActivatorImpl::OnShuttingDown() {
// |network_state_handler_| is shutting down before activation was able to
// complete.
FinishActivationAttempt(mojom::ActivationResult::kFailedToActivate);
}
const DeviceState* OtaActivatorImpl::GetCellularDeviceState() const {
return network_state_handler_->GetDeviceStateByType(
NetworkTypePattern::Cellular());
}
const NetworkState* OtaActivatorImpl::GetCellularNetworkState() const {
// Note: Chrome OS only supports up to one Cellular network at a time. Other
// configurations (e.g., a USB modem dongle in addition to an integrated SIM)
// are not supported.
return network_state_handler_->FirstNetworkByType(
NetworkTypePattern::Cellular());
}
void OtaActivatorImpl::ChangeStateAndAttemptNextStep(State state) {
DCHECK_NE(state, state_);
NET_LOG(DEBUG) << "OtaActivatorImpl: " << state_ << " => " << state << ".";
state_ = state;
AttemptNextActivationStep();
}
void OtaActivatorImpl::AttemptNextActivationStep() {
switch (state_) {
case State::kNotYetStarted:
// The flow either has not yet started; nothing to do.
break;
case State::kWaitingForValidSimToBecomePresent:
AttemptToDiscoverSim();
break;
case State::kWaitingForCellularConnection:
AttemptConnectionToCellularNetwork();
break;
case State::kWaitingForCellularPayment:
AttemptToSendMetadataToDelegate();
break;
case State::kWaitingForActivation:
AttemptToCompleteActivation();
break;
case State::kFinished:
InvokeOnFinishedCallback();
break;
}
}
void OtaActivatorImpl::FinishActivationAttempt(
mojom::ActivationResult activation_result) {
DCHECK(network_state_handler_);
network_state_handler_->RemoveObserver(this, FROM_HERE);
network_state_handler_ = nullptr;
NET_LOG(EVENT) << "Finished attempt with result " << activation_result << ".";
if (activation_delegate_)
activation_delegate_->OnActivationFinished(activation_result);
ChangeStateAndAttemptNextStep(State::kFinished);
}
void OtaActivatorImpl::AttemptToDiscoverSim() {
DCHECK(state_ == State::kWaitingForValidSimToBecomePresent);
const DeviceState* cellular_device = GetCellularDeviceState();
// If the Cellular device is not present, either this machine does not support
// cellular connections or the modem on the device is in the process of
// restarting.
if (!cellular_device)
return;
// If no SIM card is present, it may be due to the fact that some devices do
// not have hardware support for determining whether a SIM has been inserted.
// Restart the modem to see if the SIM is detected when the modem powers back
// on.
if (!cellular_device->sim_present()) {
NET_LOG(DEBUG) << "No SIM detected; restarting modem.";
ShillDeviceClient::Get()->Reset(
dbus::ObjectPath(cellular_device->path()),
base::Bind(&OtaActivatorImpl::AttemptNextActivationStep,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&OnModemResetError));
return;
}
// The device must have the properties required for the activation flow;
// namely, the operator name, MEID, IMEI, and MDN must be available. Return
// and wait to see if DevicePropertiesUpdated() is invoked with valid
// properties.
if (cellular_device->operator_name().empty() ||
cellular_device->meid().empty() || cellular_device->imei().empty() ||
cellular_device->mdn().empty()) {
NET_LOG(DEBUG) << "Insufficient activation data: "
<< "Operator name: " << cellular_device->operator_name()
<< ", MEID: " << cellular_device->meid() << ", "
<< "IMEI: " << cellular_device->imei() << ", "
<< "MDN: " << cellular_device->mdn();
return;
}
ChangeStateAndAttemptNextStep(State::kWaitingForCellularConnection);
}
void OtaActivatorImpl::AttemptConnectionToCellularNetwork() {
DCHECK(state_ == State::kWaitingForCellularConnection);
const NetworkState* cellular_network = GetCellularNetworkState();
// There is no cellular network to be connected; return early and wait for
// NetworkListChanged() to be called if/when one becomes available.
if (!cellular_network)
return;
// If the network is already activated, there is no need to complete the rest
// of the flow.
if (cellular_network->activation_state() ==
shill::kActivationStateActivated) {
FinishActivationAttempt(mojom::ActivationResult::kAlreadyActivated);
return;
}
// The network must have payment information; at minimum, a payment URL is
// required in order to contact the carrier payment portal. Return and wait to
// see if NetworkPropertiesUpdated() is invoked with valid properties.
if (cellular_network->payment_url().empty()) {
NET_LOG(DEBUG) << "Insufficient activation data: "
<< "Payment URL: " << cellular_network->payment_url() << ", "
<< "Post Data: " << cellular_network->payment_post_data();
return;
}
// The network is disconnected; trigger a connection and wait for
// NetworkPropertiesUpdated() to be called when the network connects.
if (!cellular_network->IsConnectingOrConnected()) {
network_connection_handler_->ConnectToNetwork(
cellular_network->path(), base::DoNothing(),
base::Bind(&OnNetworkConnectionError), false /* check_error_state */,
ConnectCallbackMode::ON_STARTED);
return;
}
// The network is connecting; return early and wait for
// NetworkPropertiesUpdated() to be called if/when the network connects.
if (cellular_network->IsConnectingState())
return;
ChangeStateAndAttemptNextStep(State::kWaitingForCellularPayment);
}
void OtaActivatorImpl::AttemptToSendMetadataToDelegate() {
DCHECK(state_ == State::kWaitingForCellularPayment);
// Metadata should only be sent to the delegate once.
if (!has_sent_metadata_) {
has_sent_metadata_ = true;
const DeviceState* cellular_device = GetCellularDeviceState();
const NetworkState* cellular_network = GetCellularNetworkState();
NET_LOG(DEBUG) << "Sending CellularMetadata. "
<< "Payment URL: " << cellular_network->payment_url() << ", "
<< "Post data: " << cellular_network->payment_post_data()
<< ", Carrier: " << cellular_device->operator_name() << ", "
<< "MEID: " << cellular_device->meid() << ", "
<< "IMEI: " << cellular_device->imei() << ", "
<< "MDN: " << cellular_device->mdn();
activation_delegate_->OnActivationStarted(mojom::CellularMetadata::New(
GURL(cellular_network->payment_url()),
cellular_network->payment_post_data(), cellular_device->operator_name(),
cellular_device->meid(), cellular_device->imei(),
cellular_device->mdn()));
}
// The user must successfully pay via the carrier portal before continuing.
if (last_carrier_portal_status_ !=
mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment) {
return;
}
ChangeStateAndAttemptNextStep(State::kWaitingForActivation);
}
void OtaActivatorImpl::AttemptToCompleteActivation() {
DCHECK(state_ == State::kWaitingForActivation);
// CompleteActivation() should only be called once.
if (has_called_complete_activation_)
return;
has_called_complete_activation_ = true;
network_activation_handler_->CompleteActivation(
GetCellularNetworkState()->path(),
base::Bind(&OtaActivatorImpl::FinishActivationAttempt,
weak_ptr_factory_.GetWeakPtr(),
mojom::ActivationResult::kSuccessfullyStartedActivation),
base::Bind(&OtaActivatorImpl::OnCompleteActivationError,
weak_ptr_factory_.GetWeakPtr()));
}
void OtaActivatorImpl::OnCompleteActivationError(
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
NET_LOG(ERROR) << "CompleteActivation() failed. Error name: " << error_name;
FinishActivationAttempt(mojom::ActivationResult::kFailedToActivate);
}
void OtaActivatorImpl::FlushForTesting() {
if (activation_delegate_)
activation_delegate_.FlushForTesting();
}
std::ostream& operator<<(std::ostream& stream,
const OtaActivatorImpl::State& state) {
switch (state) {
case OtaActivatorImpl::State::kNotYetStarted:
stream << "[Not yet started]";
break;
case OtaActivatorImpl::State::kWaitingForValidSimToBecomePresent:
stream << "[Waiting for SIM to become present]";
break;
case OtaActivatorImpl::State::kWaitingForCellularConnection:
stream << "[Waiting for connected cellular network]";
break;
case OtaActivatorImpl::State::kWaitingForCellularPayment:
stream << "[Waiting cellular payment payment to complete]";
break;
case OtaActivatorImpl::State::kWaitingForActivation:
stream << "[Waiting for Shill activation to complete]";
break;
case OtaActivatorImpl::State::kFinished:
stream << "[Finished]";
break;
}
return stream;
}
} // namespace cellular_setup
} // namespace chromeos
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_IMPL_H_
#define CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_IMPL_H_
#include <memory>
#include <ostream>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/network/network_state_handler_observer.h"
#include "chromeos/services/cellular_setup/ota_activator.h"
#include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.h"
namespace base {
class DictionaryValue;
} // namespace base
namespace chromeos {
class NetworkActivationHandler;
class NetworkConnectionHandler;
class NetworkState;
class NetworkStateHandler;
namespace cellular_setup {
// Concrete OtaActivator implementation. This class activates a SIM using the
// following steps:
// (1) Find a valid SIM in the device. In this context, a SIM is only valid if
// it is present in the machine and has an associated carrier, MEID, IMEI,
// and MDN. If a valid SIM is not present, this class reboots the modem to
// see if the SIM can be detected after a restart.
// (2) Ensure an eligible cellular connection is active. In this context, a
// cellular network is only eligible for activation if it has associated
// payment metadata which can be provided to the carrier portal. If such
// a network is available, this class connects to that network.
// (3) Wait for carrier payment to complete. This class impelments
// CarrierPortalHandler to receive updates about the payment status.
// (4) Complete activation via Shill.
class OtaActivatorImpl : public OtaActivator,
public NetworkStateHandlerObserver {
public:
class Factory {
public:
static std::unique_ptr<OtaActivator> Create(
mojom::ActivationDelegatePtr activation_delegate,
base::OnceClosure on_finished_callback,
NetworkStateHandler* network_state_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkActivationHandler* network_activation_handler);
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<OtaActivator> BuildInstance(
mojom::ActivationDelegatePtr activation_delegate,
base::OnceClosure on_finished_callback,
NetworkStateHandler* network_state_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkActivationHandler* network_activation_handler) = 0;
};
~OtaActivatorImpl() override;
private:
friend class CellularSetupOtaActivatorImplTest;
enum class State {
kNotYetStarted,
kWaitingForValidSimToBecomePresent,
kWaitingForCellularConnection,
kWaitingForCellularPayment,
kWaitingForActivation,
kFinished
};
friend std::ostream& operator<<(std::ostream& stream, const State& state);
OtaActivatorImpl(mojom::ActivationDelegatePtr activation_delegate,
base::OnceClosure on_finished_callback,
NetworkStateHandler* network_state_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkActivationHandler* network_activation_handler);
// mojom::CarrierPortalHandler:
void OnCarrierPortalStatusChange(mojom::CarrierPortalStatus status) override;
// NetworkStateHandlerObserver:
void NetworkListChanged() override;
void DeviceListChanged() override;
void NetworkPropertiesUpdated(const NetworkState* network) override;
void DevicePropertiesUpdated(const DeviceState* device) override;
void OnShuttingDown() override;
const DeviceState* GetCellularDeviceState() const;
const NetworkState* GetCellularNetworkState() const;
void ChangeStateAndAttemptNextStep(State state);
void AttemptNextActivationStep();
void FinishActivationAttempt(mojom::ActivationResult activation_result);
void AttemptToDiscoverSim();
void AttemptConnectionToCellularNetwork();
void AttemptToSendMetadataToDelegate();
void AttemptToCompleteActivation();
void OnCompleteActivationError(
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data);
void FlushForTesting();
mojom::ActivationDelegatePtr activation_delegate_;
NetworkStateHandler* network_state_handler_;
NetworkConnectionHandler* network_connection_handler_;
NetworkActivationHandler* network_activation_handler_;
State state_ = State::kNotYetStarted;
base::Optional<mojom::CarrierPortalStatus> last_carrier_portal_status_;
bool has_sent_metadata_ = false;
bool has_called_complete_activation_ = false;
base::WeakPtrFactory<OtaActivatorImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(OtaActivatorImpl);
};
std::ostream& operator<<(std::ostream& stream,
const OtaActivatorImpl::State& state);
} // namespace cellular_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_CELLULAR_SETUP_OTA_ACTIVATOR_IMPL_H_
// Copyright 2019 The Chromium 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 "chromeos/services/cellular_setup/ota_activator_impl.h"
#include <memory>
#include <utility>
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/network/fake_network_activation_handler.h"
#include "chromeos/network/fake_network_connection_handler.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_state_test_helper.h"
#include "chromeos/services/cellular_setup/public/cpp/fake_activation_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace chromeos {
namespace cellular_setup {
namespace {
const char kTestCellularDevicePath[] = "/device/wwan0";
const char kTestCellularDeviceName[] = "testDeviceName";
const char kTestCellularDeviceCarrier[] = "testDeviceCarrier";
const char kTestCellularDeviceMeid[] = "testDeviceMeid";
const char kTestCellularDeviceImei[] = "testDeviceImei";
const char kTestCellularDeviceMdn[] = "testDeviceMdn";
const char kTestCellularServicePath[] = "/service/cellular0";
const char kTestCellularServiceGuid[] = "testServiceGuid";
const char kTestCellularServiceName[] = "testServiceName";
const char kTestCellularServicePaymentUrl[] = "testServicePaymentUrl.com";
const char kTestCellularServicePaymentPostData[] = "testServicePostData";
const char kPaymentPortalMethodPost[] = "POST";
} // namespace
class CellularSetupOtaActivatorImplTest : public testing::Test {
protected:
CellularSetupOtaActivatorImplTest()
: test_helper_(false /* use_default_devices_and_services */) {}
~CellularSetupOtaActivatorImplTest() override = default;
// testing::Test:
void SetUp() override {
fake_activation_delegate_ = std::make_unique<FakeActivationDelegate>();
fake_network_connection_handler_ =
std::make_unique<FakeNetworkConnectionHandler>();
fake_network_activation_handler_ =
std::make_unique<FakeNetworkActivationHandler>();
}
void BuildOtaActivator() {
ota_activator_ = OtaActivatorImpl::Factory::Create(
fake_activation_delegate_->GenerateInterfacePtr(),
base::BindOnce(&CellularSetupOtaActivatorImplTest::OnFinished,
base::Unretained(this)),
test_helper_.network_state_handler(),
fake_network_connection_handler_.get(),
fake_network_activation_handler_.get());
carrier_portal_handler_ptr_ = ota_activator_->GenerateInterfacePtr();
}
void AddCellularDevice(bool has_valid_sim) {
ShillDeviceClient::TestInterface* device_test = test_helper_.device_test();
device_test->AddDevice(kTestCellularDevicePath, shill::kTypeCellular,
kTestCellularDeviceName);
if (has_valid_sim) {
device_test->SetDeviceProperty(
kTestCellularDevicePath, shill::kSIMPresentProperty,
base::Value(true), false /* notify_changed */);
base::DictionaryValue home_provider;
home_provider.SetString(shill::kOperatorNameKey,
kTestCellularDeviceCarrier);
device_test->SetDeviceProperty(kTestCellularDevicePath,
shill::kHomeProviderProperty,
home_provider, false /* notify_changed */);
device_test->SetDeviceProperty(
kTestCellularDevicePath, shill::kMeidProperty,
base::Value(kTestCellularDeviceMeid), false /* notify_changed */);
device_test->SetDeviceProperty(
kTestCellularDevicePath, shill::kImeiProperty,
base::Value(kTestCellularDeviceImei), false /* notify_changed */);
device_test->SetDeviceProperty(
kTestCellularDevicePath, shill::kMdnProperty,
base::Value(kTestCellularDeviceMdn), false /* notify_changed */);
}
base::RunLoop().RunUntilIdle();
}
void RemoveCellularDevice() {
test_helper_.device_test()->RemoveDevice(kTestCellularDevicePath);
base::RunLoop().RunUntilIdle();
}
void AddCellularNetwork(bool has_valid_payment_info,
bool is_connected,
bool is_already_activated) {
ShillServiceClient::TestInterface* service_test =
test_helper_.service_test();
service_test->AddService(
kTestCellularServicePath, kTestCellularServiceGuid,
kTestCellularServiceName, shill::kTypeCellular,
is_connected ? shill::kStateOnline : shill::kStateIdle, true);
service_test->SetServiceProperty(
kTestCellularServicePath, shill::kActivationStateProperty,
is_already_activated
? base::Value(shill::kActivationStateActivated)
: base::Value(shill::kActivationStateNotActivated));
if (has_valid_payment_info) {
base::DictionaryValue payment_portal;
payment_portal.SetString(shill::kPaymentPortalURL,
kTestCellularServicePaymentUrl);
payment_portal.SetString(shill::kPaymentPortalMethod,
kPaymentPortalMethodPost);
payment_portal.SetString(shill::kPaymentPortalPostData,
kTestCellularServicePaymentPostData);
service_test->SetServiceProperty(kTestCellularServicePath,
shill::kPaymentPortalProperty,
payment_portal);
}
base::RunLoop().RunUntilIdle();
}
void RemoveCellularNetwork() {
test_helper_.service_test()->RemoveService(kTestCellularServicePath);
base::RunLoop().RunUntilIdle();
}
void FlushForTesting() {
static_cast<OtaActivatorImpl*>(ota_activator_.get())->FlushForTesting();
}
void VerifyCellularMetadataReceivedByDelegate() {
const std::vector<mojom::CellularMetadataPtr>& cellular_metadata_list =
fake_activation_delegate_->cellular_metadata_list();
ASSERT_EQ(1u, cellular_metadata_list.size());
EXPECT_EQ(GURL(kTestCellularServicePaymentUrl),
cellular_metadata_list[0]->payment_url);
EXPECT_EQ(kTestCellularDeviceCarrier, cellular_metadata_list[0]->carrier);
EXPECT_EQ(kTestCellularDeviceMeid, cellular_metadata_list[0]->meid);
EXPECT_EQ(kTestCellularDeviceImei, cellular_metadata_list[0]->imei);
EXPECT_EQ(kTestCellularDeviceMdn, cellular_metadata_list[0]->mdn);
}
void UpdateCarrierPortalState(
mojom::CarrierPortalStatus carrier_portal_status) {
carrier_portal_handler_ptr_->OnCarrierPortalStatusChange(
carrier_portal_status);
carrier_portal_handler_ptr_.FlushForTesting();
}
void ConnectCellularNetwork() {
const std::vector<FakeNetworkConnectionHandler::ConnectionParams>&
connect_calls = fake_network_connection_handler_->connect_calls();
ASSERT_LE(1u, connect_calls.size());
// A connection should have been requested by |ota_activator_|.
EXPECT_EQ(kTestCellularServicePath, connect_calls[0].service_path());
// Simulate the connection succeeding.
test_helper_.service_test()->SetServiceProperty(
kTestCellularServicePath, shill::kStateProperty,
base::Value(shill::kStateOnline));
base::RunLoop().RunUntilIdle();
}
void InvokePendingActivationCallback(bool success) {
const std::vector<FakeNetworkActivationHandler::ActivationParams>&
complete_activation_calls =
fake_network_activation_handler_->complete_activation_calls();
ASSERT_EQ(1u, complete_activation_calls.size());
EXPECT_EQ(kTestCellularServicePath,
complete_activation_calls[0].service_path());
if (success) {
complete_activation_calls[0].InvokeSuccessCallback();
} else {
complete_activation_calls[0].InvokeErrorCallback(
"error", nullptr /* error_data */);
}
}
void VerifyActivationFinished(mojom::ActivationResult activation_result) {
const std::vector<mojom::ActivationResult>& activation_results =
fake_activation_delegate_->activation_results();
ASSERT_EQ(1u, activation_results.size());
EXPECT_EQ(activation_result, activation_results[0]);
EXPECT_TRUE(is_finished_);
}
void DisconnectDelegate() { fake_activation_delegate_->DisconnectBindings(); }
bool is_finished() { return is_finished_; }
private:
void OnFinished() {
EXPECT_FALSE(is_finished_);
is_finished_ = true;
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
NetworkStateTestHelper test_helper_;
std::unique_ptr<FakeActivationDelegate> fake_activation_delegate_;
std::unique_ptr<FakeNetworkConnectionHandler>
fake_network_connection_handler_;
std::unique_ptr<FakeNetworkActivationHandler>
fake_network_activation_handler_;
std::unique_ptr<OtaActivator> ota_activator_;
mojom::CarrierPortalHandlerPtr carrier_portal_handler_ptr_;
bool is_finished_ = false;
DISALLOW_COPY_AND_ASSIGN(CellularSetupOtaActivatorImplTest);
};
TEST_F(CellularSetupOtaActivatorImplTest, Success) {
AddCellularDevice(true /* has_valid_sim */);
AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
false /* is_already_activated */);
BuildOtaActivator();
FlushForTesting();
VerifyCellularMetadataReceivedByDelegate();
UpdateCarrierPortalState(
mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
UpdateCarrierPortalState(
mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment);
InvokePendingActivationCallback(true /* success */);
FlushForTesting();
VerifyActivationFinished(
mojom::ActivationResult::kSuccessfullyStartedActivation);
}
TEST_F(CellularSetupOtaActivatorImplTest,
SimAndPaymentInfoNotInitiallyPresent_AndNetworkNotConnected) {
AddCellularDevice(false /* has_valid_sim */);
AddCellularNetwork(false /* has_valid_payment_info */,
false /* is_connected */,
false /* is_already_activated */);
BuildOtaActivator();
RemoveCellularDevice();
RemoveCellularNetwork();
AddCellularDevice(true /* has_valid_sim */);
AddCellularNetwork(true /* has_valid_payment_info */,
false /* is_connected */,
false /* is_already_activated */);
ConnectCellularNetwork();
FlushForTesting();
VerifyCellularMetadataReceivedByDelegate();
UpdateCarrierPortalState(
mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
UpdateCarrierPortalState(
mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment);
InvokePendingActivationCallback(true /* success */);
FlushForTesting();
VerifyActivationFinished(
mojom::ActivationResult::kSuccessfullyStartedActivation);
}
TEST_F(CellularSetupOtaActivatorImplTest, AlreadyActivated) {
AddCellularDevice(true /* has_valid_sim */);
AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
true /* is_already_activated */);
BuildOtaActivator();
FlushForTesting();
VerifyActivationFinished(mojom::ActivationResult::kAlreadyActivated);
}
TEST_F(CellularSetupOtaActivatorImplTest, DelegateBecomesDisconnected) {
AddCellularDevice(true /* has_valid_sim */);
AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
false /* is_already_activated */);
BuildOtaActivator();
DisconnectDelegate();
FlushForTesting();
// Note: Cannot check the ActivationResult received by the delegate because
// the delegate was disconnected and did not receive the result.
EXPECT_TRUE(is_finished());
}
} // namespace cellular_setup
} // namespace chromeos
...@@ -18,6 +18,10 @@ mojom::ActivationDelegatePtr FakeActivationDelegate::GenerateInterfacePtr() { ...@@ -18,6 +18,10 @@ mojom::ActivationDelegatePtr FakeActivationDelegate::GenerateInterfacePtr() {
return interface_ptr; return interface_ptr;
} }
void FakeActivationDelegate::DisconnectBindings() {
bindings_.CloseAllBindings();
}
void FakeActivationDelegate::OnActivationStarted( void FakeActivationDelegate::OnActivationStarted(
mojom::CellularMetadataPtr cellular_metadata) { mojom::CellularMetadataPtr cellular_metadata) {
cellular_metadata_list_.push_back(std::move(cellular_metadata)); cellular_metadata_list_.push_back(std::move(cellular_metadata));
......
...@@ -22,6 +22,7 @@ class FakeActivationDelegate : public mojom::ActivationDelegate { ...@@ -22,6 +22,7 @@ class FakeActivationDelegate : public mojom::ActivationDelegate {
~FakeActivationDelegate() override; ~FakeActivationDelegate() override;
mojom::ActivationDelegatePtr GenerateInterfacePtr(); mojom::ActivationDelegatePtr GenerateInterfacePtr();
void DisconnectBindings();
const std::vector<mojom::CellularMetadataPtr>& cellular_metadata_list() const std::vector<mojom::CellularMetadataPtr>& cellular_metadata_list()
const { const {
......
...@@ -25,9 +25,16 @@ enum CarrierPortalStatus { ...@@ -25,9 +25,16 @@ enum CarrierPortalStatus {
// Potential results for an activation attempt. // Potential results for an activation attempt.
enum ActivationResult { enum ActivationResult {
kSuccess, // Activation was initiated successfully by the attempt. Note that the device
// may not be fully activated by the time this result occurs since the process
// completes in the background.
kSuccessfullyStartedActivation,
// Activation was unnecessary because the SIM is already activated.
kAlreadyActivated,
// Activation failed (e.g., due to a failure within Shill).
kFailedToActivate, kFailedToActivate,
kTimedOutActivating
}; };
// Metadata corresponding to a cellular activation request which allows the // Metadata corresponding to a cellular activation request which allows the
...@@ -37,6 +44,9 @@ struct CellularMetadata { ...@@ -37,6 +44,9 @@ struct CellularMetadata {
// carrier-based payment flow. // carrier-based payment flow.
url.mojom.Url payment_url; url.mojom.Url payment_url;
// Data to be passed to the payment portal to verify the SIM.
string payment_post_data;
// Human-readable name of the carrier associated with the SIM card. // Human-readable name of the carrier associated with the SIM card.
string carrier; string carrier;
...@@ -60,11 +70,11 @@ interface CarrierPortalHandler { ...@@ -60,11 +70,11 @@ interface CarrierPortalHandler {
// Delegate which is notified when activation starts and finishes. Used by the // Delegate which is notified when activation starts and finishes. Used by the
// service to notify the UI of the state of the activation flow. // service to notify the UI of the state of the activation flow.
interface ActivationDelegate { interface ActivationDelegate {
// Called when activation has started; provides relevant metadata for // Called when the flow has started; only called when the device is eligible
// activation. // for activation (i.e., has a valid, unactivated SIM).
OnActivationStarted(CellularMetadata metadata); OnActivationStarted(CellularMetadata metadata);
// Called when activation finishes, regardless of success or failure. // Called when the flow has finished, regardless of success or failure.
OnActivationFinished(ActivationResult result); OnActivationFinished(ActivationResult result);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment