| // Copyright 2015 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 "components/proximity_auth/proximity_monitor_impl.h" | 
 |  | 
 | #include <math.h> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/location.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/time/tick_clock.h" | 
 | #include "base/time/time.h" | 
 | #include "components/proximity_auth/logging/logging.h" | 
 | #include "components/proximity_auth/metrics.h" | 
 | #include "components/proximity_auth/proximity_monitor_observer.h" | 
 | #include "device/bluetooth/bluetooth_adapter.h" | 
 | #include "device/bluetooth/bluetooth_adapter_factory.h" | 
 |  | 
 | using device::BluetoothDevice; | 
 |  | 
 | namespace proximity_auth { | 
 |  | 
 | // The time to wait, in milliseconds, between proximity polling iterations. | 
 | const int kPollingTimeoutMs = 250; | 
 |  | 
 | // The RSSI threshold below which we consider the remote device to not be in | 
 | // proximity. | 
 | const int kRssiThreshold = -5; | 
 |  | 
 | // The weight of the most recent RSSI sample. | 
 | const double kRssiSampleWeight = 0.3; | 
 |  | 
 | ProximityMonitorImpl::ProximityMonitorImpl( | 
 |     const cryptauth::RemoteDevice& remote_device, | 
 |     std::unique_ptr<base::TickClock> clock) | 
 |     : remote_device_(remote_device), | 
 |       strategy_(Strategy::NONE), | 
 |       remote_device_is_in_proximity_(false), | 
 |       is_active_(false), | 
 |       clock_(std::move(clock)), | 
 |       polling_weak_ptr_factory_(this), | 
 |       weak_ptr_factory_(this) { | 
 |   if (device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { | 
 |     device::BluetoothAdapterFactory::GetAdapter( | 
 |         base::Bind(&ProximityMonitorImpl::OnAdapterInitialized, | 
 |                    weak_ptr_factory_.GetWeakPtr())); | 
 |   } else { | 
 |     PA_LOG(ERROR) << "[Proximity] Proximity monitoring unavailable: " | 
 |                   << "Bluetooth is unsupported on this platform."; | 
 |   } | 
 |  | 
 |   // TODO(isherman): Test prefs to set the strategy. Need to read from "Local | 
 |   // State" prefs on the sign-in screen, and per-user prefs on the lock screen. | 
 |   // TODO(isherman): Unlike in the JS app, destroy and recreate the proximity | 
 |   // monitor when the connection state changes. | 
 | } | 
 |  | 
 | ProximityMonitorImpl::~ProximityMonitorImpl() { | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::Start() { | 
 |   is_active_ = true; | 
 |   UpdatePollingState(); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::Stop() { | 
 |   is_active_ = false; | 
 |   ClearProximityState(); | 
 |   UpdatePollingState(); | 
 | } | 
 |  | 
 | ProximityMonitor::Strategy ProximityMonitorImpl::GetStrategy() const { | 
 |   return strategy_; | 
 | } | 
 |  | 
 | bool ProximityMonitorImpl::IsUnlockAllowed() const { | 
 |   return strategy_ == Strategy::NONE || remote_device_is_in_proximity_; | 
 | } | 
 |  | 
 | bool ProximityMonitorImpl::IsInRssiRange() const { | 
 |   return (strategy_ != Strategy::NONE && rssi_rolling_average_ && | 
 |           *rssi_rolling_average_ > kRssiThreshold); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::RecordProximityMetricsOnAuthSuccess() { | 
 |   double rssi_rolling_average = rssi_rolling_average_ | 
 |                                     ? *rssi_rolling_average_ | 
 |                                     : metrics::kUnknownProximityValue; | 
 |  | 
 |   int last_transmit_power_delta = | 
 |       last_transmit_power_reading_ | 
 |           ? (last_transmit_power_reading_->transmit_power - | 
 |              last_transmit_power_reading_->max_transmit_power) | 
 |           : metrics::kUnknownProximityValue; | 
 |  | 
 |   // If no zero RSSI value has been read, then record an overflow. | 
 |   base::TimeDelta time_since_last_zero_rssi; | 
 |   if (last_zero_rssi_timestamp_) | 
 |     time_since_last_zero_rssi = clock_->NowTicks() - *last_zero_rssi_timestamp_; | 
 |   else | 
 |     time_since_last_zero_rssi = base::TimeDelta::FromDays(100); | 
 |  | 
 |   std::string remote_device_model = metrics::kUnknownDeviceModel; | 
 |   if (remote_device_.name != remote_device_.bluetooth_address) | 
 |     remote_device_model = remote_device_.name; | 
 |  | 
 |   metrics::RecordAuthProximityRollingRssi(round(rssi_rolling_average)); | 
 |   metrics::RecordAuthProximityTransmitPowerDelta(last_transmit_power_delta); | 
 |   metrics::RecordAuthProximityTimeSinceLastZeroRssi(time_since_last_zero_rssi); | 
 |   metrics::RecordAuthProximityRemoteDeviceModelHash(remote_device_model); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::AddObserver(ProximityMonitorObserver* observer) { | 
 |   observers_.AddObserver(observer); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::RemoveObserver(ProximityMonitorObserver* observer) { | 
 |   observers_.RemoveObserver(observer); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::SetStrategy(Strategy strategy) { | 
 |   if (strategy_ == strategy) | 
 |     return; | 
 |   strategy_ = strategy; | 
 |   CheckForProximityStateChange(); | 
 |   UpdatePollingState(); | 
 | } | 
 |  | 
 | ProximityMonitorImpl::TransmitPowerReading::TransmitPowerReading( | 
 |     int transmit_power, | 
 |     int max_transmit_power) | 
 |     : transmit_power(transmit_power), max_transmit_power(max_transmit_power) { | 
 | } | 
 |  | 
 | bool ProximityMonitorImpl::TransmitPowerReading::IsInProximity() const { | 
 |   return transmit_power < max_transmit_power; | 
 | } | 
 |  | 
 | 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::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |         FROM_HERE, | 
 |         base::Bind(&ProximityMonitorImpl::PerformScheduledUpdatePollingState, | 
 |                    polling_weak_ptr_factory_.GetWeakPtr()), | 
 |         base::TimeDelta::FromMilliseconds(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 { | 
 |   // Note: We poll even if the strategy is NONE so we can record measurements. | 
 |   return is_active_ && bluetooth_adapter_; | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::Poll() { | 
 |   DCHECK(ShouldPoll()); | 
 |  | 
 |   BluetoothDevice* device = | 
 |       bluetooth_adapter_->GetDevice(remote_device_.bluetooth_address); | 
 |  | 
 |   if (!device) { | 
 |     PA_LOG(ERROR) << "Unknown Bluetooth device with address " | 
 |                   << remote_device_.bluetooth_address; | 
 |     ClearProximityState(); | 
 |     return; | 
 |   } | 
 |   if (!device->IsConnected()) { | 
 |     PA_LOG(ERROR) << "Bluetooth device with address " | 
 |                   << remote_device_.bluetooth_address << " is not connected."; | 
 |     ClearProximityState(); | 
 |     return; | 
 |   } | 
 |  | 
 |   device->GetConnectionInfo(base::Bind(&ProximityMonitorImpl::OnConnectionInfo, | 
 |                                        weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::OnConnectionInfo( | 
 |     const BluetoothDevice::ConnectionInfo& connection_info) { | 
 |   if (!is_active_) { | 
 |     PA_LOG(INFO) << "[Proximity] Got connection info after stopping"; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (connection_info.rssi != BluetoothDevice::kUnknownPower && | 
 |       connection_info.transmit_power != BluetoothDevice::kUnknownPower && | 
 |       connection_info.max_transmit_power != BluetoothDevice::kUnknownPower) { | 
 |     AddSample(connection_info); | 
 |   } else { | 
 |     PA_LOG(WARNING) << "[Proximity] Unkown values received from API: " | 
 |                     << connection_info.rssi << " " | 
 |                     << connection_info.transmit_power << " " | 
 |                     << connection_info.max_transmit_power; | 
 |     rssi_rolling_average_.reset(); | 
 |     last_transmit_power_reading_.reset(); | 
 |     CheckForProximityStateChange(); | 
 |   } | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::ClearProximityState() { | 
 |   if (is_active_ && remote_device_is_in_proximity_) { | 
 |     for (auto& observer : observers_) | 
 |       observer.OnProximityStateChanged(); | 
 |   } | 
 |  | 
 |   remote_device_is_in_proximity_ = false; | 
 |   rssi_rolling_average_.reset(); | 
 |   last_transmit_power_reading_.reset(); | 
 |   last_zero_rssi_timestamp_.reset(); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::AddSample( | 
 |     const BluetoothDevice::ConnectionInfo& connection_info) { | 
 |   double weight = kRssiSampleWeight; | 
 |   if (!rssi_rolling_average_) { | 
 |     rssi_rolling_average_.reset(new double(connection_info.rssi)); | 
 |   } else { | 
 |     *rssi_rolling_average_ = | 
 |         weight * connection_info.rssi + (1 - weight) * (*rssi_rolling_average_); | 
 |   } | 
 |   last_transmit_power_reading_.reset(new TransmitPowerReading( | 
 |       connection_info.transmit_power, connection_info.max_transmit_power)); | 
 |  | 
 |   // It's rare but possible for the RSSI to be positive briefly. | 
 |   if (connection_info.rssi >= 0) | 
 |     last_zero_rssi_timestamp_.reset(new base::TimeTicks(clock_->NowTicks())); | 
 |  | 
 |   CheckForProximityStateChange(); | 
 | } | 
 |  | 
 | void ProximityMonitorImpl::CheckForProximityStateChange() { | 
 |   if (strategy_ == Strategy::NONE) | 
 |     return; | 
 |  | 
 |   bool is_now_in_proximity = false; | 
 |   switch (strategy_) { | 
 |     case Strategy::NONE: | 
 |       return; | 
 |  | 
 |     case Strategy::CHECK_RSSI: | 
 |       is_now_in_proximity = IsInRssiRange(); | 
 |       break; | 
 |  | 
 |     case Strategy::CHECK_TRANSMIT_POWER: | 
 |       is_now_in_proximity = (last_transmit_power_reading_ && | 
 |                              last_transmit_power_reading_->IsInProximity()); | 
 |       break; | 
 |   } | 
 |  | 
 |   if (remote_device_is_in_proximity_ != is_now_in_proximity) { | 
 |     PA_LOG(INFO) << "[Proximity] Updated proximity state: " | 
 |                  << (is_now_in_proximity ? "proximate" : "distant"); | 
 |     remote_device_is_in_proximity_ = is_now_in_proximity; | 
 |     for (auto& observer : observers_) | 
 |       observer.OnProximityStateChanged(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace proximity_auth |