diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..ddeb4b12 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: ["-Ibazel-out/k8-fastbuild/bin", "-DNO_WEBRTC"] diff --git a/.gitignore b/.gitignore index 44ad5eb7..a8b34e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,7 @@ bazel-* /.clwb/ /dist/ /sharing/linux/dist-minimal/ -/sharing/linux/app/linuxdeploy.AppImage +/sharing/linux/app/linuxdeploy* # Devcontainers # /.devcontainer/ diff --git a/sharing/linux/app/BUILD b/sharing/linux/app/BUILD index f32dc4e4..3706ee7f 100644 --- a/sharing/linux/app/BUILD +++ b/sharing/linux/app/BUILD @@ -58,6 +58,7 @@ qt_cc_library( "//sharing:transfer_metadata", "//sharing:types", "//sharing/flags/generated:generated_flags", + "//sharing/internal/api:platform", "//sharing/linux:linux_sharing_platform", "//sharing/proto:enums_cc_proto", "@com_google_absl//absl/time", @@ -68,6 +69,32 @@ qt_cc_library( ], ) +cc_test( + name = "backend_test", + size = "small", + srcs = ["backend_test.cc"], + deps = [ + ":backend", + "//sharing:attachments", + "//sharing:transfer_metadata", + "//sharing/internal/api:platform", + "@com_google_googletest//:gtest_main", + "@rules_qt//:qt_core", + "@rules_qt//:qt_gui", + "@rules_qt//:qt_hdrs", + "@rules_qt//:qt_labs_platform", + "@rules_qt//:qt_opengl", + "@rules_qt//:qt_qml", + "@rules_qt//:qt_qml_meta", + "@rules_qt//:qt_qml_workerscript", + "@rules_qt//:qt_quick", + "@rules_qt//:qt_quick_controls2", + "@rules_qt//:qt_quick_layouts", + "@rules_qt//:qt_quick_shapes", + "@rules_qt//:qt_widgets", + ], +) + cc_library( name = "native_file_logging", srcs = ["native_file_logging.cc"], diff --git a/sharing/linux/app/backend.cc b/sharing/linux/app/backend.cc index b190604b..55b24bda 100644 --- a/sharing/linux/app/backend.cc +++ b/sharing/linux/app/backend.cc @@ -30,6 +30,7 @@ namespace { using NearbySharingService = nearby::sharing::NearbySharingService; constexpr std::chrono::seconds kShutdownTimeout(10); +constexpr int kReceiveTimeoutMilliseconds = 30 * 1000; QString ToQString(const std::string& value) { return QString::fromStdString(value); @@ -359,11 +360,19 @@ Backend::Backend(QObject* parent) : QObject(parent), targets_(this), transfers_(this), + platform_( + std::make_unique()), + fast_init_manager_(&platform_->GetFastInitiationManager()), send_transfer_callback_(*this, false), receive_transfer_callback_(*this, true) { + receive_timeout_timer_.setSingleShot(true); + receive_timeout_timer_.setInterval(kReceiveTimeoutMilliseconds); + connect(&receive_timeout_timer_, &QTimer::timeout, this, + &Backend::OnReceiveTimeout); + ConfigureFlags(); service_ = nearby::sharing::NearbySharingServiceFactory::GetInstance() - ->CreateSharingService(platform_, &analytics_recorder_, + ->CreateSharingService(*platform_, &analytics_recorder_, /*event_logger=*/nullptr, /*supports_file_sync=*/false); if (service_ == nullptr) { @@ -389,7 +398,7 @@ Backend::Backend(QObject* parent) status == NearbySharingService::StatusCodes::kOk; backend->ReportStatus(QStringLiteral("Initialize"), status); if (backend->initialized_) { - backend->DriveMode(); + backend->StartFastInitiationMonitoring(); } else { backend->desired_mode_ = Mode::kNone; } @@ -397,6 +406,24 @@ Backend::Backend(QObject* parent) }); } +Backend::Backend(NearbySharingService& service, + nearby::api::FastInitiationManager& fast_init_manager, + int receive_timeout_milliseconds, QObject* parent) + : QObject(parent), + targets_(this), + transfers_(this), + fast_init_manager_(&fast_init_manager), + service_(&service), + send_transfer_callback_(*this, false), + receive_transfer_callback_(*this, true), + initialized_(true) { + receive_timeout_timer_.setSingleShot(true); + receive_timeout_timer_.setInterval(receive_timeout_milliseconds); + connect(&receive_timeout_timer_, &QTimer::timeout, this, + &Backend::OnReceiveTimeout); + StartFastInitiationMonitoring(); +} + Backend::~Backend() { shutdown(); } @@ -406,11 +433,22 @@ QString Backend::hostname() const { } void Backend::startReceive() { - SetDesiredMode(Mode::kReceive); + if (service_ == nullptr || shutting_down_) { + return; + } + StartFastInitiationMonitoring(); } void Backend::startDiscovery() { - SetDesiredMode(Mode::kDiscovery); + if (service_ == nullptr || shutting_down_) { + return; + } + monitoring_requested_ = false; + receive_timeout_timer_.stop(); + receive_window_pending_ = false; + receive_offer_received_ = false; + fallback_receive_window_ = false; + StopFastInitiationScanningForDiscovery(); } bool Backend::sendFile(qint64 share_target_id, const QString& path) { @@ -477,6 +515,12 @@ void Backend::shutdown() { return; } shutting_down_ = true; + monitoring_requested_ = false; + receive_timeout_timer_.stop(); + auto& fast_init_manager = *fast_init_manager_; + if (fast_init_manager.IsScanning()) { + fast_init_manager.StopScanning(nullptr); + } desired_mode_ = Mode::kNone; const auto status = ShutdownService(*service_); if (status != NearbySharingService::StatusCodes::kOk) { @@ -526,6 +570,8 @@ void Backend::OnTransferUpdate( if (receive_mode && transfer.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) { + backend->receive_offer_received_ = true; + backend->receive_timeout_timer_.stop(); emit backend->incomingTransfer(target.id); } }); @@ -628,6 +674,7 @@ void Backend::OnModeStopped(Mode mode, ReportStatus(QStringLiteral("Stop sharing mode"), status); } DriveMode(); + MaybeStartFastInitiationScanning(); } void Backend::OnModeStarted(Mode mode, @@ -638,11 +685,183 @@ void Backend::OnModeStarted(Mode mode, } if (status == NearbySharingService::StatusCodes::kOk) { active_mode_ = mode; + if (mode == Mode::kReceive && receive_window_pending_) { + receive_window_pending_ = false; + receive_offer_received_ = false; + receive_timeout_timer_.start(); + } } else { desired_mode_ = Mode::kNone; + receive_window_pending_ = false; + receive_timeout_timer_.stop(); + if (fallback_receive_window_) { + monitoring_requested_ = false; + } ReportStatus(QStringLiteral("Start sharing mode"), status); } DriveMode(); + MaybeStartFastInitiationScanning(); +} + +void Backend::StartFastInitiationMonitoring() { + monitoring_requested_ = true; + fallback_receive_used_ = false; + fallback_receive_window_ = false; + receive_window_pending_ = false; + receive_offer_received_ = false; + receive_timeout_timer_.stop(); + SetDesiredMode(Mode::kNone); + MaybeStartFastInitiationScanning(); +} + +void Backend::MaybeStartFastInitiationScanning() { + if (!initialized_ || service_ == nullptr || shutting_down_ || + !monitoring_requested_ || fast_init_scan_stop_in_flight_ || + mode_operation_in_flight_ || active_mode_ != Mode::kNone || + desired_mode_ != Mode::kNone) { + return; + } + + if (fallback_receive_window_ && fallback_receive_used_) { + OpenReceiveWindow(/*fallback=*/true); + return; + } + + auto& fast_init_manager = *fast_init_manager_; + if (fast_init_manager.IsScanning()) { + return; + } + + sender_present_ = false; + receive_trigger_armed_ = true; + QPointer backend(this); + fast_init_manager.StartScanning( + [backend]() { + if (backend) { + PostStatus(backend, [backend]() { + if (backend) { + backend->OnFastInitiationDevicesDiscovered(); + } + }); + } + }, + [backend]() { + if (backend) { + PostStatus(backend, [backend]() { + if (backend) { + backend->OnFastInitiationDevicesNotDiscovered(); + } + }); + } + }, + [backend](nearby::api::FastInitiationManager::Error error) { + if (backend) { + PostStatus(backend, [backend, error]() { + if (backend) { + backend->OnFastInitiationScanningError(error); + } + }); + } + }); +} + +void Backend::StopFastInitiationScanningForDiscovery() { + auto& fast_init_manager = *fast_init_manager_; + if (!fast_init_manager.IsScanning()) { + SetDesiredMode(Mode::kDiscovery); + return; + } + if (fast_init_scan_stop_in_flight_) { + return; + } + + fast_init_scan_stop_in_flight_ = true; + QPointer backend(this); + fast_init_manager.StopScanning([backend]() { + if (backend) { + PostStatus(backend, [backend]() { + if (backend) { + backend->OnFastInitiationScanningStoppedForDiscovery(); + } + }); + } + }); +} + +void Backend::OnFastInitiationScanningStoppedForDiscovery() { + fast_init_scan_stop_in_flight_ = false; + sender_present_ = false; + receive_trigger_armed_ = true; + if (!shutting_down_ && !monitoring_requested_) { + SetDesiredMode(Mode::kDiscovery); + } else { + MaybeStartFastInitiationScanning(); + } +} + +void Backend::OnFastInitiationDevicesDiscovered() { + if (shutting_down_ || !monitoring_requested_) { + return; + } + sender_present_ = true; + const bool should_open_receive = receive_trigger_armed_; + receive_trigger_armed_ = false; + if (!should_open_receive || active_mode_ != Mode::kNone || + desired_mode_ != Mode::kNone || mode_operation_in_flight_) { + return; + } + OpenReceiveWindow(/*fallback=*/false); +} + +void Backend::OnFastInitiationDevicesNotDiscovered() { + if (shutting_down_ || !monitoring_requested_) { + return; + } + sender_present_ = false; + receive_trigger_armed_ = true; +} + +void Backend::OnFastInitiationScanningError( + nearby::api::FastInitiationManager::Error error) { + if (shutting_down_ || !monitoring_requested_) { + return; + } + qWarning() << "Fast Initiation scanning failed with error" + << static_cast(error); + if (fallback_receive_used_) { + monitoring_requested_ = false; + return; + } + + fallback_receive_used_ = true; + fallback_receive_window_ = true; + if (active_mode_ == Mode::kReceive || desired_mode_ == Mode::kReceive) { + return; + } + if (active_mode_ == Mode::kNone && desired_mode_ == Mode::kNone && + !mode_operation_in_flight_) { + OpenReceiveWindow(/*fallback=*/true); + } +} + +void Backend::OpenReceiveWindow(bool fallback) { + fallback_receive_window_ = fallback; + receive_window_pending_ = true; + receive_offer_received_ = false; + SetDesiredMode(Mode::kReceive); +} + +void Backend::OnReceiveTimeout() { + if (shutting_down_ || receive_offer_received_ || + active_mode_ != Mode::kReceive) { + return; + } + if (fallback_receive_window_) { + monitoring_requested_ = false; + } + receive_window_pending_ = false; + fallback_receive_window_ = false; + SetDesiredMode(Mode::kNone); } std::function Backend::StatusCallback( diff --git a/sharing/linux/app/backend.h b/sharing/linux/app/backend.h index 7d0b2e59..d05c7fda 100644 --- a/sharing/linux/app/backend.h +++ b/sharing/linux/app/backend.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "QtQmlIntegration/qqmlintegration.h" #include "qtmetamacros.h" @@ -131,6 +132,8 @@ class Backend : public QObject, void outgoingTransferStartFailed(qint64 share_target_id); private: + friend class BackendTestPeer; + using NearbySharingService = nearby::sharing::NearbySharingService; using ShareTarget = nearby::sharing::ShareTarget; using TransferMetadata = nearby::sharing::TransferMetadata; @@ -157,6 +160,10 @@ class Backend : public QObject, kDiscovery, }; + Backend(NearbySharingService& service, + nearby::api::FastInitiationManager& fast_init_manager, + int receive_timeout_milliseconds, QObject* parent); + void OnShareTargetDiscovered(const ShareTarget& target) override; void OnShareTargetUpdated(const ShareTarget& target) override; void OnShareTargetLost(const ShareTarget& target) override; @@ -168,6 +175,16 @@ class Backend : public QObject, void DriveMode(); void OnModeStopped(Mode mode, NearbySharingService::StatusCodes status); void OnModeStarted(Mode mode, NearbySharingService::StatusCodes status); + void StartFastInitiationMonitoring(); + void MaybeStartFastInitiationScanning(); + void StopFastInitiationScanningForDiscovery(); + void OnFastInitiationScanningStoppedForDiscovery(); + void OnFastInitiationDevicesDiscovered(); + void OnFastInitiationDevicesNotDiscovered(); + void OnFastInitiationScanningError( + nearby::api::FastInitiationManager::Error error); + void OpenReceiveWindow(bool fallback); + void OnReceiveTimeout(); std::function StatusCallback( QString operation); void ReportStatus(const QString& operation, @@ -176,13 +193,23 @@ class Backend : public QObject, ShareTargetModel targets_; ShareTransferModel transfers_; nearby::sharing::linux::NoOpAnalyticsRecorder analytics_recorder_; - nearby::sharing::linux::LinuxSharingPlatform platform_; + std::unique_ptr platform_; + nearby::api::FastInitiationManager* fast_init_manager_ = nullptr; NearbySharingService* service_ = nullptr; TransferCallback send_transfer_callback_; TransferCallback receive_transfer_callback_; + QTimer receive_timeout_timer_; Mode active_mode_ = Mode::kNone; - Mode desired_mode_ = Mode::kReceive; + Mode desired_mode_ = Mode::kNone; bool initialized_ = false; bool mode_operation_in_flight_ = false; + bool monitoring_requested_ = true; + bool fast_init_scan_stop_in_flight_ = false; + bool sender_present_ = false; + bool receive_trigger_armed_ = true; + bool receive_window_pending_ = false; + bool receive_offer_received_ = false; + bool fallback_receive_window_ = false; + bool fallback_receive_used_ = false; bool shutting_down_ = false; }; diff --git a/sharing/linux/app/backend_test.cc b/sharing/linux/app/backend_test.cc new file mode 100644 index 00000000..f38c89c9 --- /dev/null +++ b/sharing/linux/app/backend_test.cc @@ -0,0 +1,322 @@ +#include "sharing/linux/app/backend.h" + +#include +#include + +#include +#include +#include "gtest/gtest.h" +#include "sharing/attachment_container.h" +#include "sharing/transfer_metadata_builder.h" + +class BackendTestPeer { + public: + static std::unique_ptr Create( + nearby::sharing::NearbySharingService& service, + nearby::api::FastInitiationManager& fast_init_manager) { + return std::unique_ptr( + new Backend(service, fast_init_manager, + /*receive_timeout_milliseconds=*/5000, nullptr)); + } + + static bool IsIdle(const Backend& backend) { + return backend.active_mode_ == Backend::Mode::kNone; + } + + static bool IsReceiving(const Backend& backend) { + return backend.active_mode_ == Backend::Mode::kReceive; + } + + static bool IsDiscovering(const Backend& backend) { + return backend.active_mode_ == Backend::Mode::kDiscovery; + } + + static bool IsReceiveTimerActive(const Backend& backend) { + return backend.receive_timeout_timer_.isActive(); + } + + static bool IsMonitoringRequested(const Backend& backend) { + return backend.monitoring_requested_; + } + + static void ExpireReceiveWindow(Backend& backend) { + backend.OnReceiveTimeout(); + } +}; + +namespace { + +class TestSharingService final : public nearby::sharing::NearbySharingService { + public: + void AddObserver(Observer*) override {} + void RemoveObserver(Observer*) override {} + void Shutdown(std::function callback) override { + callback(StatusCodes::kOk); + } + void RegisterSendSurface( + nearby::sharing::TransferUpdateCallback*, + nearby::sharing::ShareTargetDiscoveredCallback*, SendSurfaceState, + nearby::sharing::Advertisement::BlockedVendorId, bool, + absl::AnyInvocable callback) override { + send_registered_ = true; + std::move(callback)(StatusCodes::kOk); + } + void UnregisterSendSurface( + nearby::sharing::TransferUpdateCallback*, + absl::AnyInvocable callback) override { + send_registered_ = false; + std::move(callback)(StatusCodes::kOk); + } + void RegisterReceiveSurface( + nearby::sharing::TransferUpdateCallback* callback, ReceiveSurfaceState, + nearby::sharing::Advertisement::BlockedVendorId, + absl::AnyInvocable status_callback) override { + receive_callback_ = callback; + std::move(status_callback)(StatusCodes::kOk); + } + void UnregisterReceiveSurface( + nearby::sharing::TransferUpdateCallback*, + absl::AnyInvocable callback) override { + receive_callback_ = nullptr; + std::move(callback)(StatusCodes::kOk); + } + void ClearForegroundReceiveSurfaces( + absl::AnyInvocable callback) override { + receive_callback_ = nullptr; + std::move(callback)(StatusCodes::kOk); + } + bool IsTransferring() const override { return false; } + bool IsScanning() const override { return send_registered_; } + bool IsBluetoothPresent() const override { return true; } + bool IsBluetoothPowered() const override { return true; } + bool IsExtendedAdvertisingSupported() const override { return true; } + bool IsLanConnected() const override { return true; } + std::string GetQrCodeUrl() const override { return {}; } + void SendAttachments(int64_t, + std::unique_ptr, + std::function callback) override { + callback(StatusCodes::kOk); + } + void Accept(int64_t, std::function callback) override { + callback(StatusCodes::kOk); + } + void Reject(int64_t, std::function callback) override { + callback(StatusCodes::kOk); + } + void Cancel(int64_t, std::function callback) override { + callback(StatusCodes::kOk); + } + void InitiatePairing( + int64_t, nearby::sharing::service::proto::BindingRequest::Type, + absl::AnyInvocable callback) override { + std::move(callback)(StatusCodes::kOk); + } + void SetVisibility( + nearby::sharing::proto::DeviceVisibility, absl::Duration, + absl::AnyInvocable callback) override { + std::move(callback)(StatusCodes::kOk); + } + std::string Dump() const override { return {}; } + void UpdateFilePathsInProgress(bool) override {} + nearby::sharing::NearbyShareSettings* GetSettings() override { + return nullptr; + } + nearby::sharing::NearbyShareCertificateManager* GetCertificateManager() + override { + return nullptr; + } + nearby::sharing::AccountManager* GetAccountManager() override { + return nullptr; + } + nearby::Clock& GetClock() override { std::abort(); } + void SetAlternateServiceUuidForDiscovery(uint16_t) override {} + nearby::sharing::SyncManager& sync_manager() override { std::abort(); } + nearby::sharing::OutgoingTargetsManager& outgoing_targets_manager() override { + std::abort(); + } + void UpdateBackupSavePath( + absl::string_view, absl::string_view, + absl::AnyInvocable callback) override { + std::move(callback)(StatusCodes::kOk); + } + + void FireReceiveTransferUpdate( + const nearby::sharing::ShareTarget& target, + const nearby::sharing::AttachmentContainer& attachments, + const nearby::sharing::TransferMetadata& metadata) { + ASSERT_NE(receive_callback_, nullptr); + receive_callback_->OnTransferUpdate(target, attachments, metadata); + } + + private: + bool send_registered_ = false; + nearby::sharing::TransferUpdateCallback* receive_callback_ = nullptr; +}; + +class TestFastInitiationManager final + : public nearby::api::FastInitiationManager { + public: + void StartAdvertising(nearby::api::FastInitBleBeacon::FastInitType, + std::function callback, + std::function) override { + if (callback) callback(); + } + void StopAdvertising(std::function callback) override { + if (callback) callback(); + } + void StartScanning(std::function discovered_callback, + std::function not_discovered_callback, + std::function error_callback) override { + discovered_callback_ = std::move(discovered_callback); + not_discovered_callback_ = std::move(not_discovered_callback); + error_callback_ = std::move(error_callback); + is_scanning_ = true; + } + void StopScanning(std::function callback) override { + stopped_callback_ = std::move(callback); + is_scanning_ = false; + } + bool IsAdvertising() override { return false; } + bool IsScanning() override { return is_scanning_; } + + void FireDiscovered() { discovered_callback_(); } + void FireNotDiscovered() { not_discovered_callback_(); } + void FireError(Error error) { error_callback_(error); } + void CompleteStop() { + if (stopped_callback_) std::move(stopped_callback_)(); + } + + private: + bool is_scanning_ = false; + std::function discovered_callback_; + std::function not_discovered_callback_; + std::function stopped_callback_; + std::function error_callback_; +}; + +QCoreApplication& TestApplication() { + static int argc = 1; + static char application_name[] = "backend_test"; + static char* argv[] = {application_name, nullptr}; + static QCoreApplication application(argc, argv); + return application; +} + +void DrainEvents() { + static_cast(TestApplication()); + for (int i = 0; i < 4; ++i) { + QCoreApplication::processEvents(QEventLoop::AllEvents); + } +} + +class BackendFastInitiationTest : public testing::Test { + protected: + void SetUp() override { + static_cast(TestApplication()); + backend_ = BackendTestPeer::Create(service_, fast_init_manager_); + } + + void TearDown() override { + backend_.reset(); + DrainEvents(); + } + + TestSharingService service_; + TestFastInitiationManager fast_init_manager_; + std::unique_ptr backend_; +}; + +TEST_F(BackendFastInitiationTest, StartupScansWithoutReceiving) { + EXPECT_TRUE(fast_init_manager_.IsScanning()); + EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_)); + EXPECT_FALSE(BackendTestPeer::IsReceiveTimerActive(*backend_)); +} + +TEST_F(BackendFastInitiationTest, PresenceOpensTimedReceiveWindow) { + fast_init_manager_.FireDiscovered(); + DrainEvents(); + + EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_)); + EXPECT_TRUE(BackendTestPeer::IsReceiveTimerActive(*backend_)); + EXPECT_TRUE(fast_init_manager_.IsScanning()); +} + +TEST_F(BackendFastInitiationTest, + TimeoutRequiresSenderAbsenceBeforeAnotherWindow) { + fast_init_manager_.FireDiscovered(); + DrainEvents(); + BackendTestPeer::ExpireReceiveWindow(*backend_); + DrainEvents(); + ASSERT_TRUE(BackendTestPeer::IsIdle(*backend_)); + + fast_init_manager_.FireDiscovered(); + DrainEvents(); + EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_)); + + fast_init_manager_.FireNotDiscovered(); + fast_init_manager_.FireDiscovered(); + DrainEvents(); + EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_)); +} + +TEST_F(BackendFastInitiationTest, SenderLossDoesNotCloseCurrentWindow) { + fast_init_manager_.FireDiscovered(); + DrainEvents(); + fast_init_manager_.FireNotDiscovered(); + DrainEvents(); + + EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_)); + EXPECT_TRUE(BackendTestPeer::IsReceiveTimerActive(*backend_)); +} + +TEST_F(BackendFastInitiationTest, IncomingOfferCancelsReceiveTimeout) { + fast_init_manager_.FireDiscovered(); + DrainEvents(); + + nearby::sharing::ShareTarget target; + target.id = 1; + auto attachments = nearby::sharing::AttachmentContainer::Builder().Build(); + service_.FireReceiveTransferUpdate( + target, *attachments, + nearby::sharing::TransferMetadataBuilder() + .set_status(nearby::sharing::TransferMetadata::Status:: + kAwaitingLocalConfirmation) + .build()); + DrainEvents(); + + EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_)); + EXPECT_FALSE(BackendTestPeer::IsReceiveTimerActive(*backend_)); +} + +TEST_F(BackendFastInitiationTest, DiscoveryStopsFastInitiationScanning) { + backend_->startDiscovery(); + EXPECT_FALSE(fast_init_manager_.IsScanning()); + EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_)); + + fast_init_manager_.CompleteStop(); + DrainEvents(); + EXPECT_TRUE(BackendTestPeer::IsDiscovering(*backend_)); + + backend_->startReceive(); + DrainEvents(); + EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_)); + EXPECT_TRUE(fast_init_manager_.IsScanning()); +} + +TEST_F(BackendFastInitiationTest, ScanErrorUsesOneFallbackWindowThenIdles) { + fast_init_manager_.StopScanning(nullptr); + fast_init_manager_.FireError( + nearby::api::FastInitiationManager::Error::kHardwareNotSupported); + DrainEvents(); + + ASSERT_TRUE(BackendTestPeer::IsReceiving(*backend_)); + EXPECT_TRUE(BackendTestPeer::IsReceiveTimerActive(*backend_)); + BackendTestPeer::ExpireReceiveWindow(*backend_); + DrainEvents(); + + EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_)); + EXPECT_FALSE(BackendTestPeer::IsMonitoringRequested(*backend_)); + EXPECT_FALSE(fast_init_manager_.IsScanning()); +} + +} // namespace