1 // Copyright (c) 2012 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.
8 #include "base/memory/ref_counted.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/test/test_simple_task_runner.h"
11 #include "device/bluetooth/bluetooth_adapter.h"
12 #include "device/bluetooth/bluetooth_adapter_win.h"
13 #include "device/bluetooth/bluetooth_device.h"
14 #include "device/bluetooth/bluetooth_discovery_session_outcome.h"
15 #include "device/bluetooth/bluetooth_task_manager_win.h"
16 #include "device/bluetooth/test/test_bluetooth_adapter_observer.h"
17 #include "testing/gtest/include/gtest/gtest.h"
21 const char kAdapterAddress
[] = "A1:B2:C3:D4:E5:F6";
22 const char kAdapterName
[] = "Bluetooth Adapter Name";
24 const char kTestAudioSdpName
[] = "Audio";
25 const char kTestAudioSdpName2
[] = "Audio2";
26 const char kTestAudioSdpBytes
[] =
27 "35510900000a00010001090001350319110a09000435103506190100090019350619001909"
28 "010209000535031910020900093508350619110d090102090100250c417564696f20536f75"
30 const device::BluetoothUUID
kTestAudioSdpUuid("110a");
32 void MakeDeviceState(const std::string
& name
,
33 const std::string
& address
,
34 device::BluetoothTaskManagerWin::DeviceState
* state
) {
36 state
->address
= address
;
37 state
->bluetooth_class
= 0;
38 state
->authenticated
= false;
39 state
->connected
= false;
46 class BluetoothAdapterWinTest
: public testing::Test
{
48 BluetoothAdapterWinTest()
49 : ui_task_runner_(new base::TestSimpleTaskRunner()),
50 bluetooth_task_runner_(new base::TestSimpleTaskRunner()),
51 adapter_(new BluetoothAdapterWin(
52 base::Bind(&BluetoothAdapterWinTest::RunInitCallback
,
53 base::Unretained(this)))),
54 adapter_win_(static_cast<BluetoothAdapterWin
*>(adapter_
.get())),
56 init_callback_called_(false) {
57 adapter_win_
->InitForTest(ui_task_runner_
, bluetooth_task_runner_
);
60 void SetUp() override
{
61 num_start_discovery_callbacks_
= 0;
62 num_start_discovery_error_callbacks_
= 0;
63 num_stop_discovery_callbacks_
= 0;
64 num_stop_discovery_error_callbacks_
= 0;
67 void RunInitCallback() {
68 init_callback_called_
= true;
71 void IncrementNumStartDiscoveryCallbacks() {
72 num_start_discovery_callbacks_
++;
75 void IncrementNumStartDiscoveryErrorCallbacks(
76 UMABluetoothDiscoverySessionOutcome
) {
77 num_start_discovery_error_callbacks_
++;
80 void IncrementNumStopDiscoveryCallbacks() {
81 num_stop_discovery_callbacks_
++;
84 void IncrementNumStopDiscoveryErrorCallbacks(
85 UMABluetoothDiscoverySessionOutcome
) {
86 num_stop_discovery_error_callbacks_
++;
89 typedef base::Callback
<void(UMABluetoothDiscoverySessionOutcome
)>
90 DiscoverySessionErrorCallback
;
92 void CallAddDiscoverySession(
93 const base::Closure
& callback
,
94 const DiscoverySessionErrorCallback
& error_callback
) {
95 adapter_win_
->AddDiscoverySession(nullptr, callback
, error_callback
);
98 void CallRemoveDiscoverySession(
99 const base::Closure
& callback
,
100 const DiscoverySessionErrorCallback
& error_callback
) {
101 adapter_win_
->RemoveDiscoverySession(nullptr, callback
, error_callback
);
105 scoped_refptr
<base::TestSimpleTaskRunner
> ui_task_runner_
;
106 scoped_refptr
<base::TestSimpleTaskRunner
> bluetooth_task_runner_
;
107 scoped_refptr
<BluetoothAdapter
> adapter_
;
108 BluetoothAdapterWin
* adapter_win_
;
109 TestBluetoothAdapterObserver observer_
;
110 bool init_callback_called_
;
111 int num_start_discovery_callbacks_
;
112 int num_start_discovery_error_callbacks_
;
113 int num_stop_discovery_callbacks_
;
114 int num_stop_discovery_error_callbacks_
;
117 TEST_F(BluetoothAdapterWinTest
, AdapterNotPresent
) {
118 BluetoothTaskManagerWin::AdapterState state
;
119 adapter_win_
->AdapterStateChanged(state
);
120 EXPECT_FALSE(adapter_win_
->IsPresent());
123 TEST_F(BluetoothAdapterWinTest
, AdapterPresent
) {
124 BluetoothTaskManagerWin::AdapterState state
;
125 state
.address
= kAdapterAddress
;
126 state
.name
= kAdapterName
;
127 adapter_win_
->AdapterStateChanged(state
);
128 EXPECT_TRUE(adapter_win_
->IsPresent());
131 TEST_F(BluetoothAdapterWinTest
, AdapterPresentChanged
) {
132 BluetoothTaskManagerWin::AdapterState state
;
133 state
.address
= kAdapterAddress
;
134 state
.name
= kAdapterName
;
135 adapter_win_
->AdapterStateChanged(state
);
136 EXPECT_EQ(1, observer_
.present_changed_count());
137 adapter_win_
->AdapterStateChanged(state
);
138 EXPECT_EQ(1, observer_
.present_changed_count());
139 BluetoothTaskManagerWin::AdapterState empty_state
;
140 adapter_win_
->AdapterStateChanged(empty_state
);
141 EXPECT_EQ(2, observer_
.present_changed_count());
144 TEST_F(BluetoothAdapterWinTest
, AdapterPoweredChanged
) {
145 BluetoothTaskManagerWin::AdapterState state
;
146 state
.powered
= true;
147 adapter_win_
->AdapterStateChanged(state
);
148 EXPECT_EQ(1, observer_
.powered_changed_count());
149 adapter_win_
->AdapterStateChanged(state
);
150 EXPECT_EQ(1, observer_
.powered_changed_count());
151 state
.powered
= false;
152 adapter_win_
->AdapterStateChanged(state
);
153 EXPECT_EQ(2, observer_
.powered_changed_count());
156 TEST_F(BluetoothAdapterWinTest
, AdapterInitialized
) {
157 EXPECT_FALSE(adapter_win_
->IsInitialized());
158 EXPECT_FALSE(init_callback_called_
);
159 BluetoothTaskManagerWin::AdapterState state
;
160 adapter_win_
->AdapterStateChanged(state
);
161 EXPECT_TRUE(adapter_win_
->IsInitialized());
162 EXPECT_TRUE(init_callback_called_
);
165 TEST_F(BluetoothAdapterWinTest
, SingleStartDiscovery
) {
166 bluetooth_task_runner_
->ClearPendingTasks();
167 CallAddDiscoverySession(
168 base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
169 base::Unretained(this)),
170 DiscoverySessionErrorCallback());
171 EXPECT_TRUE(ui_task_runner_
->GetPendingTasks().empty());
172 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
173 EXPECT_FALSE(adapter_
->IsDiscovering());
174 EXPECT_EQ(0, num_start_discovery_callbacks_
);
175 adapter_win_
->DiscoveryStarted(true);
176 ui_task_runner_
->RunPendingTasks();
177 EXPECT_TRUE(adapter_
->IsDiscovering());
178 EXPECT_EQ(1, num_start_discovery_callbacks_
);
179 EXPECT_EQ(1, observer_
.discovering_changed_count());
182 TEST_F(BluetoothAdapterWinTest
, SingleStartDiscoveryFailure
) {
183 CallAddDiscoverySession(
186 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks
,
187 base::Unretained(this)));
188 adapter_win_
->DiscoveryStarted(false);
189 ui_task_runner_
->RunPendingTasks();
190 EXPECT_FALSE(adapter_
->IsDiscovering());
191 EXPECT_EQ(1, num_start_discovery_error_callbacks_
);
192 EXPECT_EQ(0, observer_
.discovering_changed_count());
195 TEST_F(BluetoothAdapterWinTest
, MultipleStartDiscoveries
) {
196 bluetooth_task_runner_
->ClearPendingTasks();
197 int num_discoveries
= 5;
198 for (int i
= 0; i
< num_discoveries
; i
++) {
199 CallAddDiscoverySession(
201 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
202 base::Unretained(this)),
203 DiscoverySessionErrorCallback());
204 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
206 EXPECT_TRUE(ui_task_runner_
->GetPendingTasks().empty());
207 EXPECT_FALSE(adapter_
->IsDiscovering());
208 EXPECT_EQ(0, num_start_discovery_callbacks_
);
209 adapter_win_
->DiscoveryStarted(true);
210 ui_task_runner_
->RunPendingTasks();
211 EXPECT_TRUE(adapter_
->IsDiscovering());
212 EXPECT_EQ(num_discoveries
, num_start_discovery_callbacks_
);
213 EXPECT_EQ(1, observer_
.discovering_changed_count());
216 TEST_F(BluetoothAdapterWinTest
, MultipleStartDiscoveriesFailure
) {
217 int num_discoveries
= 5;
218 for (int i
= 0; i
< num_discoveries
; i
++) {
219 CallAddDiscoverySession(
222 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks
,
223 base::Unretained(this)));
225 adapter_win_
->DiscoveryStarted(false);
226 ui_task_runner_
->RunPendingTasks();
227 EXPECT_FALSE(adapter_
->IsDiscovering());
228 EXPECT_EQ(num_discoveries
, num_start_discovery_error_callbacks_
);
229 EXPECT_EQ(0, observer_
.discovering_changed_count());
232 TEST_F(BluetoothAdapterWinTest
, MultipleStartDiscoveriesAfterDiscovering
) {
233 CallAddDiscoverySession(
234 base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
235 base::Unretained(this)),
236 DiscoverySessionErrorCallback());
237 adapter_win_
->DiscoveryStarted(true);
238 ui_task_runner_
->RunPendingTasks();
239 EXPECT_TRUE(adapter_
->IsDiscovering());
240 EXPECT_EQ(1, num_start_discovery_callbacks_
);
242 bluetooth_task_runner_
->ClearPendingTasks();
243 for (int i
= 0; i
< 5; i
++) {
244 int num_start_discovery_callbacks
= num_start_discovery_callbacks_
;
245 CallAddDiscoverySession(
247 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
248 base::Unretained(this)),
249 DiscoverySessionErrorCallback());
250 EXPECT_TRUE(adapter_
->IsDiscovering());
251 EXPECT_TRUE(bluetooth_task_runner_
->GetPendingTasks().empty());
252 EXPECT_TRUE(ui_task_runner_
->GetPendingTasks().empty());
253 EXPECT_EQ(num_start_discovery_callbacks
+ 1,
254 num_start_discovery_callbacks_
);
256 EXPECT_EQ(1, observer_
.discovering_changed_count());
259 TEST_F(BluetoothAdapterWinTest
, StartDiscoveryAfterDiscoveringFailure
) {
260 CallAddDiscoverySession(
263 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks
,
264 base::Unretained(this)));
265 adapter_win_
->DiscoveryStarted(false);
266 ui_task_runner_
->RunPendingTasks();
267 EXPECT_FALSE(adapter_
->IsDiscovering());
268 EXPECT_EQ(1, num_start_discovery_error_callbacks_
);
270 CallAddDiscoverySession(
271 base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
272 base::Unretained(this)),
273 DiscoverySessionErrorCallback());
274 adapter_win_
->DiscoveryStarted(true);
275 ui_task_runner_
->RunPendingTasks();
276 EXPECT_TRUE(adapter_
->IsDiscovering());
277 EXPECT_EQ(1, num_start_discovery_callbacks_
);
280 TEST_F(BluetoothAdapterWinTest
, SingleStopDiscovery
) {
281 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
282 adapter_win_
->DiscoveryStarted(true);
283 ui_task_runner_
->ClearPendingTasks();
284 CallRemoveDiscoverySession(
285 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
286 base::Unretained(this)),
287 DiscoverySessionErrorCallback());
288 EXPECT_TRUE(adapter_
->IsDiscovering());
289 EXPECT_EQ(0, num_stop_discovery_callbacks_
);
290 bluetooth_task_runner_
->ClearPendingTasks();
291 adapter_win_
->DiscoveryStopped();
292 ui_task_runner_
->RunPendingTasks();
293 EXPECT_FALSE(adapter_
->IsDiscovering());
294 EXPECT_EQ(1, num_stop_discovery_callbacks_
);
295 EXPECT_TRUE(bluetooth_task_runner_
->GetPendingTasks().empty());
296 EXPECT_EQ(2, observer_
.discovering_changed_count());
299 TEST_F(BluetoothAdapterWinTest
, MultipleStopDiscoveries
) {
300 int num_discoveries
= 5;
301 for (int i
= 0; i
< num_discoveries
; i
++) {
302 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
304 adapter_win_
->DiscoveryStarted(true);
305 ui_task_runner_
->ClearPendingTasks();
306 bluetooth_task_runner_
->ClearPendingTasks();
307 for (int i
= 0; i
< num_discoveries
- 1; i
++) {
308 CallRemoveDiscoverySession(
309 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
310 base::Unretained(this)),
311 DiscoverySessionErrorCallback());
312 EXPECT_TRUE(bluetooth_task_runner_
->GetPendingTasks().empty());
313 ui_task_runner_
->RunPendingTasks();
314 EXPECT_EQ(i
+ 1, num_stop_discovery_callbacks_
);
316 CallRemoveDiscoverySession(
317 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
318 base::Unretained(this)),
319 DiscoverySessionErrorCallback());
320 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
321 EXPECT_TRUE(adapter_
->IsDiscovering());
322 adapter_win_
->DiscoveryStopped();
323 ui_task_runner_
->RunPendingTasks();
324 EXPECT_FALSE(adapter_
->IsDiscovering());
325 EXPECT_EQ(num_discoveries
, num_stop_discovery_callbacks_
);
326 EXPECT_EQ(2, observer_
.discovering_changed_count());
329 TEST_F(BluetoothAdapterWinTest
,
330 StartDiscoveryAndStartDiscoveryAndStopDiscoveries
) {
331 CallAddDiscoverySession(
332 base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
333 base::Unretained(this)),
334 DiscoverySessionErrorCallback());
335 adapter_win_
->DiscoveryStarted(true);
336 CallAddDiscoverySession(
337 base::Bind(&BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
338 base::Unretained(this)),
339 DiscoverySessionErrorCallback());
340 ui_task_runner_
->ClearPendingTasks();
341 bluetooth_task_runner_
->ClearPendingTasks();
342 CallRemoveDiscoverySession(
343 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
344 base::Unretained(this)),
345 DiscoverySessionErrorCallback());
346 EXPECT_TRUE(bluetooth_task_runner_
->GetPendingTasks().empty());
347 CallRemoveDiscoverySession(
348 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
349 base::Unretained(this)),
350 DiscoverySessionErrorCallback());
351 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
354 TEST_F(BluetoothAdapterWinTest
,
355 StartDiscoveryAndStopDiscoveryAndStartDiscovery
) {
356 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
357 adapter_win_
->DiscoveryStarted(true);
358 EXPECT_TRUE(adapter_
->IsDiscovering());
359 CallRemoveDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
360 adapter_win_
->DiscoveryStopped();
361 EXPECT_FALSE(adapter_
->IsDiscovering());
362 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
363 adapter_win_
->DiscoveryStarted(true);
364 EXPECT_TRUE(adapter_
->IsDiscovering());
367 TEST_F(BluetoothAdapterWinTest
, StartDiscoveryBeforeDiscoveryStopped
) {
368 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
369 adapter_win_
->DiscoveryStarted(true);
370 CallRemoveDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
371 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
372 bluetooth_task_runner_
->ClearPendingTasks();
373 adapter_win_
->DiscoveryStopped();
374 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
377 TEST_F(BluetoothAdapterWinTest
, StopDiscoveryWithoutStartDiscovery
) {
378 CallRemoveDiscoverySession(
381 &BluetoothAdapterWinTest::IncrementNumStopDiscoveryErrorCallbacks
,
382 base::Unretained(this)));
383 EXPECT_EQ(1, num_stop_discovery_error_callbacks_
);
386 TEST_F(BluetoothAdapterWinTest
, StopDiscoveryBeforeDiscoveryStarted
) {
387 CallAddDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
388 CallRemoveDiscoverySession(base::Closure(), DiscoverySessionErrorCallback());
389 bluetooth_task_runner_
->ClearPendingTasks();
390 adapter_win_
->DiscoveryStarted(true);
391 EXPECT_EQ(1, bluetooth_task_runner_
->GetPendingTasks().size());
394 TEST_F(BluetoothAdapterWinTest
, StartAndStopBeforeDiscoveryStarted
) {
395 int num_expected_start_discoveries
= 3;
396 int num_expected_stop_discoveries
= 2;
397 for (int i
= 0; i
< num_expected_start_discoveries
; i
++) {
398 CallAddDiscoverySession(
400 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryCallbacks
,
401 base::Unretained(this)),
402 DiscoverySessionErrorCallback());
404 for (int i
= 0; i
< num_expected_stop_discoveries
; i
++) {
405 CallRemoveDiscoverySession(
406 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
407 base::Unretained(this)),
408 DiscoverySessionErrorCallback());
410 bluetooth_task_runner_
->ClearPendingTasks();
411 adapter_win_
->DiscoveryStarted(true);
412 EXPECT_TRUE(bluetooth_task_runner_
->GetPendingTasks().empty());
413 ui_task_runner_
->RunPendingTasks();
414 EXPECT_EQ(num_expected_start_discoveries
, num_start_discovery_callbacks_
);
415 EXPECT_EQ(num_expected_stop_discoveries
, num_stop_discovery_callbacks_
);
418 TEST_F(BluetoothAdapterWinTest
, StopDiscoveryBeforeDiscoveryStartedAndFailed
) {
419 CallAddDiscoverySession(
422 &BluetoothAdapterWinTest::IncrementNumStartDiscoveryErrorCallbacks
,
423 base::Unretained(this)));
424 CallRemoveDiscoverySession(
425 base::Bind(&BluetoothAdapterWinTest::IncrementNumStopDiscoveryCallbacks
,
426 base::Unretained(this)),
427 DiscoverySessionErrorCallback());
428 ui_task_runner_
->ClearPendingTasks();
429 adapter_win_
->DiscoveryStarted(false);
430 ui_task_runner_
->RunPendingTasks();
431 EXPECT_EQ(1, num_start_discovery_error_callbacks_
);
432 EXPECT_EQ(1, num_stop_discovery_callbacks_
);
433 EXPECT_EQ(0, observer_
.discovering_changed_count());
436 TEST_F(BluetoothAdapterWinTest
, DevicesPolled
) {
437 BluetoothTaskManagerWin::DeviceState
* android_phone_state
=
438 new BluetoothTaskManagerWin::DeviceState();
439 MakeDeviceState("phone", "A1:B2:C3:D4:E5:E0", android_phone_state
);
440 BluetoothTaskManagerWin::DeviceState
* laptop_state
=
441 new BluetoothTaskManagerWin::DeviceState();
442 MakeDeviceState("laptop", "A1:B2:C3:D4:E5:E1", laptop_state
);
443 BluetoothTaskManagerWin::DeviceState
* iphone_state
=
444 new BluetoothTaskManagerWin::DeviceState();
445 MakeDeviceState("phone", "A1:B2:C3:D4:E5:E2", iphone_state
);
446 ScopedVector
<BluetoothTaskManagerWin::DeviceState
> devices
;
447 devices
.push_back(android_phone_state
);
448 devices
.push_back(laptop_state
);
449 devices
.push_back(iphone_state
);
453 adapter_win_
->DevicesPolled(devices
);
454 EXPECT_EQ(3, observer_
.device_added_count());
455 EXPECT_EQ(0, observer_
.device_removed_count());
456 EXPECT_EQ(0, observer_
.device_changed_count());
458 // Change a device name
459 android_phone_state
->name
= "phone2";
461 adapter_win_
->DevicesPolled(devices
);
462 EXPECT_EQ(0, observer_
.device_added_count());
463 EXPECT_EQ(0, observer_
.device_removed_count());
464 EXPECT_EQ(1, observer_
.device_changed_count());
466 // Change a device address
467 android_phone_state
->address
= "A1:B2:C3:D4:E5:E6";
469 adapter_win_
->DevicesPolled(devices
);
470 EXPECT_EQ(1, observer_
.device_added_count());
471 EXPECT_EQ(1, observer_
.device_removed_count());
472 EXPECT_EQ(0, observer_
.device_changed_count());
475 devices
.erase(devices
.begin());
477 adapter_win_
->DevicesPolled(devices
);
478 EXPECT_EQ(0, observer_
.device_added_count());
479 EXPECT_EQ(1, observer_
.device_removed_count());
480 EXPECT_EQ(0, observer_
.device_changed_count());
483 BluetoothTaskManagerWin::ServiceRecordState
* audio_state
=
484 new BluetoothTaskManagerWin::ServiceRecordState();
485 audio_state
->name
= kTestAudioSdpName
;
486 base::HexStringToBytes(kTestAudioSdpBytes
, &audio_state
->sdp_bytes
);
487 laptop_state
->service_record_states
.push_back(audio_state
);
489 adapter_win_
->DevicesPolled(devices
);
490 EXPECT_EQ(0, observer_
.device_added_count());
491 EXPECT_EQ(0, observer_
.device_removed_count());
492 EXPECT_EQ(1, observer_
.device_changed_count());
495 audio_state
->name
= kTestAudioSdpName2
;
497 adapter_win_
->DevicesPolled(devices
);
498 EXPECT_EQ(0, observer_
.device_added_count());
499 EXPECT_EQ(0, observer_
.device_removed_count());
500 EXPECT_EQ(1, observer_
.device_changed_count());
503 laptop_state
->service_record_states
.clear();
505 adapter_win_
->DevicesPolled(devices
);
506 EXPECT_EQ(0, observer_
.device_added_count());
507 EXPECT_EQ(0, observer_
.device_removed_count());
508 EXPECT_EQ(1, observer_
.device_changed_count());
511 } // namespace device