diff options
author | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-18 04:41:42 +0000 |
---|---|---|
committer | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-18 04:41:42 +0000 |
commit | 896619e714a5510c34a8d2b3d80741786d96e30b (patch) | |
tree | 9ef02ddc4df476097001c764e8172d18df804071 /device/bluetooth | |
parent | b520cfffb5be192d59638f5a8530ba88933f8973 (diff) | |
download | chromium_src-896619e714a5510c34a8d2b3d80741786d96e30b.zip chromium_src-896619e714a5510c34a8d2b3d80741786d96e30b.tar.gz chromium_src-896619e714a5510c34a8d2b3d80741786d96e30b.tar.bz2 |
Bluetooth: implement BlueZ 5 backend for Chrome OS
Provides an implementation for adapter control, device discovery,
pairing, connection, disconnection and unpairing using the BlueZ 5.x
backend on Chrome OS.
Uses Fake* classes for testing instead of Mocks.
TBR=youngki@chromium.org
BUG=220951
TEST=device_unittests
Review URL: https://chromiumcodereview.appspot.com/13927010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@194760 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device/bluetooth')
5 files changed, 3044 insertions, 14 deletions
diff --git a/device/bluetooth/bluetooth_adapter_experimental_chromeos.cc b/device/bluetooth/bluetooth_adapter_experimental_chromeos.cc index 8cdfb96..2af9376 100644 --- a/device/bluetooth/bluetooth_adapter_experimental_chromeos.cc +++ b/device/bluetooth/bluetooth_adapter_experimental_chromeos.cc @@ -6,32 +6,77 @@ #include <string> +#include "base/bind.h" +#include "base/logging.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/experimental_bluetooth_adapter_client.h" +#include "chromeos/dbus/experimental_bluetooth_device_client.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_experimental_chromeos.h" + using device::BluetoothAdapter; +using device::BluetoothDevice; namespace chromeos { BluetoothAdapterExperimentalChromeOS::BluetoothAdapterExperimentalChromeOS() - : BluetoothAdapter(), - weak_ptr_factory_(this) { + : weak_ptr_factory_(this) { + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + AddObserver(this); + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + AddObserver(this); + + std::vector<dbus::ObjectPath> object_paths = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetAdapters(); + + if (!object_paths.empty()) { + VLOG(1) << object_paths.size() << " Bluetooth adapter(s) available."; + SetAdapter(object_paths[0]); + } } BluetoothAdapterExperimentalChromeOS::~BluetoothAdapterExperimentalChromeOS() { + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + RemoveObserver(this); + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + RemoveObserver(this); } void BluetoothAdapterExperimentalChromeOS::AddObserver( BluetoothAdapter::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); } void BluetoothAdapterExperimentalChromeOS::RemoveObserver( BluetoothAdapter::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); } std::string BluetoothAdapterExperimentalChromeOS::GetAddress() const { - return std::string(); + if (!IsPresent()) + return std::string(); + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->address.value(); } std::string BluetoothAdapterExperimentalChromeOS::GetName() const { - return std::string(); + if (!IsPresent()) + return std::string(); + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->alias.value(); } bool BluetoothAdapterExperimentalChromeOS::IsInitialized() const { @@ -39,33 +84,78 @@ bool BluetoothAdapterExperimentalChromeOS::IsInitialized() const { } bool BluetoothAdapterExperimentalChromeOS::IsPresent() const { - return false; + return !object_path_.value().empty(); } bool BluetoothAdapterExperimentalChromeOS::IsPowered() const { - return false; + if (!IsPresent()) + return false; + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + + return properties->powered.value(); } -void BluetoothAdapterExperimentalChromeOS::SetPowered(bool powered, - const base::Closure& callback, - const ErrorCallback& error_callback) { - error_callback.Run(); +void BluetoothAdapterExperimentalChromeOS::SetPowered( + bool powered, + const base::Closure& callback, + const ErrorCallback& error_callback) { + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_)->powered.Set( + powered, + base::Bind(&BluetoothAdapterExperimentalChromeOS::OnSetPowered, + weak_ptr_factory_.GetWeakPtr(), + callback, + error_callback)); } bool BluetoothAdapterExperimentalChromeOS::IsDiscovering() const { - return false; + if (!IsPresent()) + return false; + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + + return properties->discovering.value(); } void BluetoothAdapterExperimentalChromeOS::StartDiscovering( const base::Closure& callback, const ErrorCallback& error_callback) { - error_callback.Run(); + // BlueZ counts discovery sessions, and permits multiple sessions for a + // single connection, so issue a StartDiscovery() call for every use + // within Chromium for the right behavior. + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + StartDiscovery( + object_path_, + base::Bind( + &BluetoothAdapterExperimentalChromeOS::OnStartDiscovery, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind( + &BluetoothAdapterExperimentalChromeOS::OnStartDiscoveryError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); } void BluetoothAdapterExperimentalChromeOS::StopDiscovering( const base::Closure& callback, const ErrorCallback& error_callback) { - error_callback.Run(); + // Inform BlueZ to stop one of our open discovery sessions. + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + StopDiscovery( + object_path_, + base::Bind( + &BluetoothAdapterExperimentalChromeOS::OnStopDiscovery, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind( + &BluetoothAdapterExperimentalChromeOS::OnStopDiscoveryError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); } void BluetoothAdapterExperimentalChromeOS::ReadLocalOutOfBandPairingData( @@ -74,4 +164,223 @@ void BluetoothAdapterExperimentalChromeOS::ReadLocalOutOfBandPairingData( error_callback.Run(); } +void BluetoothAdapterExperimentalChromeOS::AdapterAdded( + const dbus::ObjectPath& object_path) { + // Set the adapter to the newly added adapter only if no adapter is present. + if (!IsPresent()) + SetAdapter(object_path); +} + +void BluetoothAdapterExperimentalChromeOS::AdapterRemoved( + const dbus::ObjectPath& object_path) { + if (object_path == object_path_) + RemoveAdapter(); +} + +void BluetoothAdapterExperimentalChromeOS::AdapterPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (object_path != object_path_) + return; + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + + if (property_name == properties->powered.name()) + PoweredChanged(properties->powered.value()); + else if (property_name == properties->discovering.name()) + DiscoveringChanged(properties->discovering.value()); +} + +void BluetoothAdapterExperimentalChromeOS::DeviceAdded( + const dbus::ObjectPath& object_path) { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path); + if (properties->adapter.value() != object_path_) + return; + + BluetoothDeviceExperimentalChromeOS* device_chromeos = + new BluetoothDeviceExperimentalChromeOS(this, object_path); + DCHECK(devices_.find(device_chromeos->GetAddress()) == devices_.end()); + + devices_[device_chromeos->GetAddress()] = device_chromeos; + + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + DeviceAdded(this, device_chromeos)); +} + +void BluetoothAdapterExperimentalChromeOS::DeviceRemoved( + const dbus::ObjectPath& object_path) { + for (DevicesMap::iterator iter = devices_.begin(); + iter != devices_.end(); ++iter) { + BluetoothDeviceExperimentalChromeOS* device_chromeos = + static_cast<BluetoothDeviceExperimentalChromeOS*>(iter->second); + if (device_chromeos->object_path() == object_path) { + devices_.erase(iter); + + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + DeviceRemoved(this, device_chromeos)); + delete device_chromeos; + return; + } + } +} + +void BluetoothAdapterExperimentalChromeOS::DevicePropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + BluetoothDeviceExperimentalChromeOS* device_chromeos = + GetDeviceWithPath(object_path); + if (!device_chromeos) + return; + + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path); + + if (property_name == properties->bluetooth_class.name() || + property_name == properties->alias.name() || + property_name == properties->paired.name() || + property_name == properties->connected.name() || + property_name == properties->uuids.name()) { + FOR_EACH_OBSERVER( + BluetoothAdapter::Observer, observers_, + DeviceChanged(this, device_chromeos)); + } +} + +BluetoothDeviceExperimentalChromeOS* +BluetoothAdapterExperimentalChromeOS::GetDeviceWithPath( + const dbus::ObjectPath& object_path) { + for (DevicesMap::iterator iter = devices_.begin(); + iter != devices_.end(); ++iter) { + BluetoothDeviceExperimentalChromeOS* device_chromeos = + static_cast<BluetoothDeviceExperimentalChromeOS*>(iter->second); + if (device_chromeos->object_path() == object_path) + return device_chromeos; + } + + return NULL; +} + +void BluetoothAdapterExperimentalChromeOS::SetAdapter( + const dbus::ObjectPath& object_path) { + DCHECK(!IsPresent()); + object_path_ = object_path; + + VLOG(1) << object_path_.value() << ": using adapter."; + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + + PresentChanged(true); + + if (properties->powered.value()) + PoweredChanged(true); + if (properties->discovering.value()) + DiscoveringChanged(true); + + std::vector<dbus::ObjectPath> device_paths = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetDevicesForAdapter(object_path_); + + for (std::vector<dbus::ObjectPath>::iterator iter = device_paths.begin(); + iter != device_paths.end(); ++iter) { + BluetoothDeviceExperimentalChromeOS* device_chromeos = + new BluetoothDeviceExperimentalChromeOS(this, *iter); + + devices_[device_chromeos->GetAddress()] = device_chromeos; + + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + DeviceAdded(this, device_chromeos)); + } +} + +void BluetoothAdapterExperimentalChromeOS::RemoveAdapter() { + DCHECK(IsPresent()); + VLOG(1) << object_path_.value() << ": adapter removed."; + + ExperimentalBluetoothAdapterClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + GetProperties(object_path_); + + object_path_ = dbus::ObjectPath(""); + + if (properties->powered.value()) + PoweredChanged(false); + if (properties->discovering.value()) + DiscoveringChanged(false); + + // Copy the devices list here and clear the original so that when we + // send DeviceRemoved(), GetDevices() returns no devices. + DevicesMap devices = devices_; + devices_.clear(); + + for (DevicesMap::iterator iter = devices.begin(); + iter != devices.end(); ++iter) { + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + DeviceRemoved(this, iter->second)); + delete iter->second; + } + + PresentChanged(false); +} + +void BluetoothAdapterExperimentalChromeOS::PoweredChanged(bool powered) { + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + AdapterPoweredChanged(this, powered)); +} + +void BluetoothAdapterExperimentalChromeOS::DiscoveringChanged( + bool discovering) { + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + AdapterDiscoveringChanged(this, discovering)); +} + +void BluetoothAdapterExperimentalChromeOS::PresentChanged(bool present) { + FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, + AdapterPresentChanged(this, present)); +} + +void BluetoothAdapterExperimentalChromeOS::OnSetPowered( + const base::Closure& callback, + const ErrorCallback& error_callback, + bool success) { + if (success) + callback.Run(); + else + error_callback.Run(); +} + +void BluetoothAdapterExperimentalChromeOS::OnStartDiscovery( + const base::Closure& callback) { + callback.Run(); +} + +void BluetoothAdapterExperimentalChromeOS::OnStartDiscoveryError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to start discovery: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +void BluetoothAdapterExperimentalChromeOS::OnStopDiscovery( + const base::Closure& callback) { + callback.Run(); +} + +void BluetoothAdapterExperimentalChromeOS::OnStopDiscoveryError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to stop discovery: " + << error_name << ": " << error_message; + error_callback.Run(); +} + } // namespace chromeos diff --git a/device/bluetooth/bluetooth_adapter_experimental_chromeos.h b/device/bluetooth/bluetooth_adapter_experimental_chromeos.h index 1379352..8471b0f 100644 --- a/device/bluetooth/bluetooth_adapter_experimental_chromeos.h +++ b/device/bluetooth/bluetooth_adapter_experimental_chromeos.h @@ -8,6 +8,9 @@ #include <string> #include "base/memory/weak_ptr.h" +#include "chromeos/dbus/experimental_bluetooth_adapter_client.h" +#include "chromeos/dbus/experimental_bluetooth_device_client.h" +#include "dbus/object_path.h" #include "device/bluetooth/bluetooth_adapter.h" namespace device { @@ -18,12 +21,17 @@ class BluetoothAdapterFactory; namespace chromeos { +class BluetoothDeviceExperimentalChromeOS; +class BluetoothExperimentalChromeOSTest; + // The BluetoothAdapterExperimentalChromeOS class is an alternate implementation // of BluetoothAdapter for the Chrome OS platform using the Bluetooth Smart // capable backend. It will become the sole implementation for Chrome OS, and // be renamed to BluetoothAdapterChromeOS, once the backend is switched, class BluetoothAdapterExperimentalChromeOS - : public device::BluetoothAdapter { + : public device::BluetoothAdapter, + private chromeos::ExperimentalBluetoothAdapterClient::Observer, + private chromeos::ExperimentalBluetoothDeviceClient::Observer { public: // BluetoothAdapter override virtual void AddObserver( @@ -53,10 +61,66 @@ class BluetoothAdapterExperimentalChromeOS private: friend class device::BluetoothAdapterFactory; + friend class BluetoothDeviceExperimentalChromeOS; + friend class BluetoothExperimentalChromeOSTest; BluetoothAdapterExperimentalChromeOS(); virtual ~BluetoothAdapterExperimentalChromeOS(); + // ExperimentalBluetoothAdapterClient::Observer override. + virtual void AdapterAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void AdapterRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void AdapterPropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // ExperimentalBluetoothDeviceClient::Observer override. + virtual void DeviceAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // Internal method used to locate the device object by object path + // (the devices map and BluetoothDevice methods are by address) + BluetoothDeviceExperimentalChromeOS* GetDeviceWithPath( + const dbus::ObjectPath& object_path); + + // Set the tracked adapter to the one in |object_path|, this object will + // subsequently operate on that adapter until it is removed. + void SetAdapter(const dbus::ObjectPath& object_path); + + // Remove the currently tracked adapter. IsPresent() will return false after + // this is called. + void RemoveAdapter(); + + // Announce to observers a change in the adapter state. + void PoweredChanged(bool powered); + void DiscoveringChanged(bool discovering); + void PresentChanged(bool present); + + // Called by dbus:: on completion of the powered property change. + void OnSetPowered(const base::Closure& callback, + const ErrorCallback& error_callback, + bool success); + + // Called by dbus:: on completion of the D-Bus method call to start discovery. + void OnStartDiscovery(const base::Closure& callback); + void OnStartDiscoveryError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to stop discovery. + void OnStopDiscovery(const base::Closure& callback); + void OnStopDiscoveryError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Object path of the adapter we track. + dbus::ObjectPath object_path_; + + // List of observers interested in event notifications from us. + ObserverList<device::BluetoothAdapter::Observer> observers_; + // Note: This should remain the last member so it'll be destroyed and // invalidate its weak pointers before any other members are destroyed. base::WeakPtrFactory<BluetoothAdapterExperimentalChromeOS> weak_ptr_factory_; diff --git a/device/bluetooth/bluetooth_device_experimental_chromeos.cc b/device/bluetooth/bluetooth_device_experimental_chromeos.cc new file mode 100644 index 0000000..fb36c845 --- /dev/null +++ b/device/bluetooth/bluetooth_device_experimental_chromeos.cc @@ -0,0 +1,609 @@ +// Copyright (c) 2013 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 "device/bluetooth/bluetooth_device_experimental_chromeos.h" + +#include "base/bind.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/experimental_bluetooth_adapter_client.h" +#include "chromeos/dbus/experimental_bluetooth_agent_manager_client.h" +#include "chromeos/dbus/experimental_bluetooth_agent_service_provider.h" +#include "chromeos/dbus/experimental_bluetooth_device_client.h" +#include "dbus/bus.h" +#include "device/bluetooth/bluetooth_adapter_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::BluetoothDevice; + +namespace { + +// The agent path is relatively meaningless since BlueZ only supports one +// at time and will fail in an attempt to register another with "Already Exists" +// (which we fail in OnRegisterAgentError with ERROR_INPROGRESS). +const char kAgentPath[] = "/org/chromium/bluetooth_agent"; + +} // namespace + +namespace chromeos { + +BluetoothDeviceExperimentalChromeOS::BluetoothDeviceExperimentalChromeOS( + BluetoothAdapterExperimentalChromeOS* adapter, + const dbus::ObjectPath& object_path) + : adapter_(adapter), + object_path_(object_path), + num_connecting_calls_(0), + pairing_delegate_(NULL), + weak_ptr_factory_(this) { +} + +BluetoothDeviceExperimentalChromeOS::~BluetoothDeviceExperimentalChromeOS() { +} + +uint32 BluetoothDeviceExperimentalChromeOS::GetBluetoothClass() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->bluetooth_class.value(); +} + +std::string BluetoothDeviceExperimentalChromeOS::GetDeviceName() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->alias.value(); +} + +std::string BluetoothDeviceExperimentalChromeOS::GetAddress() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->address.value(); +} + +bool BluetoothDeviceExperimentalChromeOS::IsPaired() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->paired.value(); +} + +bool BluetoothDeviceExperimentalChromeOS::IsConnected() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->connected.value(); +} + +bool BluetoothDeviceExperimentalChromeOS::IsConnectable() const { + // TODO(deymo): implement + return false; +} + +bool BluetoothDeviceExperimentalChromeOS::IsConnecting() const { + return num_connecting_calls_ > 0; +} + +BluetoothDeviceExperimentalChromeOS::ServiceList +BluetoothDeviceExperimentalChromeOS::GetServices() const { + ExperimentalBluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_); + DCHECK(properties); + + return properties->uuids.value(); +} + +void BluetoothDeviceExperimentalChromeOS::GetServiceRecords( + const ServiceRecordsCallback& callback, + const ErrorCallback& error_callback) { + // TODO(keybuk): not implemented; remove + error_callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::ProvidesServiceWithName( + const std::string& name, + const ProvidesServiceCallback& callback) { + // TODO(keybuk): not implemented; remove + callback.Run(false); +} + +bool BluetoothDeviceExperimentalChromeOS::ExpectingPinCode() const { + return !pincode_callback_.is_null(); +} + +bool BluetoothDeviceExperimentalChromeOS::ExpectingPasskey() const { + return !passkey_callback_.is_null(); +} + +bool BluetoothDeviceExperimentalChromeOS::ExpectingConfirmation() const { + return !confirmation_callback_.is_null(); +} + +void BluetoothDeviceExperimentalChromeOS::Connect( + BluetoothDevice::PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + ++num_connecting_calls_; + VLOG(1) << object_path_.value() << ": Connecting, " << num_connecting_calls_ + << " in progress"; + + if (IsPaired() || IsConnected() || !pairing_delegate) { + // No need to pair, skip straight to connection. + ConnectInternal(callback, error_callback); + } else { + // Initiate high-security connection with pairing. + DCHECK(!pairing_delegate_); + DCHECK(agent_.get() == NULL); + + pairing_delegate_ = pairing_delegate; + + // The agent path is relatively meaningless since BlueZ only supports + // one per application at a time. + dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); + agent_.reset(ExperimentalBluetoothAgentServiceProvider::Create( + system_bus, dbus::ObjectPath(kAgentPath), this)); + DCHECK(agent_.get()); + + VLOG(1) << object_path_.value() << ": Registering agent for pairing"; + DBusThreadManager::Get()->GetExperimentalBluetoothAgentManagerClient()-> + RegisterAgent( + dbus::ObjectPath(kAgentPath), + bluetooth_agent_manager::kKeyboardDisplayCapability, + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnRegisterAgent, + weak_ptr_factory_.GetWeakPtr(), + callback, + error_callback), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnRegisterAgentError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); + } +} + +void BluetoothDeviceExperimentalChromeOS::SetPinCode( + const std::string& pincode) { + if (!agent_.get() || pincode_callback_.is_null()) + return; + + pincode_callback_.Run(SUCCESS, pincode); + pincode_callback_.Reset(); +} + +void BluetoothDeviceExperimentalChromeOS::SetPasskey(uint32 passkey) { + if (!agent_.get() || passkey_callback_.is_null()) + return; + + passkey_callback_.Run(SUCCESS, passkey); + passkey_callback_.Reset(); +} + +void BluetoothDeviceExperimentalChromeOS::ConfirmPairing() { + if (!agent_.get() || confirmation_callback_.is_null()) + return; + + confirmation_callback_.Run(SUCCESS); + confirmation_callback_.Reset(); +} + +void BluetoothDeviceExperimentalChromeOS::RejectPairing() { + RunPairingCallbacks(REJECTED); +} + +void BluetoothDeviceExperimentalChromeOS::CancelPairing() { + // If there wasn't a callback in progress that we can reply to then we + // have to send a CancelPairing() to the device instead. + if (!RunPairingCallbacks(CANCELLED)) { + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + CancelPairing( + object_path_, + base::Bind(&base::DoNothing), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnCancelPairingError, + weak_ptr_factory_.GetWeakPtr())); + } +} + +void BluetoothDeviceExperimentalChromeOS::Disconnect( + const base::Closure& callback, + const ErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Disconnecting"; + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + Disconnect( + object_path_, + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnDisconnect, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnDisconnectError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothDeviceExperimentalChromeOS::Forget( + const ErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Removing device"; + DBusThreadManager::Get()->GetExperimentalBluetoothAdapterClient()-> + RemoveDevice( + adapter_->object_path_, + object_path_, + base::Bind(&base::DoNothing), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnForgetError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothDeviceExperimentalChromeOS::ConnectToService( + const std::string& service_uuid, + const SocketCallback& callback) { + // TODO(keybuk): implement + callback.Run(scoped_refptr<device::BluetoothSocket>()); +} + +void BluetoothDeviceExperimentalChromeOS::SetOutOfBandPairingData( + const device::BluetoothOutOfBandPairingData& data, + const base::Closure& callback, + const ErrorCallback& error_callback) { + // TODO(keybuk): implement + error_callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::ClearOutOfBandPairingData( + const base::Closure& callback, + const ErrorCallback& error_callback) { + // TODO(keybuk): implement + error_callback.Run(); +} + + +void BluetoothDeviceExperimentalChromeOS::Release() { + DCHECK(agent_.get()); + DCHECK(pairing_delegate_); + VLOG(1) << object_path_.value() << ": Release"; + + pincode_callback_.Reset(); + passkey_callback_.Reset(); + confirmation_callback_.Reset(); + + UnregisterAgent(); +} + +void BluetoothDeviceExperimentalChromeOS::RequestPinCode( + const dbus::ObjectPath& device_path, + const PinCodeCallback& callback) { + DCHECK(agent_.get()); + DCHECK(device_path == object_path_); + VLOG(1) << object_path_.value() << ": RequestPinCode"; + + DCHECK(pairing_delegate_); + DCHECK(pincode_callback_.is_null()); + pincode_callback_ = callback; + pairing_delegate_->RequestPinCode(this); +} + +void BluetoothDeviceExperimentalChromeOS::DisplayPinCode( + const dbus::ObjectPath& device_path, + const std::string& pincode) { + DCHECK(agent_.get()); + DCHECK(device_path == object_path_); + VLOG(1) << object_path_.value() << ": DisplayPinCode: " << pincode; + + DCHECK(pairing_delegate_); + pairing_delegate_->DisplayPinCode(this, pincode); +} + +void BluetoothDeviceExperimentalChromeOS::RequestPasskey( + const dbus::ObjectPath& device_path, + const PasskeyCallback& callback) { + DCHECK(agent_.get()); + DCHECK(device_path == object_path_); + VLOG(1) << object_path_.value() << ": RequestPasskey"; + + DCHECK(pairing_delegate_); + DCHECK(passkey_callback_.is_null()); + passkey_callback_ = callback; + pairing_delegate_->RequestPasskey(this); +} + +void BluetoothDeviceExperimentalChromeOS::DisplayPasskey( + const dbus::ObjectPath& device_path, + uint32 passkey, int16 entered) { + DCHECK(agent_.get()); + DCHECK(device_path == object_path_); + VLOG(1) << object_path_.value() << ": DisplayPasskey: " << passkey + << " (" << entered << " entered)"; + + // TODO(keybuk): disambiguate entered vs display + if (entered > 0) + return; + + DCHECK(pairing_delegate_); + pairing_delegate_->DisplayPasskey(this, passkey); +} + +void BluetoothDeviceExperimentalChromeOS::RequestConfirmation( + const dbus::ObjectPath& device_path, + uint32 passkey, + const ConfirmationCallback& callback) { + DCHECK(agent_.get()); + DCHECK(device_path == object_path_); + VLOG(1) << object_path_.value() << ": RequestConfirmation: " << passkey; + + DCHECK(pairing_delegate_); + DCHECK(confirmation_callback_.is_null()); + confirmation_callback_ = callback; + pairing_delegate_->ConfirmPasskey(this, passkey); +} + +void BluetoothDeviceExperimentalChromeOS::RequestAuthorization( + const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) { + // TODO(keybuk): implement + callback.Run(CANCELLED); +} + +void BluetoothDeviceExperimentalChromeOS::AuthorizeService( + const dbus::ObjectPath& device_path, + const std::string& uuid, + const ConfirmationCallback& callback) { + // TODO(keybuk): implement + callback.Run(CANCELLED); +} + +void BluetoothDeviceExperimentalChromeOS::Cancel() { + DCHECK(agent_.get()); + VLOG(1) << object_path_.value() << ": Cancel"; + + DCHECK(pairing_delegate_); + pairing_delegate_->DismissDisplayOrConfirm(); +} + +void BluetoothDeviceExperimentalChromeOS::ConnectInternal( + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Connecting"; + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + Connect( + object_path_, + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnConnect, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnConnectError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothDeviceExperimentalChromeOS::OnConnect( + const base::Closure& callback) { + --num_connecting_calls_; + DCHECK(num_connecting_calls_ >= 0); + VLOG(1) << object_path_.value() << ": Connected, " << num_connecting_calls_ + << " still in progress"; + + callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::OnConnectError( + const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + --num_connecting_calls_; + DCHECK(num_connecting_calls_ >= 0); + LOG(WARNING) << object_path_.value() << ": Failed to connect device: " + << error_name << ": " << error_message; + VLOG(1) << object_path_.value() << ": " << num_connecting_calls_ + << " still in progress"; + + // Determine the error code from error_name. + ConnectErrorCode error_code = ERROR_UNKNOWN; + if (error_name == bluetooth_adapter::kErrorFailed) { + error_code = ERROR_FAILED; + } else if (error_name == bluetooth_adapter::kErrorInProgress) { + error_code = ERROR_INPROGRESS; + } else if (error_name == bluetooth_adapter::kErrorNotSupported) { + error_code = ERROR_UNSUPPORTED_DEVICE; + } + + error_callback.Run(error_code); +} + +void BluetoothDeviceExperimentalChromeOS::OnRegisterAgent( + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Agent registered, now pairing"; + + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + Pair(object_path_, + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnPair, + weak_ptr_factory_.GetWeakPtr(), + callback, error_callback), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnPairError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void BluetoothDeviceExperimentalChromeOS::OnRegisterAgentError( + const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + --num_connecting_calls_; + DCHECK(num_connecting_calls_ >= 0); + LOG(WARNING) << object_path_.value() << ": Failed to register agent: " + << error_name << ": " << error_message; + VLOG(1) << object_path_.value() << ": " << num_connecting_calls_ + << " still in progress"; + + UnregisterAgent(); + + // Determine the error code from error_name. + ConnectErrorCode error_code = ERROR_UNKNOWN; + if (error_name == bluetooth_adapter::kErrorAlreadyExists) + error_code = ERROR_INPROGRESS; + + error_callback.Run(error_code); +} + +void BluetoothDeviceExperimentalChromeOS::OnPair( + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + VLOG(1) << object_path_.value() << ": Paired"; + + // Now that we're paired, we need to set the device as trusted so that + // incoming connections will be accepted. This should only ever fail if + // the device is removed mid-pairing, so do it in the background while + // we connect and don't worry about errors. + DBusThreadManager::Get()->GetExperimentalBluetoothDeviceClient()-> + GetProperties(object_path_)->trusted.Set( + true, + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnSetTrusted, + weak_ptr_factory_.GetWeakPtr())); + + UnregisterAgent(); + + // Now we can connect to the device! + ConnectInternal(callback, error_callback); +} + +void BluetoothDeviceExperimentalChromeOS::OnPairError( + const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + --num_connecting_calls_; + DCHECK(num_connecting_calls_ >= 0); + LOG(WARNING) << object_path_.value() << ": Failed to pair device: " + << error_name << ": " << error_message; + VLOG(1) << object_path_.value() << ": " << num_connecting_calls_ + << " still in progress"; + + UnregisterAgent(); + + // Determine the error code from error_name. + ConnectErrorCode error_code = ERROR_UNKNOWN; + if (error_name == bluetooth_adapter::kErrorConnectionAttemptFailed) { + error_code = ERROR_FAILED; + } else if (error_name == bluetooth_adapter::kErrorAuthenticationFailed) { + error_code = ERROR_AUTH_FAILED; + } else if (error_name == bluetooth_adapter::kErrorAuthenticationCanceled) { + error_code = ERROR_AUTH_CANCELED; + } else if (error_name == bluetooth_adapter::kErrorAuthenticationRejected) { + error_code = ERROR_AUTH_REJECTED; + } else if (error_name == bluetooth_adapter::kErrorAuthenticationTimeout) { + error_code = ERROR_AUTH_TIMEOUT; + } + + error_callback.Run(error_code); +} + +void BluetoothDeviceExperimentalChromeOS::OnCancelPairingError( + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to cancel pairing: " + << error_name << ": " << error_message; +} + +void BluetoothDeviceExperimentalChromeOS::OnSetTrusted(bool success) { + LOG_IF(WARNING, !success) << object_path_.value() + << ": Failed to set device as trusted"; +} + +void BluetoothDeviceExperimentalChromeOS::UnregisterAgent() { + DCHECK(agent_.get()); + DCHECK(pairing_delegate_); + + DCHECK(pincode_callback_.is_null()); + DCHECK(passkey_callback_.is_null()); + DCHECK(confirmation_callback_.is_null()); + + pairing_delegate_->DismissDisplayOrConfirm(); + pairing_delegate_ = NULL; + + agent_.reset(); + + // Clean up after ourselves. + VLOG(1) << object_path_.value() << ": Unregistering pairing agent"; + DBusThreadManager::Get()->GetExperimentalBluetoothAgentManagerClient()-> + UnregisterAgent( + dbus::ObjectPath(kAgentPath), + base::Bind(&base::DoNothing), + base::Bind( + &BluetoothDeviceExperimentalChromeOS::OnUnregisterAgentError, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothDeviceExperimentalChromeOS::OnUnregisterAgentError( + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to unregister agent: " + << error_name << ": " << error_message; +} + +void BluetoothDeviceExperimentalChromeOS::OnDisconnect( + const base::Closure& callback) { + VLOG(1) << object_path_.value() << ": Disconnected"; + callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::OnDisconnectError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to disconnect device: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +void BluetoothDeviceExperimentalChromeOS::OnForgetError( + const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(WARNING) << object_path_.value() << ": Failed to remove device: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +bool BluetoothDeviceExperimentalChromeOS::RunPairingCallbacks(Status status) { + if (!agent_.get()) + return false; + + bool callback_run = false; + if (!pincode_callback_.is_null()) { + pincode_callback_.Run(status, ""); + pincode_callback_.Reset(); + callback_run = true; + } + + if (!passkey_callback_.is_null()) { + passkey_callback_.Run(status, 0); + passkey_callback_.Reset(); + callback_run = true; + } + + if (!confirmation_callback_.is_null()) { + confirmation_callback_.Run(status); + confirmation_callback_.Reset(); + callback_run = true; + } + + return callback_run; +} + +} // namespace chromeos diff --git a/device/bluetooth/bluetooth_device_experimental_chromeos.h b/device/bluetooth/bluetooth_device_experimental_chromeos.h new file mode 100644 index 0000000..aa24657 --- /dev/null +++ b/device/bluetooth/bluetooth_device_experimental_chromeos.h @@ -0,0 +1,200 @@ +// Copyright (c) 2013 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. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_EXPERIMENTAL_CHROMEOS_H +#define DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_EXPERIMENTAL_CHROMEOS_H + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "chromeos/dbus/experimental_bluetooth_agent_service_provider.h" +#include "chromeos/dbus/experimental_bluetooth_device_client.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_device.h" + +namespace chromeos { + +class BluetoothAdapterExperimentalChromeOS; + +// The BluetoothDeviceExperimentalChromeOS class is an alternate implementation +// of BluetoothDevice for the Chrome OS platform using the Bluetooth Smart +// capable backend. It will become the sole implementation for Chrome OS, and +// be renamed to BluetoothDeviceChromeOS, once the backend is switched. +class BluetoothDeviceExperimentalChromeOS + : public device::BluetoothDevice, + private chromeos::ExperimentalBluetoothAgentServiceProvider::Delegate { + public: + // BluetoothDevice override + virtual std::string GetAddress() const OVERRIDE; + virtual bool IsPaired() const OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectable() const OVERRIDE; + virtual bool IsConnecting() const OVERRIDE; + virtual ServiceList GetServices() const OVERRIDE; + virtual void GetServiceRecords( + const ServiceRecordsCallback& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void ProvidesServiceWithName( + const std::string& name, + const ProvidesServiceCallback& callback) OVERRIDE; + virtual bool ExpectingPinCode() const OVERRIDE; + virtual bool ExpectingPasskey() const OVERRIDE; + virtual bool ExpectingConfirmation() const OVERRIDE; + virtual void Connect( + device::BluetoothDevice::PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) OVERRIDE; + virtual void SetPinCode(const std::string& pincode) OVERRIDE; + virtual void SetPasskey(uint32 passkey) OVERRIDE; + virtual void ConfirmPairing() OVERRIDE; + virtual void RejectPairing() OVERRIDE; + virtual void CancelPairing() OVERRIDE; + virtual void Disconnect( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void Forget(const ErrorCallback& error_callback) OVERRIDE; + virtual void ConnectToService( + const std::string& service_uuid, + const SocketCallback& callback) OVERRIDE; + virtual void SetOutOfBandPairingData( + const device::BluetoothOutOfBandPairingData& data, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void ClearOutOfBandPairingData( + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + protected: + // BluetoothDevice override + virtual uint32 GetBluetoothClass() const OVERRIDE; + virtual std::string GetDeviceName() const OVERRIDE; + + private: + friend class BluetoothAdapterExperimentalChromeOS; + + BluetoothDeviceExperimentalChromeOS( + BluetoothAdapterExperimentalChromeOS* adapter, + const dbus::ObjectPath& object_path); + virtual ~BluetoothDeviceExperimentalChromeOS(); + + // ExperimentalBluetoothAgentServiceProvider::Delegate override. + virtual void Release() OVERRIDE; + virtual void RequestPinCode(const dbus::ObjectPath& device_path, + const PinCodeCallback& callback) OVERRIDE; + virtual void DisplayPinCode(const dbus::ObjectPath& device_path, + const std::string& pincode) OVERRIDE; + virtual void RequestPasskey(const dbus::ObjectPath& device_path, + const PasskeyCallback& callback) OVERRIDE; + virtual void DisplayPasskey(const dbus::ObjectPath& device_path, + uint32 passkey, int16 entered) OVERRIDE; + virtual void RequestConfirmation(const dbus::ObjectPath& device_path, + uint32 passkey, + const ConfirmationCallback& callback) + OVERRIDE; + virtual void RequestAuthorization(const dbus::ObjectPath& device_path, + const ConfirmationCallback& callback) + OVERRIDE; + virtual void AuthorizeService(const dbus::ObjectPath& device_path, + const std::string& uuid, + const ConfirmationCallback& callback) OVERRIDE; + virtual void Cancel() OVERRIDE; + + // Internal method to initiate a connection to this device, and methods called + // by dbus:: on completion of the D-Bus method call. + void ConnectInternal(const base::Closure& callback, + const ConnectErrorCallback& error_callback); + void OnConnect(const base::Closure& callback); + void OnConnectError(const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to register the + // pairing agent. + void OnRegisterAgent(const base::Closure& callback, + const ConnectErrorCallback& error_callback); + void OnRegisterAgentError(const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to pair the device. + void OnPair(const base::Closure& callback, + const ConnectErrorCallback& error_callback); + void OnPairError(const ConnectErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on failure of the D-Bus method call to cancel pairing, + // there is no matching completion call since we don't do anything special + // in that case. + void OnCancelPairingError(const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the trusted property change. + void OnSetTrusted(bool success); + + // Internal method to unregister the pairing agent and method called by dbus:: + // on failure of the D-Bus method call. No completion call as success is + // ignored. + void UnregisterAgent(); + void OnUnregisterAgentError(const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on completion of the D-Bus method call to disconnect the + // device. + void OnDisconnect(const base::Closure& callback); + void OnDisconnectError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Called by dbus:: on failure of the D-Bus method call to unpair the device; + // there is no matching completion call since this object is deleted in the + // process of unpairing. + void OnForgetError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Run any outstanding pairing callbacks passing |status| as the result of + // pairing. Returns true if any callbacks were run, false if not. + bool RunPairingCallbacks(Status status); + + // Return the object path of the device; used by + // BluetoothAdapterExperimentalChromeOS + const dbus::ObjectPath& object_path() const { return object_path_; } + + // The adapter that owns this device instance. + BluetoothAdapterExperimentalChromeOS* adapter_; + + // The dbus object path of the device object. + dbus::ObjectPath object_path_; + + // Number of ongoing calls to Connect(). + int num_connecting_calls_; + + // During pairing this is set to an object that we don't own, but on which + // we can make method calls to request, display or confirm PIN Codes and + // Passkeys. Generally it is the object that owns this one. + PairingDelegate* pairing_delegate_; + + // During pairing this is set to an instance of a D-Bus agent object + // intialized with our own class as its delegate. + scoped_ptr<ExperimentalBluetoothAgentServiceProvider> agent_; + + // During pairing these callbacks are set to those provided by method calls + // made on us by |agent_| and are called by our own method calls such as + // SetPinCode() and SetPasskey(). + PinCodeCallback pincode_callback_; + PasskeyCallback passkey_callback_; + ConfirmationCallback confirmation_callback_; + + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<BluetoothDeviceExperimentalChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceExperimentalChromeOS); +}; + +} // namespace chromeos + +#endif /* DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_EXPERIMENTAL_CHROMEOS_H */ diff --git a/device/bluetooth/bluetooth_experimental_chromeos_unittest.cc b/device/bluetooth/bluetooth_experimental_chromeos_unittest.cc new file mode 100644 index 0000000..7bd7c25 --- /dev/null +++ b/device/bluetooth/bluetooth_experimental_chromeos_unittest.cc @@ -0,0 +1,1848 @@ +// Copyright (c) 2013 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 "base/command_line.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "chromeos/chromeos_switches.h" +#include "chromeos/dbus/fake_bluetooth_adapter_client.h" +#include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/mock_dbus_thread_manager_without_gmock.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_adapter_experimental_chromeos.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_device_experimental_chromeos.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothAdapterFactory; +using device::BluetoothDevice; + +namespace chromeos { + +class TestObserver : public BluetoothAdapter::Observer { + public: + TestObserver(scoped_refptr<BluetoothAdapter> adapter) + : present_changed_count_(0), + powered_changed_count_(0), + discovering_changed_count_(0), + last_present_(false), + last_powered_(false), + last_discovering_(false), + device_added_count_(0), + device_changed_count_(0), + device_removed_count_(0), + last_device_(NULL), + adapter_(adapter) { + } + virtual ~TestObserver() {} + + virtual void AdapterPresentChanged(BluetoothAdapter* adapter, + bool present) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++present_changed_count_; + last_present_ = present; + } + + virtual void AdapterPoweredChanged(BluetoothAdapter* adapter, + bool powered) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++powered_changed_count_; + last_powered_ = powered; + } + + virtual void AdapterDiscoveringChanged(BluetoothAdapter* adapter, + bool discovering) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++discovering_changed_count_; + last_discovering_ = discovering; + } + + virtual void DeviceAdded(BluetoothAdapter* adapter, + BluetoothDevice* device) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++device_added_count_; + last_device_ = device; + last_device_address_ = device->GetAddress(); + + QuitMessageLoop(); + } + + virtual void DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++device_changed_count_; + last_device_ = device; + last_device_address_ = device->GetAddress(); + + QuitMessageLoop(); + } + + virtual void DeviceRemoved(BluetoothAdapter* adapter, + BluetoothDevice* device) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + + ++device_removed_count_; + // Can't save device, it may be freed + last_device_address_ = device->GetAddress(); + + QuitMessageLoop(); + } + + int present_changed_count_; + int powered_changed_count_; + int discovering_changed_count_; + bool last_present_; + bool last_powered_; + bool last_discovering_; + int device_added_count_; + int device_changed_count_; + int device_removed_count_; + BluetoothDevice* last_device_; + std::string last_device_address_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (MessageLoop::current() && MessageLoop::current()->is_running()) + MessageLoop::current()->Quit(); + } + + scoped_refptr<BluetoothAdapter> adapter_; +}; + +class TestPairingDelegate : public BluetoothDevice::PairingDelegate { + public: + TestPairingDelegate() + : call_count_(0), + request_pincode_count_(0), + request_passkey_count_(0), + display_pincode_count_(0), + display_passkey_count_(0), + confirm_passkey_count_(0), + dismiss_count_(0) {} + virtual ~TestPairingDelegate() {} + + void RequestPinCode(BluetoothDevice* device) OVERRIDE { + ++call_count_; + ++request_pincode_count_; + QuitMessageLoop(); + } + + void RequestPasskey(BluetoothDevice* device) OVERRIDE { + ++call_count_; + ++request_passkey_count_; + QuitMessageLoop(); + } + + void DisplayPinCode(BluetoothDevice* device, + const std::string& pincode) OVERRIDE { + ++call_count_; + ++display_pincode_count_; + last_pincode_ = pincode; + QuitMessageLoop(); + } + + void DisplayPasskey(BluetoothDevice* device, + uint32 passkey) OVERRIDE { + ++call_count_; + ++display_passkey_count_; + last_passkey_ = passkey; + QuitMessageLoop(); + } + + void ConfirmPasskey(BluetoothDevice* device, + uint32 passkey) OVERRIDE { + ++call_count_; + ++confirm_passkey_count_; + last_passkey_ = passkey; + QuitMessageLoop(); + } + + void DismissDisplayOrConfirm() OVERRIDE { + ++call_count_; + ++dismiss_count_; + QuitMessageLoop(); + } + + int call_count_; + int request_pincode_count_; + int request_passkey_count_; + int display_pincode_count_; + int display_passkey_count_; + int confirm_passkey_count_; + int dismiss_count_; + uint32 last_passkey_; + std::string last_pincode_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (MessageLoop::current() && MessageLoop::current()->is_running()) + MessageLoop::current()->Quit(); + } +}; + +class BluetoothExperimentalChromeOSTest : public testing::Test { + public: + virtual void SetUp() { + if (!CommandLine::ForCurrentProcess()->HasSwitch( + chromeos::switches::kEnableExperimentalBluetooth)) + CommandLine::ForCurrentProcess()->AppendSwitch( + chromeos::switches::kEnableExperimentalBluetooth); + + mock_dbus_thread_manager_ = + new MockDBusThreadManagerWithoutGMock(); + DBusThreadManager::InitializeForTesting(mock_dbus_thread_manager_); + + fake_bluetooth_adapter_client_ = + mock_dbus_thread_manager_->fake_bluetooth_adapter_client(); + fake_bluetooth_device_client_ = + mock_dbus_thread_manager_->fake_bluetooth_device_client(); + + callback_count_ = 0; + error_callback_count_ = 0; + last_connect_error_ = BluetoothDevice::ERROR_UNKNOWN; + } + + virtual void TearDown() { + adapter_ = NULL; + DBusThreadManager::Shutdown(); + } + + // Generic callbacks + void Callback() { + ++callback_count_; + } + + void ErrorCallback() { + ++error_callback_count_; + } + + void ConnectErrorCallback(enum BluetoothDevice::ConnectErrorCode error) { + ++error_callback_count_; + last_connect_error_ = error; + } + + // Call to fill the adapter_ member with a BluetoothAdapter instance. + void GetAdapter() { + adapter_ = new BluetoothAdapterExperimentalChromeOS(); + ASSERT_TRUE(adapter_ != NULL); + ASSERT_TRUE(adapter_->IsInitialized()); + } + + // Run a discovery phase until the named device is detected, or if the named + // device is not created, the discovery process ends without finding it. + // + // The correct behavior of discovery is tested by the "Discovery" test case + // without using this function. + void DiscoverDevice(const std::string& address) { + ASSERT_TRUE(adapter_ != NULL); + + if (MessageLoop::current() == NULL) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + DiscoverDevices(); + return; + } + + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + ASSERT_EQ(2, callback_count_); + ASSERT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + while (!observer.device_removed_count_ && + observer.last_device_address_ != address) + MessageLoop::current()->Run(); + + adapter_->StopDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + ASSERT_EQ(1, callback_count_); + ASSERT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_FALSE(adapter_->IsDiscovering()); + + adapter_->RemoveObserver(&observer); + } + + // Run a discovery phase so we have devices that can be paired with. + void DiscoverDevices() { + // Pass an invalid address for the device so that the discovery process + // completes with all devices. + DiscoverDevice("does not exist"); + } + + protected: + FakeBluetoothAdapterClient* fake_bluetooth_adapter_client_; + FakeBluetoothDeviceClient* fake_bluetooth_device_client_; + MockDBusThreadManagerWithoutGMock* mock_dbus_thread_manager_; + scoped_refptr<BluetoothAdapter> adapter_; + + int callback_count_; + int error_callback_count_; + enum BluetoothDevice::ConnectErrorCode last_connect_error_; +}; + +TEST_F(BluetoothExperimentalChromeOSTest, AlreadyPresent) { + GetAdapter(); + + // This verifies that the class gets the list of adapters when created; + // and initializes with an existing adapter if there is one. + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_EQ(FakeBluetoothAdapterClient::kAdapterAddress, + adapter_->GetAddress()); + EXPECT_EQ(FakeBluetoothAdapterClient::kAdapterName, adapter_->GetName()); + EXPECT_FALSE(adapter_->IsDiscovering()); + + // There should be a device + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + EXPECT_EQ(1U, devices.size()); + EXPECT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, BecomePresent) { + fake_bluetooth_adapter_client_->SetVisible(false); + GetAdapter(); + ASSERT_FALSE(adapter_->IsPresent()); + + // Install an observer; expect the AdapterPresentChanged to be called + // with true, and IsPresent() to return true. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + fake_bluetooth_adapter_client_->SetVisible(true); + + EXPECT_EQ(1, observer.present_changed_count_); + EXPECT_TRUE(observer.last_present_); + + EXPECT_TRUE(adapter_->IsPresent()); + + // We should have had a device announced. + EXPECT_EQ(1, observer.device_added_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + observer.last_device_address_); + + // Other callbacks shouldn't be called if the values are false. + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, BecomeNotPresent) { + GetAdapter(); + ASSERT_TRUE(adapter_->IsPresent()); + + // Install an observer; expect the AdapterPresentChanged to be called + // with false, and IsPresent() to return false. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + fake_bluetooth_adapter_client_->SetVisible(false); + + EXPECT_EQ(1, observer.present_changed_count_); + EXPECT_FALSE(observer.last_present_); + + EXPECT_FALSE(adapter_->IsPresent()); + + // We should have had a device removed. + EXPECT_EQ(1, observer.device_removed_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + observer.last_device_address_); + + // Other callbacks shouldn't be called since the values are false. + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, SecondAdapter) { + GetAdapter(); + ASSERT_TRUE(adapter_->IsPresent()); + + // Install an observer, then add a second adapter. Nothing should change, + // we ignore the second adapter. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + fake_bluetooth_adapter_client_->SetSecondVisible(true); + + EXPECT_EQ(0, observer.present_changed_count_); + + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_EQ(FakeBluetoothAdapterClient::kAdapterAddress, + adapter_->GetAddress()); + + // Try removing the first adapter, we should now act as if the adapter + // is no longer present rather than fall back to the second. + fake_bluetooth_adapter_client_->SetVisible(false); + + EXPECT_EQ(1, observer.present_changed_count_); + EXPECT_FALSE(observer.last_present_); + + EXPECT_FALSE(adapter_->IsPresent()); + + // We should have had a device removed. + EXPECT_EQ(1, observer.device_removed_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + observer.last_device_address_); + + // Other callbacks shouldn't be called since the values are false. + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.discovering_changed_count_); + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsDiscovering()); + + observer.device_removed_count_ = 0; + + // Removing the second adapter shouldn't set anything either. + fake_bluetooth_adapter_client_->SetSecondVisible(false); + + EXPECT_EQ(0, observer.device_removed_count_); + EXPECT_EQ(0, observer.powered_changed_count_); + EXPECT_EQ(0, observer.discovering_changed_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, BecomePowered) { + GetAdapter(); + ASSERT_FALSE(adapter_->IsPowered()); + + // Install an observer; expect the AdapterPoweredChanged to be called + // with true, and IsPowered() to return true. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.powered_changed_count_); + EXPECT_TRUE(observer.last_powered_); + + EXPECT_TRUE(adapter_->IsPowered()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, BecomeNotPowered) { + GetAdapter(); + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(adapter_->IsPowered()); + + // Install an observer; expect the AdapterPoweredChanged to be called + // with false, and IsPowered() to return false. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + false, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.powered_changed_count_); + EXPECT_FALSE(observer.last_powered_); + + EXPECT_FALSE(adapter_->IsPowered()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, StopDiscovery) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + // Install an observer; aside from the callback, expect the + // AdapterDiscoveringChanged method to be called and no longer to be + // discovering, + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->StopDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, StopDiscoveryAfterTwoStarts) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + // Install an observer and start discovering again; only the callback + // should be called since we were already discovering to begin with. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + EXPECT_EQ(0, observer.discovering_changed_count_); + + // Stop discovering; only the callback should be called since we're still + // discovering. The adapter should be still discovering. + adapter_->StopDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + EXPECT_EQ(0, observer.discovering_changed_count_); + + EXPECT_TRUE(adapter_->IsDiscovering()); + + // Stop discovering one more time; aside from the callback, expect the + // AdapterDiscoveringChanged method to be called and no longer to be + // discovering, + adapter_->StopDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, Discovery) { + // Test a simulated discovery session. + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + GetAdapter(); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + // First device to appear should be an Apple Mouse. + message_loop.Run(); + + EXPECT_EQ(1, observer.device_added_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kAppleMouseAddress, + observer.last_device_address_); + + // Next we should get another two devices... + message_loop.Run(); + EXPECT_EQ(3, observer.device_added_count_); + + // Okay, let's run forward until a device is actually removed... + while (!observer.device_removed_count_) + message_loop.Run(); + + EXPECT_EQ(1, observer.device_removed_count_); + EXPECT_EQ(FakeBluetoothDeviceClient::kVanishingDeviceAddress, + observer.last_device_address_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PoweredAndDiscovering) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + + GetAdapter(); + adapter_->SetPowered( + true, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartDiscovering( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, callback_count_); + EXPECT_EQ(0, error_callback_count_); + callback_count_ = 0; + + // Stop the timers that the simulation uses + fake_bluetooth_device_client_->EndDiscoverySimulation( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath)); + + ASSERT_TRUE(adapter_->IsPowered()); + ASSERT_TRUE(adapter_->IsDiscovering()); + + fake_bluetooth_adapter_client_->SetVisible(false); + ASSERT_FALSE(adapter_->IsPresent()); + + // Install an observer; expect the AdapterPresentChanged, + // AdapterPoweredChanged and AdapterDiscoveringChanged methods to be called + // with true, and IsPresent(), IsPowered() and IsDiscovering() to all + // return true. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + fake_bluetooth_adapter_client_->SetVisible(true); + + EXPECT_EQ(1, observer.present_changed_count_); + EXPECT_TRUE(observer.last_present_); + EXPECT_TRUE(adapter_->IsPresent()); + + EXPECT_EQ(1, observer.powered_changed_count_); + EXPECT_TRUE(observer.last_powered_); + EXPECT_TRUE(adapter_->IsPowered()); + + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_TRUE(observer.last_discovering_); + EXPECT_TRUE(adapter_->IsDiscovering()); + + observer.present_changed_count_ = 0; + observer.powered_changed_count_ = 0; + observer.discovering_changed_count_ = 0; + + // Now mark the adapter not present again. Expect the methods to be called + // again, to reset the properties back to false + fake_bluetooth_adapter_client_->SetVisible(false); + + EXPECT_EQ(1, observer.present_changed_count_); + EXPECT_FALSE(observer.last_present_); + EXPECT_FALSE(adapter_->IsPresent()); + + EXPECT_EQ(1, observer.powered_changed_count_); + EXPECT_FALSE(observer.last_powered_); + EXPECT_FALSE(adapter_->IsPowered()); + + EXPECT_EQ(1, observer.discovering_changed_count_); + EXPECT_FALSE(observer.last_discovering_); + EXPECT_FALSE(adapter_->IsDiscovering()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DeviceProperties) { + GetAdapter(); + + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + ASSERT_EQ(1U, devices.size()); + ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); + + // Verify the other device properties. + EXPECT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), + devices[0]->GetName()); + EXPECT_EQ(BluetoothDevice::DEVICE_COMPUTER, devices[0]->GetDeviceType()); + EXPECT_TRUE(devices[0]->IsPaired()); + EXPECT_FALSE(devices[0]->IsConnected()); + EXPECT_FALSE(devices[0]->IsConnectable()); + EXPECT_FALSE(devices[0]->IsConnecting()); + + BluetoothDevice::ServiceList uuids = devices[0]->GetServices(); + ASSERT_EQ(2U, uuids.size()); + EXPECT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DeviceClassChanged) { + // Simulate a change of class of a device, as sometimes occurs + // during discovery. + GetAdapter(); + + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + ASSERT_EQ(1U, devices.size()); + ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); + ASSERT_EQ(BluetoothDevice::DEVICE_COMPUTER, devices[0]->GetDeviceType()); + + // Install an observer; expect the DeviceChanged method to be called when + // we change the class of the device. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kPairedDevicePath)); + + properties->bluetooth_class.ReplaceValue(0x002580); + properties->NotifyPropertyChanged(properties->bluetooth_class.name()); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(devices[0], observer.last_device_); + + EXPECT_EQ(BluetoothDevice::DEVICE_MOUSE, devices[0]->GetDeviceType()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DeviceNameChanged) { + // Simulate a change of name of a device. + GetAdapter(); + + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + ASSERT_EQ(1U, devices.size()); + ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); + ASSERT_EQ(UTF8ToUTF16(FakeBluetoothDeviceClient::kPairedDeviceName), + devices[0]->GetName()); + + // Install an observer; expect the DeviceChanged method to be called when + // we change the alias of the device. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kPairedDevicePath)); + + static const std::string new_name("New Device Name"); + properties->alias.ReplaceValue(new_name); + properties->NotifyPropertyChanged(properties->alias.name()); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(devices[0], observer.last_device_); + + EXPECT_EQ(UTF8ToUTF16(new_name), devices[0]->GetName()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DeviceUuidsChanged) { + // Simulate a change of advertised services of a device. + GetAdapter(); + + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + ASSERT_EQ(1U, devices.size()); + ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); + + BluetoothDevice::ServiceList uuids = devices[0]->GetServices(); + ASSERT_EQ(2U, uuids.size()); + ASSERT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); + ASSERT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); + + // Install an observer; expect the DeviceChanged method to be called when + // we change the class of the device. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kPairedDevicePath)); + + uuids.push_back("0000110c-0000-1000-8000-00805f9b34fb"); + uuids.push_back("0000110e-0000-1000-8000-00805f9b34fb"); + uuids.push_back("0000110a-0000-1000-8000-00805f9b34fb"); + + properties->uuids.ReplaceValue(uuids); + properties->NotifyPropertyChanged(properties->uuids.name()); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(devices[0], observer.last_device_); + + // Fetching the value should give the new one. + uuids = devices[0]->GetServices(); + ASSERT_EQ(5U, uuids.size()); + EXPECT_EQ(uuids[0], "00001800-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[1], "00001801-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[2], "0000110c-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[3], "0000110e-0000-1000-8000-00805f9b34fb"); + EXPECT_EQ(uuids[4], "0000110a-0000-1000-8000-00805f9b34fb"); +} + +TEST_F(BluetoothExperimentalChromeOSTest, ForgetDevice) { + GetAdapter(); + + BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); + ASSERT_EQ(1U, devices.size()); + ASSERT_EQ(FakeBluetoothDeviceClient::kPairedDeviceAddress, + devices[0]->GetAddress()); + + std::string address = devices[0]->GetAddress(); + + // Install an observer; expect the DeviceRemoved method to be called + // with the device we remove. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + devices[0]->Forget( + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.device_removed_count_); + EXPECT_EQ(address, observer.last_device_address_); + + // GetDevices shouldn't return the device either. + devices = adapter_->GetDevices(); + ASSERT_EQ(0U, devices.size()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, ConnectPairedDevice) { + GetAdapter(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_TRUE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Connect without a pairing delegate; since the device is already Paired + // this should succeed and the device should become connected. + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, ConnectUnpairableDevice) { + GetAdapter(); + DiscoverDevices(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kMicrosoftMouseAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Connect without a pairing delegate; since the device does not require + // pairing, this should succeed and the device should become connected. + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, ConnectConnectedDevice) { + GetAdapter(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_TRUE(device->IsPaired()); + + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + ASSERT_EQ(1, callback_count_); + ASSERT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(device->IsConnected()); + + // Connect again; since the device is already Connected, this shouldn't do + // anything, not even the observer method should be called. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, ConnectDeviceFails) { + GetAdapter(); + DiscoverDevices(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kAppleMouseAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Connect without a pairing delegate; since the device requires pairing, + // this should fail with an error. + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_FAILED, last_connect_error_); + + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DisconnectDevice) { + GetAdapter(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_TRUE(device->IsPaired()); + + device->Connect( + NULL, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + ASSERT_EQ(1, callback_count_); + ASSERT_EQ(0, error_callback_count_); + callback_count_ = 0; + + ASSERT_TRUE(device->IsConnected()); + ASSERT_FALSE(device->IsConnecting()); + + // Disconnect the device, we should see the observer method fire and the + // device get dropped. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + device->Disconnect( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_FALSE(device->IsConnected()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, DisconnectUnconnectedDevice) { + GetAdapter(); + + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_TRUE(device->IsPaired()); + ASSERT_FALSE(device->IsConnected()); + + // Disconnect the device, we should see the observer method fire and the + // device get dropped. + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + device->Disconnect( + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + + EXPECT_EQ(0, observer.device_changed_count_); + + EXPECT_FALSE(device->IsConnected()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairAppleMouse) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // The Apple Mouse requires no PIN or Passkey to pair; this is equivalent + // to Simple Secure Pairing or a device with a fixed 0000 PIN. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kAppleMouseAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, pairing_delegate.call_count_); + EXPECT_TRUE(device->IsConnecting()); + + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kAppleMousePath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairAppleKeyboard) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // The Apple Keyboard requires that we display a randomly generated + // PIN on the screen. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kAppleKeyboardAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.display_pincode_count_); + EXPECT_EQ("123456", pairing_delegate.last_pincode_); + EXPECT_TRUE(device->IsConnecting()); + + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kAppleKeyboardPath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairMotorolaKeyboard) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // The Motorola Keyboard requires that we display a randomly generated + // Passkey on the screen, and notifies us as it's typed in. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kMotorolaKeyboardAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.display_passkey_count_); + EXPECT_EQ(123456U, pairing_delegate.last_passkey_); + EXPECT_TRUE(device->IsConnecting()); + + // TODO(keybuk): verify we get typing notifications + + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kMotorolaKeyboardPath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairSonyHeadphones) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // The Sony Headphones fake requires that the user enters a PIN for them. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Set the PIN. + device->SetPinCode("1234"); + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kSonyHeadphonesPath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairPhone) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // The fake phone requests that we confirm a displayed passkey. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPhoneAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.confirm_passkey_count_); + EXPECT_EQ(123456U, pairing_delegate.last_passkey_); + EXPECT_TRUE(device->IsConnecting()); + + // Confirm the passkey. + device->ConfirmPairing(); + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kPhonePath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairWeirdDevice) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Use the "weird device" fake that requires that the user enters a Passkey, + // this would be some kind of device that has a display, but doesn't use + // "just works" - maybe a car? + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kWeirdDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_passkey_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Set the Passkey. + device->SetPasskey(1234); + message_loop.Run(); + + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // One change for connected, and one for paired. + EXPECT_EQ(2, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true. + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kWeirdDevicePath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingFails) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevice(FakeBluetoothDeviceClient::kVanishingDeviceAddress); + + // The vanishing device times out during pairing + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kVanishingDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, pairing_delegate.call_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Run the loop to get the error.. + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_TIMEOUT, last_connect_error_); + + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingFailsAtConnection) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Everything seems to go according to plan with the Microsoft Mouse, it + // pairs with 0000, but then you can't make connections to it after. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kMicrosoftMouseAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, pairing_delegate.call_count_); + EXPECT_TRUE(device->IsConnecting()); + + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_FAILED, last_connect_error_); + + // Just one change for paired, the device should not be connected. + EXPECT_EQ(1, observer.device_changed_count_); + EXPECT_EQ(device, observer.last_device_); + + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + + EXPECT_TRUE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); + + // Make sure the trusted property has been set to true still (since pairing + // worked). + FakeBluetoothDeviceClient::Properties* properties = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(FakeBluetoothDeviceClient::kMicrosoftMousePath)); + EXPECT_TRUE(properties->trusted.value()); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingRejectedAtPinCode) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Reject the pairing after we receive a request for the PIN code. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Reject the pairing. + device->RejectPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_REJECTED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingCancelledAtPinCode) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Cancel the pairing after we receive a request for the PIN code. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kSonyHeadphonesAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Cancel the pairing. + device->CancelPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingRejectedAtPasskey) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Reject the pairing after we receive a request for the passkey. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kWeirdDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_passkey_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Reject the pairing. + device->RejectPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_REJECTED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingCancelledAtPasskey) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Cancel the pairing after we receive a request for the passkey. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kWeirdDeviceAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_passkey_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Cancel the pairing. + device->CancelPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingRejectedAtConfirmation) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Reject the pairing after we receive a request for passkey confirmation. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPhoneAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.confirm_passkey_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Reject the pairing. + device->RejectPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_REJECTED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingCancelledAtConfirmation) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Cancel the pairing after we receive a request for the passkey. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kPhoneAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.confirm_passkey_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Cancel the pairing. + device->CancelPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(2, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +TEST_F(BluetoothExperimentalChromeOSTest, PairingCancelledInFlight) { + base::MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + fake_bluetooth_device_client_->SetSimulationIntervalMs(10); + + GetAdapter(); + DiscoverDevices(); + + // Cancel the pairing while we're waiting for the remote host. + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kAppleMouseAddress); + ASSERT_TRUE(device != NULL); + ASSERT_FALSE(device->IsPaired()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + TestPairingDelegate pairing_delegate; + device->Connect( + &pairing_delegate, + base::Bind(&BluetoothExperimentalChromeOSTest::Callback, + base::Unretained(this)), + base::Bind(&BluetoothExperimentalChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, pairing_delegate.call_count_); + EXPECT_TRUE(device->IsConnecting()); + + // Cancel the pairing. + device->CancelPairing(); + message_loop.Run(); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, last_connect_error_); + + // Should be no changes. + EXPECT_EQ(0, observer.device_changed_count_); + EXPECT_FALSE(device->IsConnected()); + EXPECT_FALSE(device->IsConnecting()); + EXPECT_FALSE(device->IsPaired()); + + // Pairing dialog should be dismissed + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.dismiss_count_); +} + +} // namespace chromeos |