Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1122)

Unified Diff: content/browser/bluetooth/bluetooth_dispatcher_host.cc

Issue 1286063002: Add a path for content/ to open and control a Bluetooth chooser dialog. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@lkcr
Patch Set: Fix comment wrapping. Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 c6c49b127592f10e6292f9c8a5316e947bff7cb8..af5c8f4360970ab1e3a60e92ac0804400da473c3 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) {}
- std::vector<BluetoothScanFilter> filters;
- std::vector<BluetoothUUID> optional_services;
+ void AddFilteredDevice(const device::BluetoothDevice& device) {
+ 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(
@@ -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;
}
- 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;
+ }
+
+ // 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();
+
+ // 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::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->ShowDiscoveryState(
+ 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);
- Send(new BluetoothMsg_RequestDeviceError(
- thread_id, request_id, WebBluetoothError::DiscoverySessionStopFailed));
- request_device_sessions_.erase(std::make_pair(thread_id, request_id));
+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;
+ }
+}
+
+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(
« no previous file with comments | « content/browser/bluetooth/bluetooth_dispatcher_host.h ('k') | content/browser/bluetooth/bluetooth_metrics.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698