| // Copyright 2017 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/components/tether/host_scan_scheduler_impl.h" |
| |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/components/multidevice/logging/logging.h" |
| #include "chromeos/network/network_handler.h" |
| #include "chromeos/network/network_state.h" |
| #include "chromeos/network/network_state_handler.h" |
| #include "chromeos/network/network_type_pattern.h" |
| #include "components/session_manager/core/session_manager.h" |
| |
| namespace chromeos { |
| |
| namespace 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::ThreadTaskRunnerHandle::Get()), |
| is_screen_locked_(session_manager_->IsScreenLocked()), |
| weak_ptr_factory_(this) { |
| network_state_handler_->AddObserver(this, FROM_HERE); |
| host_scanner_->AddObserver(this); |
| session_manager_->AddObserver(this); |
| } |
| |
| HostScanSchedulerImpl::~HostScanSchedulerImpl() { |
| network_state_handler_->SetTetherScanState(false); |
| network_state_handler_->RemoveObserver(this, FROM_HERE); |
| 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 chromeos::NetworkTypePattern network_type_pattern = |
| chromeos::switches::ShouldTetherHostScansIgnoreWiredConnections() |
| ? chromeos::NetworkTypePattern::Wireless() |
| : chromeos::NetworkTypePattern::Default(); |
| const chromeos::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::ScanFinished() { |
| network_state_handler_->SetTetherScanState(false); |
| |
| last_scan_end_timestamp_ = clock_->Now(); |
| host_scan_batch_timer_->Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kMaxNumSecondsBetweenBatchScans), |
| base::Bind(&HostScanSchedulerImpl::LogHostScanBatchMetric, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void 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::TimeDelta::FromSeconds(kMinScanMetricSeconds) /* min */, |
| base::TimeDelta::FromDays(kMaxScanMetricsDays) /* max */, |
| kNumMetricsBuckets /* bucket_count */); |
| |
| PA_LOG(VERBOSE) << "Logging host scan batch duration. Duration was " |
| << batch_duration.InSeconds() << " seconds."; |
| } |
| |
| } // namespace tether |
| |
| } // namespace chromeos |