summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjyasskin <jyasskin@chromium.org>2015-08-25 21:46:47 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-26 04:47:21 +0000
commit5e7aec4faea108b6e64ee6a4712e77ca5b43e3c7 (patch)
treeb4ac0cc748e3564d72428c7c628bb786b7377d98
parent56c58615524d8ad4bba0423c7efb05cf4e2233c2 (diff)
downloadchromium_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.h2
-rw-r--r--content/browser/bluetooth/bluetooth_dispatcher_host.cc381
-rw-r--r--content/browser/bluetooth/bluetooth_dispatcher_host.h44
-rw-r--r--content/browser/bluetooth/bluetooth_metrics.h10
-rw-r--r--content/browser/bluetooth/first_device_bluetooth_chooser.cc46
-rw-r--r--content/browser/bluetooth/first_device_bluetooth_chooser.h39
-rw-r--r--content/content_browser.gypi4
-rw-r--r--content/public/browser/bluetooth_chooser.cc11
-rw-r--r--content/public/browser/bluetooth_chooser.h66
-rw-r--r--content/public/browser/web_contents_delegate.cc7
-rw-r--r--content/public/browser/web_contents_delegate.h8
-rw-r--r--tools/metrics/histograms/histograms.xml13
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">