// Copyright 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_adapter_chromeos.h" #include <string> #include "base/bind.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" #include "base/sys_info.h" #include "base/thread_task_runner_handle.h" #include "chromeos/dbus/bluetooth_adapter_client.h" #include "chromeos/dbus/bluetooth_agent_manager_client.h" #include "chromeos/dbus/bluetooth_agent_service_provider.h" #include "chromeos/dbus/bluetooth_device_client.h" #include "chromeos/dbus/bluetooth_input_client.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device_chromeos.h" #include "device/bluetooth/bluetooth_pairing_chromeos.h" #include "device/bluetooth/bluetooth_socket_chromeos.h" #include "device/bluetooth/bluetooth_socket_thread.h" #include "device/bluetooth/bluetooth_uuid.h" #include "third_party/cros_system_api/dbus/service_constants.h" using device::BluetoothAdapter; using device::BluetoothDevice; using device::BluetoothSocket; using device::BluetoothUUID; namespace { // The agent path is relatively meaningless since BlueZ only permits one to // exist per D-Bus connection, it just has to be unique within Chromium. const char kAgentPath[] = "/org/chromium/bluetooth_agent"; void OnUnregisterAgentError(const std::string& error_name, const std::string& error_message) { // It's okay if the agent didn't exist, it means we never saw an adapter. if (error_name == bluetooth_agent_manager::kErrorDoesNotExist) return; LOG(WARNING) << "Failed to unregister pairing agent: " << error_name << ": " << error_message; } } // namespace namespace device { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter( const InitCallback& init_callback) { return chromeos::BluetoothAdapterChromeOS::CreateAdapter(); } } namespace chromeos { // static base::WeakPtr<BluetoothAdapter> BluetoothAdapterChromeOS::CreateAdapter() { BluetoothAdapterChromeOS* adapter = new BluetoothAdapterChromeOS(); return adapter->weak_ptr_factory_.GetWeakPtr(); } BluetoothAdapterChromeOS::BluetoothAdapterChromeOS() : num_discovery_sessions_(0), discovery_request_pending_(false), weak_ptr_factory_(this) { ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); socket_thread_ = device::BluetoothSocketThread::Get(); DBusThreadManager::Get()->GetBluetoothAdapterClient()->AddObserver(this); DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); DBusThreadManager::Get()->GetBluetoothInputClient()->AddObserver(this); // Register the pairing agent. dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); agent_.reset(BluetoothAgentServiceProvider::Create( system_bus, dbus::ObjectPath(kAgentPath), this)); DCHECK(agent_.get()); std::vector<dbus::ObjectPath> object_paths = DBusThreadManager::Get()->GetBluetoothAdapterClient()->GetAdapters(); if (!object_paths.empty()) { VLOG(1) << object_paths.size() << " Bluetooth adapter(s) available."; SetAdapter(object_paths[0]); } } BluetoothAdapterChromeOS::~BluetoothAdapterChromeOS() { DBusThreadManager::Get()->GetBluetoothAdapterClient()->RemoveObserver(this); DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); DBusThreadManager::Get()->GetBluetoothInputClient()->RemoveObserver(this); VLOG(1) << "Unregistering pairing agent"; DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> UnregisterAgent( dbus::ObjectPath(kAgentPath), base::Bind(&base::DoNothing), base::Bind(&OnUnregisterAgentError)); } void BluetoothAdapterChromeOS::AddObserver( BluetoothAdapter::Observer* observer) { DCHECK(observer); observers_.AddObserver(observer); } void BluetoothAdapterChromeOS::RemoveObserver( BluetoothAdapter::Observer* observer) { DCHECK(observer); observers_.RemoveObserver(observer); } std::string BluetoothAdapterChromeOS::GetAddress() const { if (!IsPresent()) return std::string(); BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); DCHECK(properties); return BluetoothDevice::CanonicalizeAddress(properties->address.value()); } std::string BluetoothAdapterChromeOS::GetName() const { if (!IsPresent()) return std::string(); BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); DCHECK(properties); return properties->alias.value(); } void BluetoothAdapterChromeOS::SetName(const std::string& name, const base::Closure& callback, const ErrorCallback& error_callback) { if (!IsPresent()) error_callback.Run(); DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_)->alias.Set( name, base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } bool BluetoothAdapterChromeOS::IsInitialized() const { return true; } bool BluetoothAdapterChromeOS::IsPresent() const { return !object_path_.value().empty(); } bool BluetoothAdapterChromeOS::IsPowered() const { if (!IsPresent()) return false; BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); return properties->powered.value(); } void BluetoothAdapterChromeOS::SetPowered( bool powered, const base::Closure& callback, const ErrorCallback& error_callback) { if (!IsPresent()) error_callback.Run(); DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_)->powered.Set( powered, base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } bool BluetoothAdapterChromeOS::IsDiscoverable() const { if (!IsPresent()) return false; BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); return properties->discoverable.value(); } void BluetoothAdapterChromeOS::SetDiscoverable( bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) { if (!IsPresent()) error_callback.Run(); DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_)->discoverable.Set( discoverable, base::Bind(&BluetoothAdapterChromeOS::OnSetDiscoverable, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } bool BluetoothAdapterChromeOS::IsDiscovering() const { if (!IsPresent()) return false; BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); return properties->discovering.value(); } void BluetoothAdapterChromeOS::CreateRfcommService( const BluetoothUUID& uuid, int channel, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { VLOG(1) << object_path_.value() << ": Creating RFCOMM service: " << uuid.canonical_value(); scoped_refptr<BluetoothSocketChromeOS> socket = BluetoothSocketChromeOS::CreateBluetoothSocket( ui_task_runner_, socket_thread_, NULL, net::NetLog::Source()); socket->Listen(this, BluetoothSocketChromeOS::kRfcomm, uuid, channel, base::Bind(callback, socket), error_callback); } void BluetoothAdapterChromeOS::CreateL2capService( const BluetoothUUID& uuid, int psm, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { VLOG(1) << object_path_.value() << ": Creating L2CAP service: " << uuid.canonical_value(); scoped_refptr<BluetoothSocketChromeOS> socket = BluetoothSocketChromeOS::CreateBluetoothSocket( ui_task_runner_, socket_thread_, NULL, net::NetLog::Source()); socket->Listen(this, BluetoothSocketChromeOS::kL2cap, uuid, psm, base::Bind(callback, socket), error_callback); } void BluetoothAdapterChromeOS::RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) { // Before removing a pairing delegate make sure that there aren't any devices // currently using it; if there are, clear the pairing context which will // make any responses no-ops. for (DevicesMap::iterator iter = devices_.begin(); iter != devices_.end(); ++iter) { BluetoothDeviceChromeOS* device_chromeos = static_cast<BluetoothDeviceChromeOS*>(iter->second); BluetoothPairingChromeOS* pairing = device_chromeos->GetPairing(); if (pairing && pairing->GetPairingDelegate() == pairing_delegate) device_chromeos->EndPairing(); } } void BluetoothAdapterChromeOS::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 BluetoothAdapterChromeOS::AdapterRemoved( const dbus::ObjectPath& object_path) { if (object_path == object_path_) RemoveAdapter(); } void BluetoothAdapterChromeOS::AdapterPropertyChanged( const dbus::ObjectPath& object_path, const std::string& property_name) { if (object_path != object_path_) return; BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); if (property_name == properties->powered.name()) PoweredChanged(properties->powered.value()); else if (property_name == properties->discoverable.name()) DiscoverableChanged(properties->discoverable.value()); else if (property_name == properties->discovering.name()) DiscoveringChanged(properties->discovering.value()); } void BluetoothAdapterChromeOS::DeviceAdded( const dbus::ObjectPath& object_path) { BluetoothDeviceClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothDeviceClient()-> GetProperties(object_path); if (properties->adapter.value() != object_path_) return; BluetoothDeviceChromeOS* device_chromeos = new BluetoothDeviceChromeOS(this, object_path, ui_task_runner_, socket_thread_); 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 BluetoothAdapterChromeOS::DeviceRemoved( const dbus::ObjectPath& object_path) { for (DevicesMap::iterator iter = devices_.begin(); iter != devices_.end(); ++iter) { BluetoothDeviceChromeOS* device_chromeos = static_cast<BluetoothDeviceChromeOS*>(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 BluetoothAdapterChromeOS::DevicePropertyChanged( const dbus::ObjectPath& object_path, const std::string& property_name) { BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path); if (!device_chromeos) return; BluetoothDeviceClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothDeviceClient()-> GetProperties(object_path); if (property_name == properties->bluetooth_class.name() || property_name == properties->address.name() || property_name == properties->alias.name() || property_name == properties->paired.name() || property_name == properties->trusted.name() || property_name == properties->connected.name() || property_name == properties->uuids.name() || property_name == properties->rssi.name() || property_name == properties->connection_rssi.name() || property_name == properties->connection_tx_power.name()) NotifyDeviceChanged(device_chromeos); // When a device becomes paired, mark it as trusted so that the user does // not need to approve every incoming connection if (property_name == properties->paired.name() && properties->paired.value() && !properties->trusted.value()) device_chromeos->SetTrusted(); // UMA connection counting if (property_name == properties->connected.name()) { // PlayStation joystick tries to reconnect after disconnection from USB. // If it is still not trusted, set it, so it becomes available on the // list of known devices. if (properties->connected.value() && device_chromeos->IsTrustable() && !properties->trusted.value()) device_chromeos->SetTrusted(); int count = 0; for (DevicesMap::iterator iter = devices_.begin(); iter != devices_.end(); ++iter) { if (iter->second->IsPaired() && iter->second->IsConnected()) ++count; } UMA_HISTOGRAM_COUNTS_100("Bluetooth.ConnectedDeviceCount", count); } } void BluetoothAdapterChromeOS::InputPropertyChanged( const dbus::ObjectPath& object_path, const std::string& property_name) { BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path); if (!device_chromeos) return; BluetoothInputClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothInputClient()-> GetProperties(object_path); // Properties structure can be removed, which triggers a change in the // BluetoothDevice::IsConnectable() property, as does a change in the // actual reconnect_mode property. if (!properties || property_name == properties->reconnect_mode.name()) NotifyDeviceChanged(device_chromeos); } void BluetoothAdapterChromeOS::Released() { DCHECK(agent_.get()); VLOG(1) << "Release"; // Called after we unregister the pairing agent, e.g. when changing I/O // capabilities. Nothing much to be done right now. } void BluetoothAdapterChromeOS::RequestPinCode( const dbus::ObjectPath& device_path, const PinCodeCallback& callback) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": RequestPinCode"; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) { callback.Run(REJECTED, ""); return; } pairing->RequestPinCode(callback); } void BluetoothAdapterChromeOS::DisplayPinCode( const dbus::ObjectPath& device_path, const std::string& pincode) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": DisplayPinCode: " << pincode; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) return; pairing->DisplayPinCode(pincode); } void BluetoothAdapterChromeOS::RequestPasskey( const dbus::ObjectPath& device_path, const PasskeyCallback& callback) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": RequestPasskey"; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) { callback.Run(REJECTED, 0); return; } pairing->RequestPasskey(callback); } void BluetoothAdapterChromeOS::DisplayPasskey( const dbus::ObjectPath& device_path, uint32 passkey, uint16 entered) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": DisplayPasskey: " << passkey << " (" << entered << " entered)"; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) return; if (entered == 0) pairing->DisplayPasskey(passkey); pairing->KeysEntered(entered); } void BluetoothAdapterChromeOS::RequestConfirmation( const dbus::ObjectPath& device_path, uint32 passkey, const ConfirmationCallback& callback) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": RequestConfirmation: " << passkey; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) { callback.Run(REJECTED); return; } pairing->RequestConfirmation(passkey, callback); } void BluetoothAdapterChromeOS::RequestAuthorization( const dbus::ObjectPath& device_path, const ConfirmationCallback& callback) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": RequestAuthorization"; BluetoothPairingChromeOS* pairing = GetPairing(device_path); if (!pairing) { callback.Run(REJECTED); return; } pairing->RequestAuthorization(callback); } void BluetoothAdapterChromeOS::AuthorizeService( const dbus::ObjectPath& device_path, const std::string& uuid, const ConfirmationCallback& callback) { DCHECK(agent_.get()); VLOG(1) << device_path.value() << ": AuthorizeService: " << uuid; BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(device_path); if (!device_chromeos) { callback.Run(CANCELLED); return; } // We always set paired devices to Trusted, so the only reason that this // method call would ever be called is in the case of a race condition where // our "Set('Trusted', true)" method call is still pending in the Bluetooth // daemon because it's busy handling the incoming connection. if (device_chromeos->IsPaired()) { callback.Run(SUCCESS); return; } // TODO(keybuk): reject service authorizations when not paired, determine // whether this is acceptable long-term. LOG(WARNING) << "Rejecting service connection from unpaired device " << device_chromeos->GetAddress() << " for UUID " << uuid; callback.Run(REJECTED); } void BluetoothAdapterChromeOS::Cancel() { DCHECK(agent_.get()); VLOG(1) << "Cancel"; } void BluetoothAdapterChromeOS::OnRegisterAgent() { VLOG(1) << "Pairing agent registered, requesting to be made default"; DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> RequestDefaultAgent( dbus::ObjectPath(kAgentPath), base::Bind(&BluetoothAdapterChromeOS::OnRequestDefaultAgent, weak_ptr_factory_.GetWeakPtr()), base::Bind(&BluetoothAdapterChromeOS::OnRequestDefaultAgentError, weak_ptr_factory_.GetWeakPtr())); } void BluetoothAdapterChromeOS::OnRegisterAgentError( const std::string& error_name, const std::string& error_message) { // Our agent being already registered isn't an error. if (error_name == bluetooth_agent_manager::kErrorAlreadyExists) return; LOG(WARNING) << ": Failed to register pairing agent: " << error_name << ": " << error_message; } void BluetoothAdapterChromeOS::OnRequestDefaultAgent() { VLOG(1) << "Pairing agent now default"; } void BluetoothAdapterChromeOS::OnRequestDefaultAgentError( const std::string& error_name, const std::string& error_message) { LOG(WARNING) << ": Failed to make pairing agent default: " << error_name << ": " << error_message; } BluetoothDeviceChromeOS* BluetoothAdapterChromeOS::GetDeviceWithPath( const dbus::ObjectPath& object_path) { for (DevicesMap::iterator iter = devices_.begin(); iter != devices_.end(); ++iter) { BluetoothDeviceChromeOS* device_chromeos = static_cast<BluetoothDeviceChromeOS*>(iter->second); if (device_chromeos->object_path() == object_path) return device_chromeos; } return NULL; } BluetoothPairingChromeOS* BluetoothAdapterChromeOS::GetPairing( const dbus::ObjectPath& object_path) { BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path); if (!device_chromeos) { LOG(WARNING) << "Pairing Agent request for unknown device: " << object_path.value(); return NULL; } BluetoothPairingChromeOS* pairing = device_chromeos->GetPairing(); if (pairing) return pairing; // The device doesn't have its own pairing context, so this is an incoming // pairing request that should use our best default delegate (if we have one). BluetoothDevice::PairingDelegate* pairing_delegate = DefaultPairingDelegate(); if (!pairing_delegate) return NULL; return device_chromeos->BeginPairing(pairing_delegate); } void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { DCHECK(!IsPresent()); object_path_ = object_path; VLOG(1) << object_path_.value() << ": using adapter."; VLOG(1) << "Registering pairing agent"; DBusThreadManager::Get()->GetBluetoothAgentManagerClient()-> RegisterAgent( dbus::ObjectPath(kAgentPath), bluetooth_agent_manager::kKeyboardDisplayCapability, base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgent, weak_ptr_factory_.GetWeakPtr()), base::Bind(&BluetoothAdapterChromeOS::OnRegisterAgentError, weak_ptr_factory_.GetWeakPtr())); SetDefaultAdapterName(); BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); PresentChanged(true); if (properties->powered.value()) PoweredChanged(true); if (properties->discoverable.value()) DiscoverableChanged(true); if (properties->discovering.value()) DiscoveringChanged(true); std::vector<dbus::ObjectPath> device_paths = DBusThreadManager::Get()->GetBluetoothDeviceClient()-> GetDevicesForAdapter(object_path_); for (std::vector<dbus::ObjectPath>::iterator iter = device_paths.begin(); iter != device_paths.end(); ++iter) { DeviceAdded(*iter); } } void BluetoothAdapterChromeOS::SetDefaultAdapterName() { std::string board = base::SysInfo::GetLsbReleaseBoard(); std::string alias; if (board.substr(0, 6) == "stumpy") { alias = "Chromebox"; } else if (board.substr(0, 4) == "link") { alias = "Chromebook Pixel"; } else { alias = "Chromebook"; } SetName(alias, base::Bind(&base::DoNothing), base::Bind(&base::DoNothing)); } void BluetoothAdapterChromeOS::RemoveAdapter() { DCHECK(IsPresent()); VLOG(1) << object_path_.value() << ": adapter removed."; BluetoothAdapterClient::Properties* properties = DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_); object_path_ = dbus::ObjectPath(""); if (properties->powered.value()) PoweredChanged(false); if (properties->discoverable.value()) DiscoverableChanged(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 BluetoothAdapterChromeOS::PoweredChanged(bool powered) { FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterPoweredChanged(this, powered)); } void BluetoothAdapterChromeOS::DiscoverableChanged(bool discoverable) { FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterDiscoverableChanged(this, discoverable)); } void BluetoothAdapterChromeOS::DiscoveringChanged( bool discovering) { // If the adapter stopped discovery due to a reason other than a request by // us, reset the count to 0. VLOG(1) << "Discovering changed: " << discovering; if (!discovering && !discovery_request_pending_ && num_discovery_sessions_ > 0) { VLOG(1) << "Marking sessions as inactive."; num_discovery_sessions_ = 0; MarkDiscoverySessionsAsInactive(); } FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterDiscoveringChanged(this, discovering)); } void BluetoothAdapterChromeOS::PresentChanged(bool present) { FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, AdapterPresentChanged(this, present)); } void BluetoothAdapterChromeOS::NotifyDeviceChanged( BluetoothDeviceChromeOS* device) { DCHECK(device->adapter_ == this); FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_, DeviceChanged(this, device)); } void BluetoothAdapterChromeOS::OnSetDiscoverable( const base::Closure& callback, const ErrorCallback& error_callback, bool success) { // Set the discoverable_timeout property to zero so the adapter remains // discoverable forever. DBusThreadManager::Get()->GetBluetoothAdapterClient()-> GetProperties(object_path_)->discoverable_timeout.Set( 0, base::Bind(&BluetoothAdapterChromeOS::OnPropertyChangeCompleted, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } void BluetoothAdapterChromeOS::OnPropertyChangeCompleted( const base::Closure& callback, const ErrorCallback& error_callback, bool success) { if (success) callback.Run(); else error_callback.Run(); } void BluetoothAdapterChromeOS::AddDiscoverySession( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << __func__; if (discovery_request_pending_) { // The pending request is either to stop a previous session or to start a // new one. Either way, queue this one. DCHECK(num_discovery_sessions_ == 1 || num_discovery_sessions_ == 0); VLOG(1) << "Pending request to start/stop device discovery. Queueing " << "request to start a new discovery session."; discovery_request_queue_.push(std::make_pair(callback, error_callback)); return; } // The adapter is already discovering. if (num_discovery_sessions_ > 0) { DCHECK(IsDiscovering()); DCHECK(!discovery_request_pending_); num_discovery_sessions_++; callback.Run(); return; } // There are no active discovery sessions. DCHECK(num_discovery_sessions_ == 0); // This is the first request to start device discovery. discovery_request_pending_ = true; DBusThreadManager::Get()->GetBluetoothAdapterClient()-> StartDiscovery( object_path_, base::Bind(&BluetoothAdapterChromeOS::OnStartDiscovery, weak_ptr_factory_.GetWeakPtr(), callback), base::Bind(&BluetoothAdapterChromeOS::OnStartDiscoveryError, weak_ptr_factory_.GetWeakPtr(), callback, error_callback)); } void BluetoothAdapterChromeOS::RemoveDiscoverySession( const base::Closure& callback, const ErrorCallback& error_callback) { VLOG(1) << __func__; // There are active sessions other than the one currently being removed. if (num_discovery_sessions_ > 1) { DCHECK(IsDiscovering()); DCHECK(!discovery_request_pending_); num_discovery_sessions_--; callback.Run(); return; } // If there is a pending request to BlueZ, then queue this request. if (discovery_request_pending_) { VLOG(1) << "Pending request to start/stop device discovery. Queueing " << "request to stop discovery session."; error_callback.Run(); return; } // There are no active sessions. Return error. if (num_discovery_sessions_ == 0) { // TODO(armansito): This should never happen once we have the // DiscoverySession API. Replace this case with an assert once it's // the deprecated methods have been removed. (See crbug.com/3445008). VLOG(1) << "No active discovery sessions. Returning error."; error_callback.Run(); return; } // There is exactly one active discovery session. Request BlueZ to stop // discovery. DCHECK(num_discovery_sessions_ == 1); discovery_request_pending_ = true; DBusThreadManager::Get()->GetBluetoothAdapterClient()-> StopDiscovery( object_path_, base::Bind(&BluetoothAdapterChromeOS::OnStopDiscovery, weak_ptr_factory_.GetWeakPtr(), callback), base::Bind(&BluetoothAdapterChromeOS::OnStopDiscoveryError, weak_ptr_factory_.GetWeakPtr(), error_callback)); } void BluetoothAdapterChromeOS::OnStartDiscovery(const base::Closure& callback) { // Report success on the original request and increment the count. VLOG(1) << __func__; DCHECK(discovery_request_pending_); DCHECK(num_discovery_sessions_ == 0); discovery_request_pending_ = false; num_discovery_sessions_++; callback.Run(); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::OnStartDiscoveryError( const base::Closure& callback, 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; // Failed to start discovery. This can only happen if the count is at 0. DCHECK(num_discovery_sessions_ == 0); DCHECK(discovery_request_pending_); discovery_request_pending_ = false; // Discovery request may fail if discovery was previously initiated by Chrome, // but the session were invalidated due to the discovery state unexpectedly // changing to false and then back to true. In this case, report success. if (error_name == bluetooth_device::kErrorInProgress && IsDiscovering()) { VLOG(1) << "Discovery previously initiated. Reporting success."; num_discovery_sessions_++; callback.Run(); } else { error_callback.Run(); } // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::OnStopDiscovery(const base::Closure& callback) { // Report success on the original request and decrement the count. VLOG(1) << __func__; DCHECK(discovery_request_pending_); DCHECK(num_discovery_sessions_ == 1); discovery_request_pending_ = false; num_discovery_sessions_--; callback.Run(); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::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; // Failed to stop discovery. This can only happen if the count is at 1. DCHECK(discovery_request_pending_); DCHECK(num_discovery_sessions_ == 1); discovery_request_pending_ = false; error_callback.Run(); // Try to add a new discovery session for each queued request. ProcessQueuedDiscoveryRequests(); } void BluetoothAdapterChromeOS::ProcessQueuedDiscoveryRequests() { while (!discovery_request_queue_.empty()) { VLOG(1) << "Process queued discovery request."; DiscoveryCallbackPair callbacks = discovery_request_queue_.front(); discovery_request_queue_.pop(); AddDiscoverySession(callbacks.first, callbacks.second); // If the queued request resulted in a pending call, then let it // asynchonously process the remaining queued requests once the pending // call returns. if (discovery_request_pending_) return; } } } // namespace chromeos