blob: 702ea77e2b62b84ee57f31146a462d2e124a234d [file] [log] [blame]
ishermanea464072015-06-19 21:56:341// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/proximity_auth/proximity_monitor_impl.h"
6
7#include <math.h>
8
9#include "base/bind.h"
10#include "base/location.h"
11#include "base/thread_task_runner_handle.h"
12#include "base/time/tick_clock.h"
13#include "base/time/time.h"
14#include "components/proximity_auth/logging/logging.h"
15#include "components/proximity_auth/metrics.h"
16#include "components/proximity_auth/proximity_monitor_observer.h"
17#include "device/bluetooth/bluetooth_adapter.h"
18#include "device/bluetooth/bluetooth_adapter_factory.h"
19
20using device::BluetoothDevice;
21
22namespace proximity_auth {
23
24// The time to wait, in milliseconds, between proximity polling iterations.
25const int kPollingTimeoutMs = 250;
26
27// The RSSI threshold below which we consider the remote device to not be in
28// proximity.
29const int kRssiThreshold = -5;
30
31// The weight of the most recent RSSI sample.
32const double kRssiSampleWeight = 0.3;
33
34ProximityMonitorImpl::ProximityMonitorImpl(const RemoteDevice& remote_device,
tengsae50e972015-10-02 04:00:4035 scoped_ptr<base::TickClock> clock)
ishermanea464072015-06-19 21:56:3436 : remote_device_(remote_device),
ishermanea464072015-06-19 21:56:3437 strategy_(Strategy::NONE),
38 remote_device_is_in_proximity_(false),
39 is_active_(false),
40 clock_(clock.Pass()),
41 polling_weak_ptr_factory_(this),
42 weak_ptr_factory_(this) {
43 if (device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
44 device::BluetoothAdapterFactory::GetAdapter(
45 base::Bind(&ProximityMonitorImpl::OnAdapterInitialized,
46 weak_ptr_factory_.GetWeakPtr()));
47 } else {
48 PA_LOG(ERROR) << "[Proximity] Proximity monitoring unavailable: "
49 << "Bluetooth is unsupported on this platform.";
50 }
51
52 // TODO(isherman): Test prefs to set the strategy. Need to read from "Local
53 // State" prefs on the sign-in screen, and per-user prefs on the lock screen.
54 // TODO(isherman): Unlike in the JS app, destroy and recreate the proximity
55 // monitor when the connection state changes.
56}
57
58ProximityMonitorImpl::~ProximityMonitorImpl() {
59}
60
61void ProximityMonitorImpl::Start() {
62 is_active_ = true;
63 UpdatePollingState();
64}
65
66void ProximityMonitorImpl::Stop() {
67 is_active_ = false;
68 ClearProximityState();
69 UpdatePollingState();
70}
71
72ProximityMonitor::Strategy ProximityMonitorImpl::GetStrategy() const {
73 return strategy_;
74}
75
76bool ProximityMonitorImpl::IsUnlockAllowed() const {
77 return strategy_ == Strategy::NONE || remote_device_is_in_proximity_;
78}
79
80bool ProximityMonitorImpl::IsInRssiRange() const {
81 return (strategy_ != Strategy::NONE && rssi_rolling_average_ &&
82 *rssi_rolling_average_ > kRssiThreshold);
83}
84
85void ProximityMonitorImpl::RecordProximityMetricsOnAuthSuccess() {
86 double rssi_rolling_average = rssi_rolling_average_
87 ? *rssi_rolling_average_
88 : metrics::kUnknownProximityValue;
89
90 int last_transmit_power_delta =
91 last_transmit_power_reading_
92 ? (last_transmit_power_reading_->transmit_power -
93 last_transmit_power_reading_->max_transmit_power)
94 : metrics::kUnknownProximityValue;
95
96 // If no zero RSSI value has been read, then record an overflow.
97 base::TimeDelta time_since_last_zero_rssi;
98 if (last_zero_rssi_timestamp_)
99 time_since_last_zero_rssi = clock_->NowTicks() - *last_zero_rssi_timestamp_;
100 else
101 time_since_last_zero_rssi = base::TimeDelta::FromDays(100);
102
103 std::string remote_device_model = metrics::kUnknownDeviceModel;
104 if (remote_device_.name != remote_device_.bluetooth_address)
105 remote_device_model = remote_device_.name;
106
107 metrics::RecordAuthProximityRollingRssi(round(rssi_rolling_average));
108 metrics::RecordAuthProximityTransmitPowerDelta(last_transmit_power_delta);
109 metrics::RecordAuthProximityTimeSinceLastZeroRssi(time_since_last_zero_rssi);
110 metrics::RecordAuthProximityRemoteDeviceModelHash(remote_device_model);
111}
112
tengsae50e972015-10-02 04:00:40113void ProximityMonitorImpl::AddObserver(ProximityMonitorObserver* observer) {
114 observers_.AddObserver(observer);
115}
116
117void ProximityMonitorImpl::RemoveObserver(ProximityMonitorObserver* observer) {
118 observers_.RemoveObserver(observer);
119}
120
ishermanea464072015-06-19 21:56:34121void ProximityMonitorImpl::SetStrategy(Strategy strategy) {
122 if (strategy_ == strategy)
123 return;
124 strategy_ = strategy;
125 CheckForProximityStateChange();
126 UpdatePollingState();
127}
128
129ProximityMonitorImpl::TransmitPowerReading::TransmitPowerReading(
130 int transmit_power,
131 int max_transmit_power)
132 : transmit_power(transmit_power), max_transmit_power(max_transmit_power) {
133}
134
135bool ProximityMonitorImpl::TransmitPowerReading::IsInProximity() const {
136 return transmit_power < max_transmit_power;
137}
138
139void ProximityMonitorImpl::OnAdapterInitialized(
140 scoped_refptr<device::BluetoothAdapter> adapter) {
141 bluetooth_adapter_ = adapter;
142 UpdatePollingState();
143}
144
145void ProximityMonitorImpl::UpdatePollingState() {
146 if (ShouldPoll()) {
147 // If there is a polling iteration already scheduled, wait for it.
148 if (polling_weak_ptr_factory_.HasWeakPtrs())
149 return;
150
151 // Polling can re-entrantly call back into this method, so make sure to
152 // schedule the next polling iteration prior to executing the current one.
153 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
154 FROM_HERE,
155 base::Bind(&ProximityMonitorImpl::PerformScheduledUpdatePollingState,
156 polling_weak_ptr_factory_.GetWeakPtr()),
157 base::TimeDelta::FromMilliseconds(kPollingTimeoutMs));
158 Poll();
159 } else {
160 polling_weak_ptr_factory_.InvalidateWeakPtrs();
161 remote_device_is_in_proximity_ = false;
162 }
163}
164
165void ProximityMonitorImpl::PerformScheduledUpdatePollingState() {
166 polling_weak_ptr_factory_.InvalidateWeakPtrs();
167 UpdatePollingState();
168}
169
170bool ProximityMonitorImpl::ShouldPoll() const {
171 // Note: We poll even if the strategy is NONE so we can record measurements.
172 return is_active_ && bluetooth_adapter_;
173}
174
175void ProximityMonitorImpl::Poll() {
176 DCHECK(ShouldPoll());
177
178 BluetoothDevice* device =
179 bluetooth_adapter_->GetDevice(remote_device_.bluetooth_address);
180
181 if (!device) {
182 PA_LOG(ERROR) << "Unknown Bluetooth device with address "
183 << remote_device_.bluetooth_address;
184 ClearProximityState();
185 return;
186 }
187 if (!device->IsConnected()) {
188 PA_LOG(ERROR) << "Bluetooth device with address "
189 << remote_device_.bluetooth_address << " is not connected.";
190 ClearProximityState();
191 return;
192 }
193
194 device->GetConnectionInfo(base::Bind(&ProximityMonitorImpl::OnConnectionInfo,
195 weak_ptr_factory_.GetWeakPtr()));
196}
197
198void ProximityMonitorImpl::OnConnectionInfo(
199 const BluetoothDevice::ConnectionInfo& connection_info) {
200 if (!is_active_) {
201 PA_LOG(INFO) << "[Proximity] Got connection info after stopping";
202 return;
203 }
204
205 if (connection_info.rssi != BluetoothDevice::kUnknownPower &&
206 connection_info.transmit_power != BluetoothDevice::kUnknownPower &&
207 connection_info.max_transmit_power != BluetoothDevice::kUnknownPower) {
208 AddSample(connection_info);
209 } else {
210 PA_LOG(WARNING) << "[Proximity] Unkown values received from API: "
211 << connection_info.rssi << " "
212 << connection_info.transmit_power << " "
213 << connection_info.max_transmit_power;
214 rssi_rolling_average_.reset();
215 last_transmit_power_reading_.reset();
216 CheckForProximityStateChange();
217 }
218}
219
220void ProximityMonitorImpl::ClearProximityState() {
tengsae50e972015-10-02 04:00:40221 if (is_active_ && remote_device_is_in_proximity_) {
222 FOR_EACH_OBSERVER(ProximityMonitorObserver, observers_,
223 OnProximityStateChanged());
224 }
ishermanea464072015-06-19 21:56:34225
226 remote_device_is_in_proximity_ = false;
227 rssi_rolling_average_.reset();
228 last_transmit_power_reading_.reset();
229 last_zero_rssi_timestamp_.reset();
230}
231
232void ProximityMonitorImpl::AddSample(
233 const BluetoothDevice::ConnectionInfo& connection_info) {
234 double weight = kRssiSampleWeight;
235 if (!rssi_rolling_average_) {
236 rssi_rolling_average_.reset(new double(connection_info.rssi));
237 } else {
238 *rssi_rolling_average_ =
239 weight * connection_info.rssi + (1 - weight) * (*rssi_rolling_average_);
240 }
241 last_transmit_power_reading_.reset(new TransmitPowerReading(
242 connection_info.transmit_power, connection_info.max_transmit_power));
243
244 // It's rare but possible for the RSSI to be positive briefly.
245 if (connection_info.rssi >= 0)
246 last_zero_rssi_timestamp_.reset(new base::TimeTicks(clock_->NowTicks()));
247
248 CheckForProximityStateChange();
249}
250
251void ProximityMonitorImpl::CheckForProximityStateChange() {
252 if (strategy_ == Strategy::NONE)
253 return;
254
255 bool is_now_in_proximity = false;
256 switch (strategy_) {
257 case Strategy::NONE:
258 return;
259
260 case Strategy::CHECK_RSSI:
261 is_now_in_proximity = IsInRssiRange();
262 break;
263
264 case Strategy::CHECK_TRANSMIT_POWER:
265 is_now_in_proximity = (last_transmit_power_reading_ &&
266 last_transmit_power_reading_->IsInProximity());
267 break;
268 }
269
270 if (remote_device_is_in_proximity_ != is_now_in_proximity) {
271 PA_LOG(INFO) << "[Proximity] Updated proximity state: "
272 << (is_now_in_proximity ? "proximate" : "distant");
273 remote_device_is_in_proximity_ = is_now_in_proximity;
tengsae50e972015-10-02 04:00:40274 FOR_EACH_OBSERVER(ProximityMonitorObserver, observers_,
275 OnProximityStateChanged());
ishermanea464072015-06-19 21:56:34276 }
277}
278
279} // namespace proximity_auth