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( |