diff options
author | jyasskin <jyasskin@chromium.org> | 2015-08-25 21:46:47 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-26 04:47:21 +0000 |
commit | 5e7aec4faea108b6e64ee6a4712e77ca5b43e3c7 (patch) | |
tree | b4ac0cc748e3564d72428c7c628bb786b7377d98 | |
parent | 56c58615524d8ad4bba0423c7efb05cf4e2233c2 (diff) | |
download | chromium_src-5e7aec4faea108b6e64ee6a4712e77ca5b43e3c7.zip chromium_src-5e7aec4faea108b6e64ee6a4712e77ca5b43e3c7.tar.gz chromium_src-5e7aec4faea108b6e64ee6a4712e77ca5b43e3c7.tar.bz2 |
Add a path for content/ to open and control a Bluetooth chooser dialog.
This obsoletes several histogram values, and some of the corner-case error states won't be well tested until I add the ability for layout tests to manage dialog states.
2nd of 3 patches:
1. Add errors and prepare tests. (https://codereview.chromium.org/1293593003/)
2. Wire up the chooser on the Chrome side. (This patch)
3. Update the test assertions and remove now-unused errors. (https://codereview.chromium.org/1284143006/)
BUG=500989, 517237
Review URL: https://codereview.chromium.org/1286063002
Cr-Commit-Position: refs/heads/master@{#345541}
-rw-r--r-- | content/browser/bad_message.h | 2 | ||||
-rw-r--r-- | content/browser/bluetooth/bluetooth_dispatcher_host.cc | 381 | ||||
-rw-r--r-- | content/browser/bluetooth/bluetooth_dispatcher_host.h | 44 | ||||
-rw-r--r-- | content/browser/bluetooth/bluetooth_metrics.h | 10 | ||||
-rw-r--r-- | content/browser/bluetooth/first_device_bluetooth_chooser.cc | 46 | ||||
-rw-r--r-- | content/browser/bluetooth/first_device_bluetooth_chooser.h | 39 | ||||
-rw-r--r-- | content/content_browser.gypi | 4 | ||||
-rw-r--r-- | content/public/browser/bluetooth_chooser.cc | 11 | ||||
-rw-r--r-- | content/public/browser/bluetooth_chooser.h | 66 | ||||
-rw-r--r-- | content/public/browser/web_contents_delegate.cc | 7 | ||||
-rw-r--r-- | content/public/browser/web_contents_delegate.h | 8 | ||||
-rw-r--r-- | tools/metrics/histograms/histograms.xml | 13 |
12 files changed, 488 insertions, 143 deletions
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h index 9e7a185..3f5e3b0 100644 --- a/content/browser/bad_message.h +++ b/content/browser/bad_message.h @@ -114,7 +114,7 @@ enum BadMessageReason { BDH_INVALID_WRITE_VALUE_LENGTH = 90, WC_MEMORY_CACHE_RESOURCE_BAD_SECURITY_INFO = 91, WC_RENDERER_DID_NAVIGATE_BAD_SECURITY_INFO = 92, - BDH_DUPLICATE_REQUEST_DEVICE_ID = 93, + OBSOLETE_BDH_DUPLICATE_REQUEST_DEVICE_ID = 93, CSDH_INVALID_ORIGIN = 94, RDH_ILLEGAL_ORIGIN = 95, diff --git a/content/browser/bluetooth/bluetooth_dispatcher_host.cc b/content/browser/bluetooth/bluetooth_dispatcher_host.cc index c6c49b1..af5c8f4 100644 --- a/content/browser/bluetooth/bluetooth_dispatcher_host.cc +++ b/content/browser/bluetooth/bluetooth_dispatcher_host.cc @@ -10,11 +10,17 @@ #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/bluetooth/first_device_bluetooth_chooser.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/common/bluetooth/bluetooth_messages.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_device.h" @@ -33,8 +39,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 @@ -137,14 +144,33 @@ 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), + 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, + // base::Timer guarantees it won't call back after its + // destructor starts. + base::Unretained(this)), + /*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, @@ -182,6 +208,13 @@ 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, + // base::Timer guarantees it won't call back after its + // destructor starts. + base::Unretained(this))); set_adapter(mock_adapter.Pass()); } @@ -194,12 +227,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) {} + + void AddFilteredDevice(const device::BluetoothDevice& device) { + if (chooser && MatchesFilters(device, filters)) { + chooser->AddDevice(device.GetIdentifier(), device.GetName()); + } + } - std::vector<BluetoothScanFilter> filters; - std::vector<BluetoothUUID> optional_services; + 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( @@ -212,6 +261,47 @@ 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->ShowDiscoveryState( + BluetoothChooser::DiscoveryState::IDLE); + } + } +} + +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); + } +} + static scoped_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter( const std::vector<BluetoothScanFilter>& filters) { std::set<BluetoothUUID> services; @@ -262,56 +352,74 @@ 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; + } + + 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); + + BluetoothChooser::EventHandler chooser_event_handler = + base::Bind(&BluetoothDispatcherHost::OnBluetoothChooserEvent, + weak_ptr_factory_.GetWeakPtr(), chooser_id); + if (WebContents* web_contents = + WebContents::FromRenderFrameHost(render_frame_host)) { + if (WebContentsDelegate* delegate = web_contents->GetDelegate()) { + session->chooser = delegate->RunBluetoothChooser( + web_contents, chooser_event_handler, + render_frame_host->GetLastCommittedURL().GetOrigin()); + } + } + if (!session->chooser) { + LOG(WARNING) + << "No Bluetooth chooser implementation; falling back to first device."; + session->chooser.reset( + new FirstDeviceBluetoothChooser(chooser_event_handler)); + } + + // 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); + } + + if (!session->chooser) { + // If the dialog's closing, no need to do any of the rest of this. + return; + } + + if (!adapter_->IsPowered()) { + session->chooser->SetAdapterPresence( + BluetoothChooser::AdapterPresence::POWERED_OFF); + return; } - return; + + // Redundant with the chooser's default; just to be clear: + session->chooser->ShowDiscoveryState( + BluetoothChooser::DiscoveryState::DISCOVERING); + 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( @@ -564,86 +672,117 @@ 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)) { + session->discovery_session = discovery_session.Pass(); -void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int thread_id, - int request_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)); + // Arrange to stop discovery later. + discovery_session_timer_.Reset(); + } else { + VLOG(1) << "Chooser " << chooser_id + << " was closed before the session finished starting. Stopping."; + StopDiscoverySession(discovery_session.Pass()); + } } -void BluetoothDispatcherHost::StopDiscoverySession( - int thread_id, - int request_id, - scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { +void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int chooser_id) { 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)); + 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->ShowDiscoveryState( + BluetoothChooser::DiscoveryState::FAILED_TO_START); + } + } + // Ignore discovery session start errors when the dialog was already closed by + // the time they happen. } -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; - } +void BluetoothDispatcherHost::OnBluetoothChooserEvent( + int chooser_id, + BluetoothChooser::Event event, + const std::string& device_id) { + switch (event) { + case BluetoothChooser::Event::CANCELLED: + case BluetoothChooser::Event::SELECTED: + 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."; + + // Synchronously ensure nothing else calls into the chooser after it has + // asked to be closed. + session->chooser.reset(); + + // Yield to the event loop to make sure we don't destroy the session + // within a BluetoothDispatcherHost stack frame. + if (!base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&BluetoothDispatcherHost::FinishClosingChooser, + weak_ptr_factory_.GetWeakPtr(), chooser_id, event, + device_id))) { + LOG(WARNING) << "No TaskRunner; not closing requestDevice dialog."; + } + break; } - RecordRequestDeviceOutcome( - UMARequestDeviceOutcome::NO_MATCHING_DEVICES_FOUND); - Send(new BluetoothMsg_RequestDeviceError(thread_id, request_id, - WebBluetoothError::NoDevicesFound)); - request_device_sessions_.erase(session); } -void BluetoothDispatcherHost::OnDiscoverySessionStoppedError(int thread_id, - int request_id) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - DLOG(WARNING) << "BluetoothDispatcherHost::OnDiscoverySessionStoppedError"; - RecordRequestDeviceOutcome(UMARequestDeviceOutcome::DISCOVERY_STOP_FAILED); - Send(new BluetoothMsg_RequestDeviceError( - thread_id, request_id, WebBluetoothError::DiscoverySessionStopFailed)); - request_device_sessions_.erase(std::make_pair(thread_id, request_id)); +void BluetoothDispatcherHost::FinishClosingChooser( + int chooser_id, + BluetoothChooser::Event event, + const std::string& device_id) { + RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); + DCHECK(session) << "Session removed unexpectedly."; + + if (event == BluetoothChooser::Event::CANCELLED) { + RecordRequestDeviceOutcome( + UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED); + VLOG(1) << "Bluetooth chooser cancelled"; + Send(new BluetoothMsg_RequestDeviceError( + session->thread_id, session->request_id, + WebBluetoothError::ChooserCancelled)); + request_device_sessions_.Remove(chooser_id); + return; + } + DCHECK_EQ(static_cast<int>(event), + static_cast<int>(BluetoothChooser::Event::SELECTED)); + + 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( diff --git a/content/browser/bluetooth/bluetooth_dispatcher_host.h b/content/browser/bluetooth/bluetooth_dispatcher_host.h index e0c92f7..8dfee76 100644 --- a/content/browser/bluetooth/bluetooth_dispatcher_host.h +++ b/content/browser/bluetooth/bluetooth_dispatcher_host.h @@ -6,7 +6,9 @@ #define CONTENT_BROWSER_BLUETOOTH_BLUETOOTH_DISPATCHER_HOST_H_ #include "base/basictypes.h" +#include "base/id_map.h" #include "base/memory/weak_ptr.h" +#include "content/public/browser/bluetooth_chooser.h" #include "content/public/browser/browser_message_filter.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_gatt_connection.h" @@ -57,6 +59,16 @@ class CONTENT_EXPORT BluetoothDispatcherHost final // releasing references to previous |adapter_|. void set_adapter(scoped_refptr<device::BluetoothAdapter> adapter); + // Stops all BluetoothDiscoverySessions being run for requestDevice() + // choosers. + void StopDeviceDiscovery(); + + // BluetoothAdapter::Observer: + void AdapterPoweredChanged(device::BluetoothAdapter* adapter, + bool powered) override; + void DeviceAdded(device::BluetoothAdapter* adapter, + device::BluetoothDevice* device) override; + // IPC Handlers, see definitions in bluetooth_messages.h. void OnRequestDevice( int thread_id, @@ -84,20 +96,21 @@ class CONTENT_EXPORT BluetoothDispatcherHost final // Callbacks for BluetoothAdapter::StartDiscoverySession. void OnDiscoverySessionStarted( - int thread_id, - int request_id, + int chooser_id, scoped_ptr<device::BluetoothDiscoverySession> discovery_session); - void OnDiscoverySessionStartedError(int thread_id, int request_id); + void OnDiscoverySessionStartedError(int chooser_id); - // Stop in progress discovery session. - void StopDiscoverySession( - int thread_id, - int request_id, - scoped_ptr<device::BluetoothDiscoverySession> discovery_session); + // BluetoothChooser::EventHandler: + void OnBluetoothChooserEvent(int chooser_id, + BluetoothChooser::Event event, + const std::string& device_id); - // Callbacks for BluetoothDiscoverySession::Stop. - void OnDiscoverySessionStopped(int thread_id, int request_id); - void OnDiscoverySessionStoppedError(int thread_id, int request_id); + // The chooser implementation yields to the event loop to avoid re-entering + // code that's still using the RequestDeviceSession, and continues with this + // function. + void FinishClosingChooser(int chooser_id, + BluetoothChooser::Event event, + const std::string& device_id); // Callbacks for BluetoothDevice::CreateGattConnection. void OnGATTConnectionCreated( @@ -142,7 +155,7 @@ class CONTENT_EXPORT BluetoothDispatcherHost final // including the chooser dialog. // An entry is added to this map in OnRequestDevice, and should be removed // again everywhere a requestDevice() reply is sent. - std::map<std::pair<int, int>, RequestDeviceSession> request_device_sessions_; + IDMap<RequestDeviceSession, IDMapOwnPointer> request_device_sessions_; // Maps to get the object's parent based on it's instanceID // Map of service_instance_id to device_instance_id. @@ -156,6 +169,13 @@ class CONTENT_EXPORT BluetoothDispatcherHost final // A BluetoothAdapter instance representing an adapter of the system. scoped_refptr<device::BluetoothAdapter> adapter_; + // Automatically stops Bluetooth discovery a set amount of time after it was + // started. We have a single timer for all of Web Bluetooth because it's + // simpler than tracking timeouts for each RequestDeviceSession individually, + // and because there's no harm in extending the length of a few discovery + // sessions when other sessions are active. + base::Timer discovery_session_timer_; + // Must be last member, see base/memory/weak_ptr.h documentation base::WeakPtrFactory<BluetoothDispatcherHost> weak_ptr_factory_; diff --git a/content/browser/bluetooth/bluetooth_metrics.h b/content/browser/bluetooth/bluetooth_metrics.h index d094765..fb1a282 100644 --- a/content/browser/bluetooth/bluetooth_metrics.h +++ b/content/browser/bluetooth/bluetooth_metrics.h @@ -43,11 +43,13 @@ enum class UMARequestDeviceOutcome { SUCCESS = 0, NO_BLUETOOTH_ADAPTER = 1, NO_RENDER_FRAME = 2, - DISCOVERY_START_FAILED = 3, - DISCOVERY_STOP_FAILED = 4, - NO_MATCHING_DEVICES_FOUND = 5, + OBSOLETE_DISCOVERY_START_FAILED = 3, + OBSOLETE_DISCOVERY_STOP_FAILED = 4, + OBSOLETE_NO_MATCHING_DEVICES_FOUND = 5, BLUETOOTH_ADAPTER_NOT_PRESENT = 6, - BLUETOOTH_ADAPTER_OFF = 7, + OBSOLETE_BLUETOOTH_ADAPTER_OFF = 7, + CHOSEN_DEVICE_VANISHED = 8, + BLUETOOTH_CHOOSER_CANCELLED = 9, // NOTE: Add new requestDevice() outcomes immediately above this line. Make // sure to update the enum list in // tools/metrics/histograms/histograms.xml accordingly. diff --git a/content/browser/bluetooth/first_device_bluetooth_chooser.cc b/content/browser/bluetooth/first_device_bluetooth_chooser.cc new file mode 100644 index 0000000..e265435 --- /dev/null +++ b/content/browser/bluetooth/first_device_bluetooth_chooser.cc @@ -0,0 +1,46 @@ +// Copyright 2015 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 "content/browser/bluetooth/first_device_bluetooth_chooser.h" + +namespace content { + +FirstDeviceBluetoothChooser::FirstDeviceBluetoothChooser( + const EventHandler& event_handler) + : event_handler_(event_handler) {} + +FirstDeviceBluetoothChooser::~FirstDeviceBluetoothChooser() {} + +void FirstDeviceBluetoothChooser::SetAdapterPresence(AdapterPresence presence) { + switch (presence) { + case AdapterPresence::ABSENT: + case AdapterPresence::POWERED_OFF: + // Without a user-visible dialog, if the adapter is off, there's no way to + // ask the user to turn it on again, so we should cancel. + event_handler_.Run(Event::CANCELLED, ""); + break; + case AdapterPresence::POWERED_ON: + break; + } +} + +void FirstDeviceBluetoothChooser::ShowDiscoveryState(DiscoveryState state) { + switch (state) { + case DiscoveryState::FAILED_TO_START: + case DiscoveryState::IDLE: + // Without a user-visible dialog, if discovery finishes without finding a + // device, we'll never find one, so we should cancel. + event_handler_.Run(Event::CANCELLED, ""); + break; + case DiscoveryState::DISCOVERING: + break; + } +} + +void FirstDeviceBluetoothChooser::AddDevice(const std::string& deviceId, + const base::string16& deviceName) { + event_handler_.Run(Event::SELECTED, deviceId); +} + +} // namespace content diff --git a/content/browser/bluetooth/first_device_bluetooth_chooser.h b/content/browser/bluetooth/first_device_bluetooth_chooser.h new file mode 100644 index 0000000..2cc4ecf --- /dev/null +++ b/content/browser/bluetooth/first_device_bluetooth_chooser.h @@ -0,0 +1,39 @@ +// Copyright 2015 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 CONTENT_BROWSER_BLUETOOTH_FIRST_DEVICE_BLUETOOTH_CHOOSER_H_ +#define CONTENT_BROWSER_BLUETOOTH_FIRST_DEVICE_BLUETOOTH_CHOOSER_H_ + +#include "base/macros.h" +#include "content/public/browser/bluetooth_chooser.h" + +namespace content { + +// Implements a Bluetooth chooser that, instead of showing a dialog, selects the +// first added device, or cancels if no device is added before discovery stops. +// This is used as a default chooser implementation for platforms without a full +// UI. +class FirstDeviceBluetoothChooser : public BluetoothChooser { + public: + // See the BluetoothChooser::EventHandler comments for how |event_handler| is + // used. + explicit FirstDeviceBluetoothChooser(const EventHandler& event_handler); + ~FirstDeviceBluetoothChooser() override; + + // BluetoothChooser: + void SetAdapterPresence(AdapterPresence presence) override; + void ShowDiscoveryState(DiscoveryState state) override; + void AddDevice(const std::string& device_id, + const base::string16& device_name) override; + void RemoveDevice(const std::string& device_id) override {} + + private: + EventHandler event_handler_; + + DISALLOW_COPY_AND_ASSIGN(FirstDeviceBluetoothChooser); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BLUETOOTH_FIRST_DEVICE_BLUETOOTH_CHOOSER_H_ diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 0f0d0c2..1553008 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -85,6 +85,8 @@ 'public/browser/background_tracing_config.h', 'public/browser/background_tracing_manager.h', 'public/browser/blob_handle.h', + 'public/browser/bluetooth_chooser.cc', + 'public/browser/bluetooth_chooser.h', 'public/browser/browser_accessibility_state.h', 'public/browser/browser_child_process_host.h', 'public/browser/browser_child_process_host_delegate.cc', @@ -484,6 +486,8 @@ 'browser/bluetooth/bluetooth_dispatcher_host.h', 'browser/bluetooth/bluetooth_metrics.cc', 'browser/bluetooth/bluetooth_metrics.h', + 'browser/bluetooth/first_device_bluetooth_chooser.cc', + 'browser/bluetooth/first_device_bluetooth_chooser.h', 'browser/bootstrap_sandbox_mac.cc', 'browser/bootstrap_sandbox_mac.h', 'browser/browser_child_process_host_impl.cc', diff --git a/content/public/browser/bluetooth_chooser.cc b/content/public/browser/bluetooth_chooser.cc new file mode 100644 index 0000000..40d4590 --- /dev/null +++ b/content/public/browser/bluetooth_chooser.cc @@ -0,0 +1,11 @@ +// Copyright 2015 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 "content/public/browser/bluetooth_chooser.h" + +namespace content { + +BluetoothChooser::~BluetoothChooser() {} + +} // namespace content diff --git a/content/public/browser/bluetooth_chooser.h b/content/public/browser/bluetooth_chooser.h new file mode 100644 index 0000000..07cd51e --- /dev/null +++ b/content/public/browser/bluetooth_chooser.h @@ -0,0 +1,66 @@ +// Copyright 2015 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 CONTENT_PUBLIC_BROWSER_BLUETOOTH_CHOOSER_H_ +#define CONTENT_PUBLIC_BROWSER_BLUETOOTH_CHOOSER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/strings/string16.h" +#include "content/common/content_export.h" + +namespace content { + +// Represents a way to ask the user to select a Bluetooth device from a list of +// options. +class CONTENT_EXPORT BluetoothChooser { + public: + enum class Event { + // The user cancelled the chooser instead of selecting a device. + CANCELLED, + // The user selected device |opt_device_id|. + SELECTED, + + // As the dialog implementations grow more user-visible buttons and knobs, + // we'll add enumerators here to support them. + }; + + // Chooser implementations are constructed with an |EventHandler| and report + // user interaction with the chooser through it. |opt_device_id| is an empty + // string except for Event::SELECTED. + // + // The EventHandler won't be called after the chooser object is destroyed. + // + // After the EventHandler is called with Event::CANCELLED or Event::SELECTED, + // it won't be called again, and users must not call any more BluetoothChooser + // methods. + typedef base::Callback<void(Event, const std::string& opt_device_id)> + EventHandler; + + BluetoothChooser() {} + virtual ~BluetoothChooser(); + + // Lets the chooser tell the user the state of the Bluetooth adapter. This + // defaults to POWERED_ON. + enum class AdapterPresence { ABSENT, POWERED_OFF, POWERED_ON }; + virtual void SetAdapterPresence(AdapterPresence presence) {} + + // Lets the chooser tell the user whether discovery is happening. This + // defaults to DISCOVERING. + enum class DiscoveryState { FAILED_TO_START, DISCOVERING, IDLE }; + virtual void ShowDiscoveryState(DiscoveryState state) {} + + // Shows a new device in the chooser. + virtual void AddDevice(const std::string& device_id, + const base::string16& device_name) {} + + // Tells the chooser that a device is no longer available. The chooser should + // not call DeviceSelected() for a device that's been removed. + virtual void RemoveDevice(const std::string& device_id) {} +}; + +} // namespace content + +#endif // CONTENT_PUBLIC_BROWSER_BLUETOOTH_CHOOSER_H_ diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc index 016097b..6f9b32e 100644 --- a/content/public/browser/web_contents_delegate.cc +++ b/content/public/browser/web_contents_delegate.cc @@ -147,6 +147,13 @@ JavaScriptDialogManager* WebContentsDelegate::GetJavaScriptDialogManager( return nullptr; } +scoped_ptr<BluetoothChooser> WebContentsDelegate::RunBluetoothChooser( + WebContents* web_contents, + const BluetoothChooser::EventHandler& event_handler, + const GURL& origin) { + return nullptr; +} + bool WebContentsDelegate::EmbedsFullscreenWidget() const { return false; } diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h index 685c1e6..5f17bc9 100644 --- a/content/public/browser/web_contents_delegate.h +++ b/content/public/browser/web_contents_delegate.h @@ -12,6 +12,7 @@ #include "base/callback.h" #include "base/strings/string16.h" #include "content/common/content_export.h" +#include "content/public/browser/bluetooth_chooser.h" #include "content/public/browser/invalidate_type.h" #include "content/public/browser/navigation_type.h" #include "content/public/common/media_stream_request.h" @@ -342,6 +343,13 @@ class CONTENT_EXPORT WebContentsDelegate { int request_id, const base::FilePath& path) {} + // Shows a chooser for the user to select a nearby Bluetooth device. The + // observer must live at least as long as the returned chooser object. + virtual scoped_ptr<BluetoothChooser> RunBluetoothChooser( + WebContents* web_contents, + const BluetoothChooser::EventHandler& event_handler, + const GURL& origin); + // Returns true if the delegate will embed a WebContents-owned fullscreen // render widget. In this case, the delegate may access the widget by calling // WebContents::GetFullscreenRenderWidgetHostView(). If false is returned, diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index b58ed81..2745519 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -52957,7 +52957,7 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <int value="90" label="BDH_INVALID_WRITE_VALUE_LENGTH"/> <int value="91" label="WC_MEMORY_CACHE_RESOURCE_BAD_SECURITY_INFO"/> <int value="92" label="WC_RENDERER_DID_NAVIGATE_BAD_SECURITY_INFO"/> - <int value="93" label="BDH_DUPLICATE_REQUEST_DEVICE_ID"/> + <int value="93" label="OBSOLETE_BDH_DUPLICATE_REQUEST_DEVICE_ID"/> <int value="94" label="CSDH_INVALID_ORIGIN"/> <int value="95" label="RDH_ILLEGAL_ORIGIN"/> </enum> @@ -73249,11 +73249,14 @@ To add a new entry, add it with any value and run test to compute valid value. <int value="0" label="Success"/> <int value="1" label="No Bluetooth adapter"/> <int value="2" label="No RenderFrameHost for message source"/> - <int value="3" label="Discovery start failed"/> - <int value="4" label="Discovery stop failed"/> - <int value="5" label="No matching devices found"/> + <int value="3" label="(Obsolete) Discovery start failed"/> + <int value="4" label="(Obsolete) Discovery stop failed"/> + <int value="5" label="(Obsolete) No matching devices found"/> <int value="6" label="Bluetooth Adapter not present"/> - <int value="7" label="Bluetooth Adapter was off"/> + <int value="7" label="(Obsolete) Bluetooth Adapter was off"/> + <int value="8" + label="Chosen device vanished between discovery and user selection"/> + <int value="9" label="Chooser cancelled"/> </enum> <enum name="WebFontCacheHit" type="int"> |