Chromium Code Reviews| Index: content/browser/bluetooth/bluetooth_dispatcher_host.cc |
| diff --git a/content/browser/bluetooth/bluetooth_dispatcher_host.cc b/content/browser/bluetooth/bluetooth_dispatcher_host.cc |
| index cb1cfaeac2e3eeeee57af7c5159dfe45dd165b3b..6aec23ecd412f997b205ff78fc1308ac80bac8da 100644 |
| --- a/content/browser/bluetooth/bluetooth_dispatcher_host.cc |
| +++ b/content/browser/bluetooth/bluetooth_dispatcher_host.cc |
| @@ -10,7 +10,10 @@ |
| #include "content/browser/bluetooth/bluetooth_dispatcher_host.h" |
| +#include "base/bind.h" |
| +#include "base/single_thread_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| +#include "base/thread_task_runner_handle.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/bluetooth/bluetooth_metrics.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| @@ -33,8 +36,9 @@ namespace content { |
| namespace { |
| -// TODO(ortuno): Once we have a chooser for scanning and the right |
| -// callback for discovered services we should delete these constants. |
| +// TODO(ortuno): Once we have a chooser for scanning, a way to control that |
| +// chooser from tests, and the right callback for discovered services we should |
| +// delete these constants. |
| // https://crbug.com/436280 and https://crbug.com/484504 |
| const int kDelayTime = 5; // 5 seconds for scanning and discovering |
| const int kTestingDelayTime = 0; // No need to wait during tests |
| @@ -126,14 +130,30 @@ blink::WebBluetoothError TranslateGATTError( |
| return blink::WebBluetoothError::GATTUntranslatedErrorCode; |
| } |
| +void StopDiscoverySession( |
| + scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { |
| + // Nothing goes wrong if the discovery session fails to stop, and we don't |
| + // need to wait for it before letting the user's script proceed, so we ignore |
| + // the results here. |
| + discovery_session->Stop(base::Bind(&base::DoNothing), |
|
ortuno
2015/08/17 21:12:58
Should we at least log or histogram the result? It
Jeffrey Yasskin
2015/08/17 23:38:15
We should, but I think not here. I've just filed h
|
| + base::Bind(&base::DoNothing)); |
| +} |
| + |
| } // namespace |
| BluetoothDispatcherHost::BluetoothDispatcherHost(int render_process_id) |
| : BrowserMessageFilter(BluetoothMsgStart), |
| render_process_id_(render_process_id), |
| + current_delay_time_(kDelayTime), |
| + discovery_session_timer_( |
| + FROM_HERE, |
| + // TODO(jyasskin): Add a way for tests to control the dialog |
| + // directly, and change this to a reasonable discovery timeout. |
| + base::TimeDelta::FromSecondsD(current_delay_time_), |
| + base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, this), |
|
ortuno
2015/08/17 21:12:58
Shouldn't you pass a weak pointer to the timer?
Jeffrey Yasskin
2015/08/17 23:38:15
Good catch: I need to explicitly pass an unretaine
|
| + /*is_repeating=*/false), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - current_delay_time_ = kDelayTime; |
| if (BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) |
| BluetoothAdapterFactory::GetAdapter( |
| base::Bind(&BluetoothDispatcherHost::set_adapter, |
| @@ -171,6 +191,10 @@ void BluetoothDispatcherHost::SetBluetoothAdapterForTesting( |
| scoped_refptr<device::BluetoothAdapter> mock_adapter) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| current_delay_time_ = kTestingDelayTime; |
| + // Reset the discovery session timer to use the new delay time. |
| + discovery_session_timer_.Start( |
| + FROM_HERE, base::TimeDelta::FromSecondsD(current_delay_time_), |
| + base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, this)); |
|
ortuno
2015/08/17 21:12:58
Same here. Wouldn't a weak pointer be better?
Jeffrey Yasskin
2015/08/17 23:38:16
Switched to Unretained here too.
|
| set_adapter(mock_adapter.Pass()); |
| } |
| @@ -183,12 +207,28 @@ BluetoothDispatcherHost::~BluetoothDispatcherHost() { |
| // Stores information associated with an in-progress requestDevice call. This |
| // will include the state of the active chooser dialog in a future patch. |
| struct BluetoothDispatcherHost::RequestDeviceSession { |
| - RequestDeviceSession(const std::vector<BluetoothScanFilter>& filters, |
| + public: |
| + RequestDeviceSession(int thread_id, |
| + int request_id, |
| + const std::vector<BluetoothScanFilter>& filters, |
| const std::vector<BluetoothUUID>& optional_services) |
| - : filters(filters), optional_services(optional_services) {} |
| + : thread_id(thread_id), |
| + request_id(request_id), |
| + filters(filters), |
| + optional_services(optional_services) {} |
| - std::vector<BluetoothScanFilter> filters; |
| - std::vector<BluetoothUUID> optional_services; |
| + void AddFilteredDevice(const device::BluetoothDevice& device) { |
|
ortuno
2015/08/17 21:12:58
Why do you pass the device by reference if you alr
Jeffrey Yasskin
2015/08/17 23:38:16
To be clear that |device| can't be null inside thi
|
| + if (chooser && MatchesFilters(device, filters)) { |
| + chooser->AddDevice(device.GetIdentifier(), device.GetName()); |
| + } |
| + } |
| + |
| + const int thread_id; |
| + const int request_id; |
| + const std::vector<BluetoothScanFilter> filters; |
| + const std::vector<BluetoothUUID> optional_services; |
| + scoped_ptr<BluetoothChooser> chooser; |
| + scoped_ptr<device::BluetoothDiscoverySession> discovery_session; |
| }; |
| void BluetoothDispatcherHost::set_adapter( |
| @@ -201,6 +241,46 @@ void BluetoothDispatcherHost::set_adapter( |
| adapter_->AddObserver(this); |
| } |
| +void BluetoothDispatcherHost::StopDeviceDiscovery() { |
| + for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( |
| + &request_device_sessions_); |
| + !iter.IsAtEnd(); iter.Advance()) { |
| + RequestDeviceSession* session = iter.GetCurrentValue(); |
| + if (session->discovery_session) { |
| + StopDiscoverySession(session->discovery_session.Pass()); |
| + } |
| + if (session->chooser) { |
| + session->chooser->ShowDiscovering(BluetoothChooser::DiscoveryState::IDLE); |
|
ortuno
2015/08/17 21:12:58
See below. ShowDiscovering could remove the sessio
Jeffrey Yasskin
2015/08/17 23:38:16
See below.
|
| + } |
| + } |
| +} |
| + |
| +void BluetoothDispatcherHost::AdapterPoweredChanged( |
| + device::BluetoothAdapter* adapter, |
| + bool powered) { |
| + const BluetoothChooser::AdapterPresence presence = |
| + powered ? BluetoothChooser::AdapterPresence::POWERED_ON |
| + : BluetoothChooser::AdapterPresence::POWERED_OFF; |
| + for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( |
| + &request_device_sessions_); |
| + !iter.IsAtEnd(); iter.Advance()) { |
| + RequestDeviceSession* session = iter.GetCurrentValue(); |
| + if (session->chooser) |
| + session->chooser->SetAdapterPresence(presence); |
| + } |
| +} |
| + |
| +void BluetoothDispatcherHost::DeviceAdded(device::BluetoothAdapter* adapter, |
| + device::BluetoothDevice* device) { |
| + VLOG(1) << "Adding device to all choosers: " << device->GetIdentifier(); |
| + for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( |
| + &request_device_sessions_); |
| + !iter.IsAtEnd(); iter.Advance()) { |
| + RequestDeviceSession* session = iter.GetCurrentValue(); |
| + session->AddFilteredDevice(*device); |
|
ortuno
2015/08/17 21:12:58
AddFilteredDevice could end up removing the sessio
Jeffrey Yasskin
2015/08/17 23:38:15
Yep, the PostTask there makes sure the object is o
|
| + } |
| +} |
| + |
| static scoped_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter( |
| const std::vector<BluetoothScanFilter>& filters) { |
| std::set<BluetoothUUID> services; |
| @@ -251,56 +331,58 @@ void BluetoothDispatcherHost::OnRequestDevice( |
| return; |
| } |
| - // TODO(scheib): Device selection UI: crbug.com/436280 |
| - // TODO(scheib): Utilize BluetoothAdapter::Observer::DeviceAdded/Removed. |
| - if (adapter_.get()) { |
| - if (!request_device_sessions_ |
| - .insert(std::make_pair( |
| - std::make_pair(thread_id, request_id), |
| - RequestDeviceSession(filters, optional_services))) |
| - .second) { |
| - LOG(ERROR) << "2 requestDevice() calls with the same thread_id (" |
| - << thread_id << ") and request_id (" << request_id |
| - << ") shouldn't arrive at the same BluetoothDispatcherHost."; |
| - bad_message::ReceivedBadMessage( |
| - this, bad_message::BDH_DUPLICATE_REQUEST_DEVICE_ID); |
| - } |
| - if (!adapter_->IsPresent()) { |
| - VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice."; |
| - RecordRequestDeviceOutcome( |
| - UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT); |
| - Send(new BluetoothMsg_RequestDeviceError( |
| - thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); |
| - request_device_sessions_.erase(std::make_pair(thread_id, request_id)); |
| - return; |
| - } |
| - // TODO(jyasskin): Once the dialog is available, the dialog should check for |
| - // the status of the adapter, i.e. check IsPowered() and |
| - // BluetoothAdapter::Observer::PoweredChanged, and inform the user. But |
| - // until the dialog is available we log/histogram the status and return |
| - // with a message. |
| - // https://crbug.com/517237 |
| - if (!adapter_->IsPowered()) { |
| - RecordRequestDeviceOutcome( |
| - UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_OFF); |
| - Send(new BluetoothMsg_RequestDeviceError( |
| - thread_id, request_id, WebBluetoothError::BluetoothAdapterOff)); |
| - request_device_sessions_.erase(std::make_pair(thread_id, request_id)); |
| - return; |
| - } |
| - adapter_->StartDiscoverySessionWithFilter( |
| - ComputeScanFilter(filters), |
| - base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, |
| - weak_ptr_factory_.GetWeakPtr(), thread_id, request_id), |
| - base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, |
| - weak_ptr_factory_.GetWeakPtr(), thread_id, request_id)); |
| - } else { |
| + if (!adapter_) { |
| VLOG(1) << "No BluetoothAdapter. Can't serve requestDevice."; |
| RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER); |
| Send(new BluetoothMsg_RequestDeviceError( |
| thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); |
| + return; |
| } |
| - return; |
| + |
| + if (!adapter_->IsPresent()) { |
| + VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice."; |
| + RecordRequestDeviceOutcome( |
| + UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT); |
| + Send(new BluetoothMsg_RequestDeviceError( |
| + thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); |
| + return; |
| + } |
| + |
| + // Create storage for the information that backs the chooser, and show the |
| + // chooser. |
| + RequestDeviceSession* const session = new RequestDeviceSession( |
| + thread_id, request_id, filters, optional_services); |
| + int chooser_id = request_device_sessions_.Add(session); |
| + |
| + session->chooser = render_frame_host->RunBluetoothChooser( |
| + this, chooser_id, render_frame_host->GetLastCommittedURL().GetOrigin()); |
|
ortuno
2015/08/17 21:12:58
If you call requestDevice() from an iframe would i
Jeffrey Yasskin
2015/08/17 23:38:15
Right now, it would show the frame's origin, since
|
| + if (!session->chooser) { |
| + LOG(ERROR) << "No Bluetooth chooser implementation."; |
| + RecordRequestDeviceOutcome( |
| + UMARequestDeviceOutcome::NO_BLUETOOTH_CHOOSER_IMPLEMENTATION); |
| + Send(new BluetoothMsg_RequestDeviceError( |
| + thread_id, request_id, WebBluetoothError::NoBluetoothChooser)); |
| + request_device_sessions_.Remove(chooser_id); |
| + return; |
| + } |
| + |
| + session->chooser->SetAdapterPresence( |
|
ortuno
2015/08/17 21:12:58
Since this call could close the dialog shouldn't y
Jeffrey Yasskin
2015/08/17 23:38:15
Yielding to the event loop in CloseDialog() makes
|
| + adapter_->IsPowered() ? BluetoothChooser::AdapterPresence::POWERED_ON |
| + : BluetoothChooser::AdapterPresence::POWERED_OFF); |
| + |
| + // Populate the initial list of devices. |
| + VLOG(1) << "Populating devices in chooser" << chooser_id; |
| + for (const device::BluetoothDevice* device : adapter_->GetDevices()) { |
| + VLOG(1) << "\t" << device->GetIdentifier(); |
| + session->AddFilteredDevice(*device); |
| + } |
| + |
| + adapter_->StartDiscoverySessionWithFilter( |
| + ComputeScanFilter(filters), |
| + base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, |
| + weak_ptr_factory_.GetWeakPtr(), chooser_id), |
| + base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, |
| + weak_ptr_factory_.GetWeakPtr(), chooser_id)); |
| } |
| void BluetoothDispatcherHost::OnConnectGATT( |
| @@ -545,86 +627,113 @@ void BluetoothDispatcherHost::OnWriteValue( |
| } |
| void BluetoothDispatcherHost::OnDiscoverySessionStarted( |
| - int thread_id, |
| - int request_id, |
| + int chooser_id, |
| scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - BrowserThread::PostDelayedTask( |
| - BrowserThread::UI, FROM_HERE, |
| - base::Bind(&BluetoothDispatcherHost::StopDiscoverySession, |
| - weak_ptr_factory_.GetWeakPtr(), thread_id, request_id, |
| - base::Passed(&discovery_session)), |
| - base::TimeDelta::FromSeconds(current_delay_time_)); |
| + VLOG(1) << "Started discovery session for " << chooser_id; |
| + if (RequestDeviceSession* session = |
| + request_device_sessions_.Lookup(chooser_id)) { |
| + VLOG(1) << "Setting discovery session for " << chooser_id; |
| + session->discovery_session = discovery_session.Pass(); |
| + discovery_session_timer_.Reset(); |
| + if (session->chooser) { |
| + session->chooser->ShowDiscovering( |
| + BluetoothChooser::DiscoveryState::DISCOVERING); |
| + } |
| + } else { |
| + // Dialog was closed before the session finished starting. Stop the session. |
| + StopDiscoverySession(discovery_session.Pass()); |
| + } |
| } |
| -void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int thread_id, |
| - int request_id) { |
| +void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int chooser_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - DLOG(WARNING) << "BluetoothDispatcherHost::OnDiscoverySessionStartedError"; |
| - RecordRequestDeviceOutcome(UMARequestDeviceOutcome::DISCOVERY_START_FAILED); |
| - Send(new BluetoothMsg_RequestDeviceError( |
| - thread_id, request_id, WebBluetoothError::DiscoverySessionStartFailed)); |
| - request_device_sessions_.erase(std::make_pair(thread_id, request_id)); |
| -} |
| - |
| -void BluetoothDispatcherHost::StopDiscoverySession( |
| - int thread_id, |
| - int request_id, |
| - scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { |
| - DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - discovery_session->Stop( |
| - base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStopped, |
| - weak_ptr_factory_.GetWeakPtr(), thread_id, request_id), |
| - base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStoppedError, |
| - weak_ptr_factory_.GetWeakPtr(), thread_id, request_id)); |
| -} |
| - |
| -void BluetoothDispatcherHost::OnDiscoverySessionStopped(int thread_id, |
| - int request_id) { |
| - DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - auto session = |
| - request_device_sessions_.find(std::make_pair(thread_id, request_id)); |
| - CHECK(session != request_device_sessions_.end()); |
| - BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); |
| - for (device::BluetoothDevice* device : devices) { |
| - VLOG(1) << "Device: " << device->GetName(); |
| - VLOG(1) << "UUIDs: "; |
| - for (BluetoothUUID uuid : device->GetUUIDs()) |
| - VLOG(1) << "\t" << uuid.canonical_value(); |
| - if (MatchesFilters(*device, session->second.filters)) { |
| - content::BluetoothDevice device_ipc( |
| - device->GetAddress(), // instance_id |
| - device->GetName(), // name |
| - device->GetBluetoothClass(), // device_class |
| - device->GetVendorIDSource(), // vendor_id_source |
| - device->GetVendorID(), // vendor_id |
| - device->GetProductID(), // product_id |
| - device->GetDeviceID(), // product_version |
| - device->IsPaired(), // paired |
| - content::BluetoothDevice::UUIDsFromBluetoothUUIDs( |
| - device->GetUUIDs())); // uuids |
| - RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); |
| - Send(new BluetoothMsg_RequestDeviceSuccess(thread_id, request_id, |
| - device_ipc)); |
| - request_device_sessions_.erase(session); |
| - return; |
| + VLOG(1) << "Failed to start discovery session for " << chooser_id; |
| + if (RequestDeviceSession* session = |
| + request_device_sessions_.Lookup(chooser_id)) { |
| + if (session->chooser && !session->discovery_session) { |
| + session->chooser->ShowDiscovering( |
| + BluetoothChooser::DiscoveryState::FAILED_TO_START); |
| } |
| } |
| - RecordRequestDeviceOutcome( |
| - UMARequestDeviceOutcome::NO_MATCHING_DEVICES_FOUND); |
| - Send(new BluetoothMsg_RequestDeviceError(thread_id, request_id, |
| - WebBluetoothError::NoDevicesFound)); |
| - request_device_sessions_.erase(session); |
| + // Ignore discovery session start errors when the dialog was already closed by |
| + // the time they happen. |
| } |
| -void BluetoothDispatcherHost::OnDiscoverySessionStoppedError(int thread_id, |
| - int request_id) { |
| - DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| - DLOG(WARNING) << "BluetoothDispatcherHost::OnDiscoverySessionStoppedError"; |
| - RecordRequestDeviceOutcome(UMARequestDeviceOutcome::DISCOVERY_STOP_FAILED); |
| +void BluetoothDispatcherHost::CloseDialog(int chooser_id, |
| + const base::Closure& callback) { |
| + RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); |
| + DCHECK(session) << "Shouldn't close the dialog twice."; |
| + CHECK(session->chooser) << "Shouldn't close the dialog twice."; |
|
ortuno
2015/08/17 21:12:58
Do you need this CHECK? You would get a null point
Jeffrey Yasskin
2015/08/17 23:38:16
session->chooser.reset() works if session->chooser
|
| + session->chooser.reset(); |
| + if (!base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback)) { |
| + LOG(WARNING) << "No TaskRunner; not closing requestDevice dialog."; |
| + } |
| +} |
| + |
| +void BluetoothDispatcherHost::DialogCancelled(int chooser_id) { |
| + CloseDialog(chooser_id, |
| + base::Bind(&BluetoothDispatcherHost::OnRequestDeviceCancelled, |
| + weak_ptr_factory_.GetWeakPtr(), chooser_id)); |
| +} |
| + |
| +void BluetoothDispatcherHost::DeviceSelected(int chooser_id, |
| + const std::string& device_id) { |
| + CloseDialog( |
| + chooser_id, |
| + base::Bind(&BluetoothDispatcherHost::OnRequestDeviceSelected, |
| + weak_ptr_factory_.GetWeakPtr(), chooser_id, device_id)); |
| +} |
| + |
| +void BluetoothDispatcherHost::OnRequestDeviceCancelled(int chooser_id) { |
| + RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); |
| + DCHECK(session) << "Session removed unexpectedly."; |
| + RecordRequestDeviceOutcome( |
| + UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED); |
| + VLOG(1) << "Bluetooth chooser cancelled"; |
| Send(new BluetoothMsg_RequestDeviceError( |
| - thread_id, request_id, WebBluetoothError::DiscoverySessionStopFailed)); |
| - request_device_sessions_.erase(std::make_pair(thread_id, request_id)); |
| + session->thread_id, session->request_id, |
| + WebBluetoothError::ChooserCancelled)); |
| + request_device_sessions_.Remove(chooser_id); |
| +} |
| + |
| +void BluetoothDispatcherHost::OnRequestDeviceSelected( |
| + int chooser_id, |
| + const std::string& device_id) { |
| + RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); |
| + DCHECK(session) << "Session removed unexpectedly."; |
| + |
| + const device::BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); |
| + const device::BluetoothDevice* const device = adapter_->GetDevice(device_id); |
| + if (device == nullptr) { |
| + RecordRequestDeviceOutcome(UMARequestDeviceOutcome::CHOSEN_DEVICE_VANISHED); |
| + Send(new BluetoothMsg_RequestDeviceError( |
| + session->thread_id, session->request_id, |
| + WebBluetoothError::ChosenDeviceVanished)); |
| + request_device_sessions_.Remove(chooser_id); |
| + return; |
| + } |
| + |
| + VLOG(1) << "Device: " << device->GetName(); |
| + VLOG(1) << "UUIDs: "; |
| + for (BluetoothUUID uuid : device->GetUUIDs()) |
| + VLOG(1) << "\t" << uuid.canonical_value(); |
| + |
| + content::BluetoothDevice device_ipc( |
| + device->GetAddress(), // instance_id |
| + device->GetName(), // name |
| + device->GetBluetoothClass(), // device_class |
| + device->GetVendorIDSource(), // vendor_id_source |
| + device->GetVendorID(), // vendor_id |
| + device->GetProductID(), // product_id |
| + device->GetDeviceID(), // product_version |
| + device->IsPaired(), // paired |
| + content::BluetoothDevice::UUIDsFromBluetoothUUIDs( |
| + device->GetUUIDs())); // uuids |
| + RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); |
| + Send(new BluetoothMsg_RequestDeviceSuccess(session->thread_id, |
| + session->request_id, device_ipc)); |
| + request_device_sessions_.Remove(chooser_id); |
| } |
| void BluetoothDispatcherHost::OnGATTConnectionCreated( |