OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "device/bluetooth/bluetooth_adapter_chromeos.h" | 5 #include "device/bluetooth/bluetooth_adapter_chromeos.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/metrics/histogram.h" | 11 #include "base/metrics/histogram.h" |
12 #include "base/sys_info.h" | 12 #include "base/sys_info.h" |
13 #include "chromeos/dbus/bluetooth_adapter_client.h" | 13 #include "chromeos/dbus/bluetooth_adapter_client.h" |
| 14 #include "chromeos/dbus/bluetooth_agent_manager_client.h" |
| 15 #include "chromeos/dbus/bluetooth_agent_service_provider.h" |
14 #include "chromeos/dbus/bluetooth_device_client.h" | 16 #include "chromeos/dbus/bluetooth_device_client.h" |
15 #include "chromeos/dbus/bluetooth_input_client.h" | 17 #include "chromeos/dbus/bluetooth_input_client.h" |
16 #include "chromeos/dbus/dbus_thread_manager.h" | 18 #include "chromeos/dbus/dbus_thread_manager.h" |
17 #include "device/bluetooth/bluetooth_device.h" | 19 #include "device/bluetooth/bluetooth_device.h" |
18 #include "device/bluetooth/bluetooth_device_chromeos.h" | 20 #include "device/bluetooth/bluetooth_device_chromeos.h" |
| 21 #include "third_party/cros_system_api/dbus/service_constants.h" |
19 | 22 |
20 using device::BluetoothAdapter; | 23 using device::BluetoothAdapter; |
21 using device::BluetoothDevice; | 24 using device::BluetoothDevice; |
22 | 25 |
| 26 namespace { |
| 27 |
| 28 // The agent path is relatively meaningless since BlueZ only permits one to |
| 29 // exist per D-Bus connection, it just has to be unique within Chromium. |
| 30 const char kAgentPath[] = "/org/chromium/bluetooth_agent"; |
| 31 |
| 32 // Histogram enumerations for pairing methods. |
| 33 enum UMAPairingMethod { |
| 34 UMA_PAIRING_METHOD_NONE, |
| 35 UMA_PAIRING_METHOD_REQUEST_PINCODE, |
| 36 UMA_PAIRING_METHOD_REQUEST_PASSKEY, |
| 37 UMA_PAIRING_METHOD_DISPLAY_PINCODE, |
| 38 UMA_PAIRING_METHOD_DISPLAY_PASSKEY, |
| 39 UMA_PAIRING_METHOD_CONFIRM_PASSKEY, |
| 40 // NOTE: Add new pairing methods immediately above this line. Make sure to |
| 41 // update the enum list in tools/histogram/histograms.xml accordingly. |
| 42 UMA_PAIRING_METHOD_COUNT |
| 43 }; |
| 44 |
| 45 void OnUnregisterAgentError(const std::string& error_name, |
| 46 const std::string& error_message) { |
| 47 LOG(WARNING) << "Failed to unregister pairing agent: " |
| 48 << error_name << ": " << error_message; |
| 49 } |
| 50 |
| 51 } // namespace |
| 52 |
23 namespace chromeos { | 53 namespace chromeos { |
24 | 54 |
25 BluetoothAdapterChromeOS::BluetoothAdapterChromeOS() | 55 BluetoothAdapterChromeOS::BluetoothAdapterChromeOS() |
26 : weak_ptr_factory_(this) { | 56 : weak_ptr_factory_(this) { |
27 DBusThreadManager::Get()->GetBluetoothAdapterClient()->AddObserver(this); | 57 DBusThreadManager::Get()->GetBluetoothAdapterClient()->AddObserver(this); |
28 DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); | 58 DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); |
29 DBusThreadManager::Get()->GetBluetoothInputClient()->AddObserver(this); | 59 DBusThreadManager::Get()->GetBluetoothInputClient()->AddObserver(this); |
30 | 60 |
31 std::vector<dbus::ObjectPath> object_paths = | 61 std::vector<dbus::ObjectPath> object_paths = |
32 DBusThreadManager::Get()->GetBluetoothAdapterClient()->GetAdapters(); | 62 DBusThreadManager::Get()->GetBluetoothAdapterClient()->GetAdapters(); |
33 | 63 |
34 if (!object_paths.empty()) { | 64 if (!object_paths.empty()) { |
35 VLOG(1) << object_paths.size() << " Bluetooth adapter(s) available."; | 65 VLOG(1) << object_paths.size() << " Bluetooth adapter(s) available."; |
36 SetAdapter(object_paths[0]); | 66 SetAdapter(object_paths[0]); |
37 } | 67 } |
| 68 |
| 69 // Register the pairing agent. |
| 70 dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); |
| 71 agent_.reset(BluetoothAgentServiceProvider::Create( |
| 72 system_bus, dbus::ObjectPath(kAgentPath), this)); |
| 73 DCHECK(agent_.get()); |
| 74 |
| 75 VLOG(1) << "Registering pairing agent"; |
| 76 DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> |
| 77 RegisterAgent( |
| 78 dbus::ObjectPath(kAgentPath), |
| 79 bluetooth_agent_manager::kKeyboardDisplayCapability, |
| 80 base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgent, |
| 81 weak_ptr_factory_.GetWeakPtr()), |
| 82 base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgentError, |
| 83 weak_ptr_factory_.GetWeakPtr())); |
38 } | 84 } |
39 | 85 |
40 BluetoothAdapterChromeOS::~BluetoothAdapterChromeOS() { | 86 BluetoothAdapterChromeOS::~BluetoothAdapterChromeOS() { |
41 DBusThreadManager::Get()->GetBluetoothAdapterClient()->RemoveObserver(this); | 87 DBusThreadManager::Get()->GetBluetoothAdapterClient()->RemoveObserver(this); |
42 DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); | 88 DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); |
43 DBusThreadManager::Get()->GetBluetoothInputClient()->RemoveObserver(this); | 89 DBusThreadManager::Get()->GetBluetoothInputClient()->RemoveObserver(this); |
| 90 |
| 91 VLOG(1) << "Unregistering pairing agent"; |
| 92 DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> |
| 93 UnregisterAgent( |
| 94 dbus::ObjectPath(kAgentPath), |
| 95 base::Bind(&base::DoNothing), |
| 96 base::Bind(&OnUnregisterAgentError)); |
44 } | 97 } |
45 | 98 |
46 void BluetoothAdapterChromeOS::AddObserver( | 99 void BluetoothAdapterChromeOS::AddObserver( |
47 BluetoothAdapter::Observer* observer) { | 100 BluetoothAdapter::Observer* observer) { |
48 DCHECK(observer); | 101 DCHECK(observer); |
49 observers_.AddObserver(observer); | 102 observers_.AddObserver(observer); |
50 } | 103 } |
51 | 104 |
52 void BluetoothAdapterChromeOS::RemoveObserver( | 105 void BluetoothAdapterChromeOS::RemoveObserver( |
53 BluetoothAdapter::Observer* observer) { | 106 BluetoothAdapter::Observer* observer) { |
(...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
308 GetProperties(object_path); | 361 GetProperties(object_path); |
309 | 362 |
310 // Properties structure can be removed, which triggers a change in the | 363 // Properties structure can be removed, which triggers a change in the |
311 // BluetoothDevice::IsConnectable() property, as does a change in the | 364 // BluetoothDevice::IsConnectable() property, as does a change in the |
312 // actual reconnect_mode property. | 365 // actual reconnect_mode property. |
313 if (!properties || | 366 if (!properties || |
314 property_name == properties->reconnect_mode.name()) | 367 property_name == properties->reconnect_mode.name()) |
315 NotifyDeviceChanged(device_chromeos); | 368 NotifyDeviceChanged(device_chromeos); |
316 } | 369 } |
317 | 370 |
| 371 void BluetoothAdapterChromeOS::Release() { |
| 372 DCHECK(agent_.get()); |
| 373 VLOG(1) << "Release"; |
| 374 |
| 375 // Called after we unregister the pairing agent, e.g. when changing I/O |
| 376 // capabilities. Nothing much to be done right now. |
| 377 } |
| 378 |
| 379 void BluetoothAdapterChromeOS::RequestPinCode( |
| 380 const dbus::ObjectPath& device_path, |
| 381 const PinCodeCallback& callback) { |
| 382 DCHECK(agent_.get()); |
| 383 VLOG(1) << device_path.value() << ": RequestPinCode"; |
| 384 |
| 385 BluetoothDeviceChromeOS* device_chromeos; |
| 386 PairingContext* pairing_context; |
| 387 if (!GetDeviceAndPairingContext(device_path, |
| 388 &device_chromeos, &pairing_context)) { |
| 389 callback.Run(REJECTED, ""); |
| 390 return; |
| 391 } |
| 392 |
| 393 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 394 UMA_PAIRING_METHOD_REQUEST_PINCODE, |
| 395 UMA_PAIRING_METHOD_COUNT); |
| 396 |
| 397 DCHECK(pairing_context->pincode_callback_.is_null()); |
| 398 pairing_context->pincode_callback_ = callback; |
| 399 pairing_context->pairing_delegate_->RequestPinCode(device_chromeos); |
| 400 pairing_context->pairing_delegate_used_ = true; |
| 401 } |
| 402 |
| 403 void BluetoothAdapterChromeOS::DisplayPinCode( |
| 404 const dbus::ObjectPath& device_path, |
| 405 const std::string& pincode) { |
| 406 DCHECK(agent_.get()); |
| 407 VLOG(1) << device_path.value() << ": DisplayPinCode: " << pincode; |
| 408 |
| 409 BluetoothDeviceChromeOS* device_chromeos; |
| 410 PairingContext* pairing_context; |
| 411 if (!GetDeviceAndPairingContext(device_path, |
| 412 &device_chromeos, &pairing_context)) |
| 413 return; |
| 414 |
| 415 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 416 UMA_PAIRING_METHOD_DISPLAY_PINCODE, |
| 417 UMA_PAIRING_METHOD_COUNT); |
| 418 |
| 419 pairing_context->pairing_delegate_->DisplayPinCode(device_chromeos, pincode); |
| 420 pairing_context->pairing_delegate_used_ = true; |
| 421 } |
| 422 |
| 423 void BluetoothAdapterChromeOS::RequestPasskey( |
| 424 const dbus::ObjectPath& device_path, |
| 425 const PasskeyCallback& callback) { |
| 426 DCHECK(agent_.get()); |
| 427 VLOG(1) << device_path.value() << ": RequestPasskey"; |
| 428 |
| 429 BluetoothDeviceChromeOS* device_chromeos; |
| 430 PairingContext* pairing_context; |
| 431 if (!GetDeviceAndPairingContext(device_path, |
| 432 &device_chromeos, &pairing_context)) { |
| 433 callback.Run(REJECTED, 0); |
| 434 return; |
| 435 } |
| 436 |
| 437 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 438 UMA_PAIRING_METHOD_REQUEST_PASSKEY, |
| 439 UMA_PAIRING_METHOD_COUNT); |
| 440 |
| 441 DCHECK(pairing_context->passkey_callback_.is_null()); |
| 442 pairing_context->passkey_callback_ = callback; |
| 443 pairing_context->pairing_delegate_->RequestPasskey(device_chromeos); |
| 444 pairing_context->pairing_delegate_used_ = true; |
| 445 } |
| 446 |
| 447 void BluetoothAdapterChromeOS::DisplayPasskey( |
| 448 const dbus::ObjectPath& device_path, |
| 449 uint32 passkey, |
| 450 uint16 entered) { |
| 451 DCHECK(agent_.get()); |
| 452 VLOG(1) << device_path.value() << ": DisplayPasskey: " << passkey |
| 453 << " (" << entered << " entered)"; |
| 454 |
| 455 BluetoothDeviceChromeOS* device_chromeos; |
| 456 PairingContext* pairing_context; |
| 457 if (!GetDeviceAndPairingContext(device_path, |
| 458 &device_chromeos, &pairing_context)) |
| 459 return; |
| 460 |
| 461 if (entered == 0) |
| 462 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 463 UMA_PAIRING_METHOD_DISPLAY_PASSKEY, |
| 464 UMA_PAIRING_METHOD_COUNT); |
| 465 |
| 466 if (entered == 0) |
| 467 pairing_context->pairing_delegate_->DisplayPasskey(device_chromeos, |
| 468 passkey); |
| 469 |
| 470 pairing_context->pairing_delegate_->KeysEntered(device_chromeos, entered); |
| 471 pairing_context->pairing_delegate_used_ = true; |
| 472 } |
| 473 |
| 474 void BluetoothAdapterChromeOS::RequestConfirmation( |
| 475 const dbus::ObjectPath& device_path, |
| 476 uint32 passkey, |
| 477 const ConfirmationCallback& callback) { |
| 478 DCHECK(agent_.get()); |
| 479 VLOG(1) << device_path.value() << ": RequestConfirmation: " << passkey; |
| 480 |
| 481 BluetoothDeviceChromeOS* device_chromeos; |
| 482 PairingContext* pairing_context; |
| 483 if (!GetDeviceAndPairingContext(device_path, |
| 484 &device_chromeos, &pairing_context)) { |
| 485 callback.Run(REJECTED); |
| 486 return; |
| 487 } |
| 488 |
| 489 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 490 UMA_PAIRING_METHOD_CONFIRM_PASSKEY, |
| 491 UMA_PAIRING_METHOD_COUNT); |
| 492 |
| 493 DCHECK(pairing_context->confirmation_callback_.is_null()); |
| 494 pairing_context->confirmation_callback_ = callback; |
| 495 pairing_context->pairing_delegate_->ConfirmPasskey(device_chromeos, passkey); |
| 496 pairing_context->pairing_delegate_used_ = true; |
| 497 } |
| 498 |
| 499 void BluetoothAdapterChromeOS::RequestAuthorization( |
| 500 const dbus::ObjectPath& device_path, |
| 501 const ConfirmationCallback& callback) { |
| 502 DCHECK(agent_.get()); |
| 503 VLOG(1) << device_path.value() << ": RequestAuthorization"; |
| 504 |
| 505 // TODO(keybuk): implement |
| 506 callback.Run(CANCELLED); |
| 507 } |
| 508 |
| 509 void BluetoothAdapterChromeOS::AuthorizeService( |
| 510 const dbus::ObjectPath& device_path, |
| 511 const std::string& uuid, |
| 512 const ConfirmationCallback& callback) { |
| 513 DCHECK(agent_.get()); |
| 514 VLOG(1) << device_path.value() << ": AuthorizeService: " << uuid; |
| 515 |
| 516 // TODO(keybuk): implement |
| 517 callback.Run(CANCELLED); |
| 518 } |
| 519 |
| 520 void BluetoothAdapterChromeOS::Cancel() { |
| 521 DCHECK(agent_.get()); |
| 522 VLOG(1) << "Cancel"; |
| 523 } |
| 524 |
| 525 bool BluetoothAdapterChromeOS::PairingContext::ExpectingPinCode() const { |
| 526 return !pincode_callback_.is_null(); |
| 527 } |
| 528 |
| 529 bool BluetoothAdapterChromeOS::PairingContext::ExpectingPasskey() const { |
| 530 return !passkey_callback_.is_null(); |
| 531 } |
| 532 |
| 533 bool BluetoothAdapterChromeOS::PairingContext::ExpectingConfirmation() const { |
| 534 return !confirmation_callback_.is_null(); |
| 535 } |
| 536 |
| 537 void BluetoothAdapterChromeOS::PairingContext::SetPinCode( |
| 538 const std::string& pincode) { |
| 539 if (pincode_callback_.is_null()) |
| 540 return; |
| 541 |
| 542 pincode_callback_.Run(SUCCESS, pincode); |
| 543 pincode_callback_.Reset(); |
| 544 } |
| 545 |
| 546 void BluetoothAdapterChromeOS::PairingContext::SetPasskey(uint32 passkey) { |
| 547 if (passkey_callback_.is_null()) |
| 548 return; |
| 549 |
| 550 passkey_callback_.Run(SUCCESS, passkey); |
| 551 passkey_callback_.Reset(); |
| 552 } |
| 553 |
| 554 void BluetoothAdapterChromeOS::PairingContext::ConfirmPairing() { |
| 555 if (confirmation_callback_.is_null()) |
| 556 return; |
| 557 |
| 558 confirmation_callback_.Run(SUCCESS); |
| 559 confirmation_callback_.Reset(); |
| 560 } |
| 561 |
| 562 bool BluetoothAdapterChromeOS::PairingContext::RejectPairing() { |
| 563 return RunPairingCallbacks(REJECTED); |
| 564 } |
| 565 |
| 566 bool BluetoothAdapterChromeOS::PairingContext::CancelPairing() { |
| 567 return RunPairingCallbacks(CANCELLED); |
| 568 } |
| 569 |
| 570 BluetoothAdapterChromeOS::PairingContext::PairingContext( |
| 571 BluetoothDevice::PairingDelegate* pairing_delegate) |
| 572 : pairing_delegate_(pairing_delegate), |
| 573 pairing_delegate_used_(false) { |
| 574 VLOG(1) << "Created PairingContext"; |
| 575 } |
| 576 |
| 577 BluetoothAdapterChromeOS::PairingContext::~PairingContext() { |
| 578 VLOG(1) << "Destroying PairingContext"; |
| 579 |
| 580 if (!pairing_delegate_used_) |
| 581 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingMethod", |
| 582 UMA_PAIRING_METHOD_NONE, |
| 583 UMA_PAIRING_METHOD_COUNT); |
| 584 |
| 585 DCHECK(pincode_callback_.is_null()); |
| 586 DCHECK(passkey_callback_.is_null()); |
| 587 DCHECK(confirmation_callback_.is_null()); |
| 588 |
| 589 pairing_delegate_->DismissDisplayOrConfirm(); |
| 590 pairing_delegate_ = NULL; |
| 591 } |
| 592 |
| 593 bool BluetoothAdapterChromeOS::PairingContext::RunPairingCallbacks( |
| 594 BluetoothAgentServiceProvider::Delegate::Status status) { |
| 595 pairing_delegate_used_ = true; |
| 596 |
| 597 bool callback_run = false; |
| 598 if (!pincode_callback_.is_null()) { |
| 599 pincode_callback_.Run(status, ""); |
| 600 pincode_callback_.Reset(); |
| 601 callback_run = true; |
| 602 } |
| 603 |
| 604 if (!passkey_callback_.is_null()) { |
| 605 passkey_callback_.Run(status, 0); |
| 606 passkey_callback_.Reset(); |
| 607 callback_run = true; |
| 608 } |
| 609 |
| 610 if (!confirmation_callback_.is_null()) { |
| 611 confirmation_callback_.Run(status); |
| 612 confirmation_callback_.Reset(); |
| 613 callback_run = true; |
| 614 } |
| 615 |
| 616 return callback_run; |
| 617 } |
| 618 |
| 619 void BluetoothAdapterChromeOS::OnRegisterAgent() { |
| 620 VLOG(1) << "Pairing agent registered"; |
| 621 } |
| 622 |
| 623 void BluetoothAdapterChromeOS::OnRegisterAgentError( |
| 624 const std::string& error_name, |
| 625 const std::string& error_message) { |
| 626 LOG(WARNING) << ": Failed to register pairing agent: " |
| 627 << error_name << ": " << error_message; |
| 628 |
| 629 agent_.reset(); |
| 630 } |
| 631 |
318 BluetoothDeviceChromeOS* | 632 BluetoothDeviceChromeOS* |
319 BluetoothAdapterChromeOS::GetDeviceWithPath( | 633 BluetoothAdapterChromeOS::GetDeviceWithPath( |
320 const dbus::ObjectPath& object_path) { | 634 const dbus::ObjectPath& object_path) { |
321 for (DevicesMap::iterator iter = devices_.begin(); | 635 for (DevicesMap::iterator iter = devices_.begin(); |
322 iter != devices_.end(); ++iter) { | 636 iter != devices_.end(); ++iter) { |
323 BluetoothDeviceChromeOS* device_chromeos = | 637 BluetoothDeviceChromeOS* device_chromeos = |
324 static_cast<BluetoothDeviceChromeOS*>(iter->second); | 638 static_cast<BluetoothDeviceChromeOS*>(iter->second); |
325 if (device_chromeos->object_path() == object_path) | 639 if (device_chromeos->object_path() == object_path) |
326 return device_chromeos; | 640 return device_chromeos; |
327 } | 641 } |
328 | 642 |
329 return NULL; | 643 return NULL; |
330 } | 644 } |
331 | 645 |
| 646 bool BluetoothAdapterChromeOS::GetDeviceAndPairingContext( |
| 647 const dbus::ObjectPath& object_path, |
| 648 BluetoothDeviceChromeOS** device_chromeos, |
| 649 PairingContext** pairing_context) |
| 650 { |
| 651 *device_chromeos = GetDeviceWithPath(object_path); |
| 652 if (!device_chromeos) { |
| 653 LOG(WARNING) << "Pairing Agent request for unknown device: " |
| 654 << object_path.value(); |
| 655 return false; |
| 656 } |
| 657 |
| 658 *pairing_context = (*device_chromeos)->pairing_context_.get(); |
| 659 if (*pairing_context) |
| 660 return true; |
| 661 |
| 662 // TODO(keybuk): this is the point we need a default pairing delegate, create |
| 663 // a PairingContext with that passed in, set it as the context on the device |
| 664 // and return true. |
| 665 return false; |
| 666 } |
| 667 |
332 void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { | 668 void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { |
333 DCHECK(!IsPresent()); | 669 DCHECK(!IsPresent()); |
334 object_path_ = object_path; | 670 object_path_ = object_path; |
335 | 671 |
336 VLOG(1) << object_path_.value() << ": using adapter."; | 672 VLOG(1) << object_path_.value() << ": using adapter."; |
337 | 673 |
338 SetDefaultAdapterName(); | 674 SetDefaultAdapterName(); |
339 | 675 |
340 BluetoothAdapterClient::Properties* properties = | 676 BluetoothAdapterClient::Properties* properties = |
341 DBusThreadManager::Get()->GetBluetoothAdapterClient()-> | 677 DBusThreadManager::Get()->GetBluetoothAdapterClient()-> |
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
486 void BluetoothAdapterChromeOS::OnStopDiscoveryError( | 822 void BluetoothAdapterChromeOS::OnStopDiscoveryError( |
487 const ErrorCallback& error_callback, | 823 const ErrorCallback& error_callback, |
488 const std::string& error_name, | 824 const std::string& error_name, |
489 const std::string& error_message) { | 825 const std::string& error_message) { |
490 LOG(WARNING) << object_path_.value() << ": Failed to stop discovery: " | 826 LOG(WARNING) << object_path_.value() << ": Failed to stop discovery: " |
491 << error_name << ": " << error_message; | 827 << error_name << ": " << error_message; |
492 error_callback.Run(); | 828 error_callback.Run(); |
493 } | 829 } |
494 | 830 |
495 } // namespace chromeos | 831 } // namespace chromeos |
OLD | NEW |