// Copyright 2015 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/proximity_auth/proximity_monitor_impl.h"
#include <math.h>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/proximity_auth/metrics.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/client_channel.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
namespace proximity_auth {
// The time to wait, in milliseconds, between proximity polling iterations.
const int kPollingTimeoutMs = 250;
// The weight of the most recent RSSI sample.
const double kRssiSampleWeight = 0.3;
// RSSI must be greater than this value to consider the host proximate, and
// allow unlock.
const int kRssiThreshold = -70;
ProximityMonitorImpl::ProximityMonitorImpl(
ash::multidevice::RemoteDeviceRef remote_device,
ash::secure_channel::ClientChannel* channel)
: remote_device_(remote_device),
channel_(channel),
remote_device_is_in_proximity_(false),
is_active_(false) {
if (device::BluetoothAdapterFactory::IsBluetoothSupported()) {
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&ProximityMonitorImpl::OnAdapterInitialized,
weak_ptr_factory_.GetWeakPtr()));
} else {
PA_LOG(ERROR) << "[Proximity] Proximity monitoring unavailable: "
<< "Bluetooth is unsupported on this platform.";
}
}
ProximityMonitorImpl::~ProximityMonitorImpl() {}
void ProximityMonitorImpl::Start() {
is_active_ = true;
UpdatePollingState();
}
void ProximityMonitorImpl::Stop() {
is_active_ = false;
ClearProximityState();
UpdatePollingState();
}
bool ProximityMonitorImpl::IsUnlockAllowed() const {
return remote_device_is_in_proximity_;
}
void ProximityMonitorImpl::RecordProximityMetricsOnAuthSuccess() {
double rssi_rolling_average = rssi_rolling_average_
? *rssi_rolling_average_
: metrics::kUnknownProximityValue;
std::string remote_device_model = metrics::kUnknownDeviceModel;
ash::multidevice::RemoteDeviceRef remote_device = remote_device_;
if (!remote_device.name().empty())
remote_device_model = remote_device.name();
metrics::RecordAuthProximityRollingRssi(round(rssi_rolling_average));
metrics::RecordAuthProximityRemoteDeviceModelHash(remote_device_model);
}
void ProximityMonitorImpl::OnAdapterInitialized(
scoped_refptr<device::BluetoothAdapter> adapter) {
bluetooth_adapter_ = adapter;
UpdatePollingState();
}
void ProximityMonitorImpl::UpdatePollingState() {
if (ShouldPoll()) {
// If there is a polling iteration already scheduled, wait for it.
if (polling_weak_ptr_factory_.HasWeakPtrs())
return;
// Polling can re-entrantly call back into this method, so make sure to
// schedule the next polling iteration prior to executing the current one.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&ProximityMonitorImpl::PerformScheduledUpdatePollingState,
polling_weak_ptr_factory_.GetWeakPtr()),
base::Milliseconds(kPollingTimeoutMs));
Poll();
} else {
polling_weak_ptr_factory_.InvalidateWeakPtrs();
remote_device_is_in_proximity_ = false;
}
}
void ProximityMonitorImpl::PerformScheduledUpdatePollingState() {
polling_weak_ptr_factory_.InvalidateWeakPtrs();
UpdatePollingState();
}
bool ProximityMonitorImpl::ShouldPoll() const {
return is_active_ && bluetooth_adapter_;
}
void ProximityMonitorImpl::Poll() {
DCHECK(ShouldPoll());
if (channel_->is_disconnected()) {
PA_LOG(ERROR) << "Channel is disconnected.";
ClearProximityState();
return;
}
channel_->GetConnectionMetadata(
base::BindOnce(&ProximityMonitorImpl::OnGetConnectionMetadata,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityMonitorImpl::OnGetConnectionMetadata(
ash::secure_channel::mojom::ConnectionMetadataPtr connection_metadata) {
if (connection_metadata->bluetooth_connection_metadata)
OnGetRssi(connection_metadata->bluetooth_connection_metadata->current_rssi);
else
OnGetRssi(std::nullopt);
}
void ProximityMonitorImpl::OnGetRssi(const std::optional<int32_t>& rssi) {
if (!is_active_) {
PA_LOG(VERBOSE) << "Received RSSI after stopping.";
return;
}
if (rssi) {
AddSample(*rssi);
} else {
PA_LOG(WARNING) << "Received invalid RSSI value.";
rssi_rolling_average_.reset();
CheckForProximityStateChange();
}
}
void ProximityMonitorImpl::ClearProximityState() {
if (is_active_ && remote_device_is_in_proximity_)
NotifyProximityStateChanged();
remote_device_is_in_proximity_ = false;
rssi_rolling_average_.reset();
}
void ProximityMonitorImpl::AddSample(int32_t rssi) {
double weight = kRssiSampleWeight;
if (!rssi_rolling_average_) {
rssi_rolling_average_ = std::make_unique<double>(rssi);
} else {
*rssi_rolling_average_ =
weight * rssi + (1 - weight) * (*rssi_rolling_average_);
}
CheckForProximityStateChange();
}
void ProximityMonitorImpl::CheckForProximityStateChange() {
bool is_now_in_proximity =
rssi_rolling_average_ && *rssi_rolling_average_ > kRssiThreshold;
if (rssi_rolling_average_ && !is_now_in_proximity) {
PA_LOG(VERBOSE) << "Not in proximity. Rolling RSSI average: "
<< *rssi_rolling_average_;
}
if (remote_device_is_in_proximity_ != is_now_in_proximity) {
PA_LOG(VERBOSE) << "[Proximity] Updated proximity state: "
<< (is_now_in_proximity ? "proximate" : "distant");
remote_device_is_in_proximity_ = is_now_in_proximity;
NotifyProximityStateChanged();
}
}
} // namespace proximity_auth