// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/tether/host_scan_scheduler_impl.h"
#include <memory>
#include "ash/constants/ash_switches.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_clock.h"
#include "base/trace_event/trace_event.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "components/session_manager/core/session_manager.h"
namespace ash::tether {
namespace {
// The InstantTethering.HostScanBatchDuration metric meaures the duration of a
// batch of host scans. A "batch" of host scans here refers to a group of host
// scans which occur just after each other. For example, two 30-second scans
// with a 5-second gap between them are considered a single 65-second batch host
// scan for this metric. For two back-to-back scans to be considered part of the
// same batch metric, they must be at most kMaxNumSecondsBetweenBatchScans
// seconds apart.
const int64_t kMaxNumSecondsBetweenBatchScans = 60;
// Minimum value for the scan length metric.
const int64_t kMinScanMetricSeconds = 1;
// Maximum value for the scan length metric.
const int64_t kMaxScanMetricsDays = 1;
// Number of buckets in the metric.
const int kNumMetricsBuckets = 1000;
} // namespace
HostScanSchedulerImpl::HostScanSchedulerImpl(
NetworkStateHandler* network_state_handler,
HostScanner* host_scanner,
session_manager::SessionManager* session_manager)
: network_state_handler_(network_state_handler),
host_scanner_(host_scanner),
session_manager_(session_manager),
host_scan_batch_timer_(std::make_unique<base::OneShotTimer>()),
clock_(base::DefaultClock::GetInstance()),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
is_screen_locked_(session_manager_->IsScreenLocked()) {
network_state_handler_observer_.Observe(network_state_handler_.get());
host_scanner_->AddObserver(this);
session_manager_->AddObserver(this);
}
HostScanSchedulerImpl::~HostScanSchedulerImpl() {
network_state_handler_->SetTetherScanState(false);
host_scanner_->RemoveObserver(this);
session_manager_->RemoveObserver(this);
// If the most recent batch of host scans has already been logged, return
// early.
if (!host_scanner_->IsScanActive() && !host_scan_batch_timer_->IsRunning())
return;
// If a scan is still active during shutdown, there is not enough time to wait
// for the scan to finish before logging its full duration. Instead, mark the
// current time as the end of the scan so that it can be logged.
if (host_scanner_->IsScanActive())
last_scan_end_timestamp_ = clock_->Now();
LogHostScanBatchMetric();
}
void HostScanSchedulerImpl::AttemptScanIfOffline() {
const NetworkTypePattern network_type_pattern =
switches::ShouldTetherHostScansIgnoreWiredConnections()
? NetworkTypePattern::Wireless()
: NetworkTypePattern::Default();
const NetworkState* first_network =
network_state_handler_->FirstNetworkByType(network_type_pattern);
if (IsOnlineOrHasActiveTetherConnection(first_network)) {
PA_LOG(VERBOSE) << "Skipping scan attempt because the device is already "
"connected to a network.";
return;
}
AttemptScan();
}
void HostScanSchedulerImpl::DefaultNetworkChanged(const NetworkState* network) {
// If there is an active (i.e., connecting or connected) network, there is
// no need to schedule a scan.
if (IsOnlineOrHasActiveTetherConnection(network)) {
return;
}
// Schedule a scan as part of a new task. Posting a task here ensures that
// processing the default network change is done after other
// NetworkStateHandlerObservers are finished running. Processing the
// network change immediately can cause crashes; see
// https://crbug.com/800370.
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&HostScanSchedulerImpl::AttemptScan,
weak_ptr_factory_.GetWeakPtr()));
}
void HostScanSchedulerImpl::ScanRequested(const NetworkTypePattern& type) {
if (NetworkTypePattern::Tether().MatchesPattern(type))
AttemptScan();
}
void HostScanSchedulerImpl::OnShuttingDown() {
network_state_handler_observer_.Reset();
}
void HostScanSchedulerImpl::ScanFinished() {
network_state_handler_->SetTetherScanState(false);
last_scan_end_timestamp_ = clock_->Now();
host_scan_batch_timer_->Start(
FROM_HERE, base::Seconds(kMaxNumSecondsBetweenBatchScans),
base::BindOnce(&HostScanSchedulerImpl::LogHostScanBatchMetric,
weak_ptr_factory_.GetWeakPtr()));
}
void HostScanSchedulerImpl::OnSessionStateChanged() {
TRACE_EVENT0("login", "HostScanSchedulerImpl::OnSessionStateChanged");
bool was_screen_locked = is_screen_locked_;
is_screen_locked_ = session_manager_->IsScreenLocked();
if (is_screen_locked_) {
// If the screen is now locked, stop any ongoing scan.
host_scanner_->StopScan();
return;
}
if (!was_screen_locked)
return;
// If the device was just unlocked, start a scan if not already connected to
// a network.
AttemptScanIfOffline();
}
void HostScanSchedulerImpl::SetTestDoubles(
std::unique_ptr<base::OneShotTimer> test_host_scan_batch_timer,
base::Clock* test_clock,
scoped_refptr<base::TaskRunner> test_task_runner) {
host_scan_batch_timer_ = std::move(test_host_scan_batch_timer);
clock_ = test_clock;
task_runner_ = test_task_runner;
}
void HostScanSchedulerImpl::AttemptScan() {
// If already scanning, there is nothing to do.
if (host_scanner_->IsScanActive())
return;
// If the screen is locked, a host scan should not occur.
if (session_manager_->IsScreenLocked()) {
PA_LOG(VERBOSE) << "Skipping scan attempt because the screen is locked.";
return;
}
// If the timer is running, this new scan is part of the same batch as the
// previous scan, so the timer should be stopped (it will be restarted after
// the new scan finishes). If the timer is not running, the new scan is part
// of a new batch, so the start timestamp should be recorded.
if (host_scan_batch_timer_->IsRunning())
host_scan_batch_timer_->Stop();
else
last_scan_batch_start_timestamp_ = clock_->Now();
host_scanner_->StartScan();
network_state_handler_->SetTetherScanState(true);
}
bool HostScanSchedulerImpl::IsTetherNetworkConnectingOrConnected() {
return network_state_handler_->ConnectingNetworkByType(
NetworkTypePattern::Tether()) ||
network_state_handler_->ConnectedNetworkByType(
NetworkTypePattern::Tether());
}
bool HostScanSchedulerImpl::IsOnlineOrHasActiveTetherConnection(
const NetworkState* default_network) {
return (default_network && default_network->IsConnectingOrConnected()) ||
IsTetherNetworkConnectingOrConnected();
}
void HostScanSchedulerImpl::LogHostScanBatchMetric() {
DCHECK(!last_scan_batch_start_timestamp_.is_null());
DCHECK(!last_scan_end_timestamp_.is_null());
base::TimeDelta batch_duration =
last_scan_end_timestamp_ - last_scan_batch_start_timestamp_;
UMA_HISTOGRAM_CUSTOM_TIMES("InstantTethering.HostScanBatchDuration",
batch_duration,
base::Seconds(kMinScanMetricSeconds) /* min */,
base::Days(kMaxScanMetricsDays) /* max */,
kNumMetricsBuckets /* bucket_count */);
PA_LOG(VERBOSE) << "Logging host scan batch duration. Duration was "
<< batch_duration.InSeconds() << " seconds.";
}
} // namespace ash::tether