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

Side by Side Diff: content/browser/bluetooth/bluetooth_device_provider.cc

Issue 1922923002: bluetooth: Move requestDevice to mojo (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@bluetooth-separate-tests-request-device
Patch Set: Remove debug log Created 4 years, 7 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/bluetooth/bluetooth_device_provider.h"
6
7 #include <set>
8 #include <string>
9 #include <unordered_set>
10
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/browser/bluetooth/bluetooth_blacklist.h"
16 #include "content/browser/bluetooth/bluetooth_metrics.h"
17 #include "content/browser/bluetooth/first_device_bluetooth_chooser.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/browser/web_contents_delegate.h"
23 #include "device/bluetooth/bluetooth_adapter.h"
24 #include "device/bluetooth/bluetooth_discovery_session.h"
25
26 namespace content {
27
28 namespace {
29 constexpr size_t kMaxLengthForDeviceName =
30 29; // max length of device name in filter.
31
32 void LogRequestDeviceOptions(
33 const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
34 VLOG(1) << "requestDevice called with the following filters: ";
35 int i = 0;
36 for (const auto& filter : options->filters) {
37 VLOG(1) << "Filter #" << ++i;
38 if (!filter->name.is_null())
39 VLOG(1) << "Name: " << filter->name;
40
41 if (!filter->name_prefix.is_null())
42 VLOG(1) << "Name Prefix: " << filter->name_prefix;
43
44 if (!filter->services.is_null()) {
45 VLOG(1) << "Services: ";
46 VLOG(1) << "\t[";
47 for (const auto& service : filter->services)
48 VLOG(1) << "\t\t" << service;
49 VLOG(1) << "\t]";
50 }
51 }
52 }
53
54 bool IsEmptyOrInvalidFilter(
55 const blink::mojom::WebBluetoothScanFilterPtr& filter) {
56 return (filter->name.is_null() && filter->name_prefix.is_null() &&
Jeffrey Yasskin 2016/05/13 04:41:58 I'd rather a series of 'if' statements, but this i
ortuno 2016/05/13 20:11:17 Changed to ifs.
57 filter->services.is_null()) ||
58 (!filter->name.is_null() &&
59 filter->name.size() > kMaxLengthForDeviceName) ||
60 (!filter->name_prefix.is_null() &&
61 filter->name_prefix.size() > kMaxLengthForDeviceName);
62 }
63
64 bool HasEmptyOrInvalidFilter(
65 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
66 return filters.empty()
67 ? true
68 : filters.end() != std::find_if(filters.begin(), filters.end(),
69 IsEmptyOrInvalidFilter);
70 }
71
72 bool MatchesFilter(const device::BluetoothDevice& device,
73 const blink::mojom::WebBluetoothScanFilterPtr& filter) {
74 DCHECK(!IsEmptyOrInvalidFilter(filter));
75
76 const std::string device_name = base::UTF16ToUTF8(device.GetName());
77
78 if (!filter->name.is_null() && (device_name != filter->name)) {
79 return false;
80 }
81
82 if (!filter->name_prefix.is_null() &&
83 (!base::StartsWith(device_name, filter->name_prefix.get(),
84 base::CompareCase::SENSITIVE))) {
85 return false;
86 }
87
88 if (!filter->services.is_null()) {
89 const auto& device_uuid_list = device.GetUUIDs();
90 const std::set<device::BluetoothUUID> device_uuids(device_uuid_list.begin(),
91 device_uuid_list.end());
92 for (const auto& service : filter->services) {
93 if (!ContainsKey(device_uuids, device::BluetoothUUID(service))) {
94 return false;
95 }
96 }
97 }
98
99 return true;
100 }
101
102 bool MatchesFilters(
103 const device::BluetoothDevice& device,
104 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
105 DCHECK(!HasEmptyOrInvalidFilter(filters));
106 for (const auto& filter : filters) {
107 if (MatchesFilter(device, filter)) {
108 return true;
109 }
110 }
111 return false;
112 }
113
114 std::unique_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter(
115 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
116 std::unordered_set<std::string> services;
117 for (const auto& filter : filters) {
118 for (const std::string& service : filter->services) {
119 services.insert(service);
120 }
121 }
122 std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter(
123 new device::BluetoothDiscoveryFilter(
Jeffrey Yasskin 2016/05/13 04:41:59 This can now be auto discovery_filter = base::M
ortuno 2016/05/13 20:11:17 Nice. Done.
124 device::BluetoothDiscoveryFilter::TRANSPORT_DUAL));
125 for (const std::string& service : services) {
126 discovery_filter->AddUUID(device::BluetoothUUID(service));
127 }
128 return discovery_filter;
129 }
130
131 void StopDiscoverySession(
132 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
133 // Nothing goes wrong if the discovery session fails to stop, and we don't
134 // need to wait for it before letting the user's script proceed, so we ignore
135 // the results here.
136 discovery_session->Stop(base::Bind(&base::DoNothing),
137 base::Bind(&base::DoNothing));
138 }
139
140 UMARequestDeviceOutcome OutcomeFromChooserEvent(BluetoothChooser::Event event) {
141 switch (event) {
142 case BluetoothChooser::Event::DENIED_PERMISSION:
143 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION;
144 case BluetoothChooser::Event::CANCELLED:
145 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED;
146 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP:
147 return UMARequestDeviceOutcome::BLUETOOTH_OVERVIEW_HELP_LINK_PRESSED;
148 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP:
149 return UMARequestDeviceOutcome::ADAPTER_OFF_HELP_LINK_PRESSED;
150 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP:
151 return UMARequestDeviceOutcome::NEED_LOCATION_HELP_LINK_PRESSED;
152 case BluetoothChooser::Event::SELECTED:
153 // We can't know if we are going to send a success message yet because
154 // the device could have vanished. This event should be histogramed
155 // manually after checking if the device is still around.
156 NOTREACHED();
157 return UMARequestDeviceOutcome::SUCCESS;
158 case BluetoothChooser::Event::RESCAN:
159 // Rescanning doesn't result in a IPC message for the request being sent
160 // so no need to histogram it.
161 NOTREACHED();
162 return UMARequestDeviceOutcome::SUCCESS;
163 }
164 NOTREACHED();
165 return UMARequestDeviceOutcome::SUCCESS;
166 }
167
168 } // namespace
169
170 BluetoothDeviceProvider::BluetoothDeviceProvider(
171 RenderFrameHost* render_frame_host,
172 device::BluetoothAdapter* adapter,
173 int scan_duration)
174 : adapter_(adapter),
175 render_frame_host_(render_frame_host),
176 web_contents_(WebContents::FromRenderFrameHost(render_frame_host_)),
177 discovery_session_timer_(
178 FROM_HERE,
179 // TODO(jyasskin): Add a way for tests to control the dialog
180 // directly, and change this to a reasonable discovery timeout.
181 base::TimeDelta::FromSecondsD(scan_duration),
182 base::Bind(&BluetoothDeviceProvider::StopDeviceDiscovery,
183 // base::Timer guarantees it won't call back after its
184 // destructor starts.
185 base::Unretained(this)),
186 /*is_repeating=*/false),
187 weak_ptr_factory_(this) {
188 CHECK(adapter_);
189 }
190
191 BluetoothDeviceProvider::~BluetoothDeviceProvider() {}
192
193 void BluetoothDeviceProvider::GetDevice(
194 blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
195 const SuccessCallback& success_callback,
196 const ErrorCallback& error_callback) {
197 DCHECK_CURRENTLY_ON(BrowserThread::UI);
198
199 // GetDevice should only be called once.
200 DCHECK(success_callback_.is_null());
201 DCHECK(error_callback_.is_null());
202
203 success_callback_ = success_callback;
204 error_callback_ = error_callback;
205
206 // The renderer should never send empty filters.
207 CHECK(!HasEmptyOrInvalidFilter(options->filters));
Jeffrey Yasskin 2016/05/13 04:41:59 We shouldn't CHECK that the renderer is behaving w
ortuno 2016/05/13 20:11:17 Done. This required passing in the WebBluetoothSer
208 options_ = std::move(options);
209 LogRequestDeviceOptions(options_);
210
211 // Check blacklist to reject invalid filters and adjust optional_services.
212 if (BluetoothBlacklist::Get().IsExcluded(options_->filters)) {
Jeffrey Yasskin 2016/05/13 04:41:59 The comment on this function says that the UUIDs m
ortuno 2016/05/13 20:11:17 Added a check for UUID validity to IsEmptyOrInvali
213 RecordRequestDeviceOutcome(
214 UMARequestDeviceOutcome::BLACKLISTED_SERVICE_IN_FILTER);
215 PostErrorCallback(
216 blink::mojom::WebBluetoothError::REQUEST_DEVICE_WITH_BLACKLISTED_UUID);
217 return;
218 }
219 BluetoothBlacklist::Get().RemoveExcludedUUIDs(options_.get());
220
221 const url::Origin requesting_origin =
222 render_frame_host_->GetLastCommittedOrigin();
223 const url::Origin embedding_origin =
224 web_contents_->GetMainFrame()->GetLastCommittedOrigin();
225
226 // TODO(crbug.com/518042): Enforce correctly-delegated permissions instead of
227 // matching origins. When relaxing this, take care to handle non-sandboxed
228 // unique origins.
229 if (!embedding_origin.IsSameOriginWith(requesting_origin)) {
230 PostErrorCallback(blink::mojom::WebBluetoothError::
231 REQUEST_DEVICE_FROM_CROSS_ORIGIN_IFRAME);
232 return;
233 }
234 // The above also excludes unique origins, which are not even same-origin with
235 // themselves.
236 DCHECK(!requesting_origin.unique());
237
238 if (!adapter_->IsPresent()) {
239 VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice.";
240 RecordRequestDeviceOutcome(
241 UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT);
242 PostErrorCallback(blink::mojom::WebBluetoothError::NO_BLUETOOTH_ADAPTER);
243 return;
244 }
245
246 switch (GetContentClient()->browser()->AllowWebBluetooth(
247 web_contents_->GetBrowserContext(), requesting_origin,
248 embedding_origin)) {
249 case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_POLICY: {
250 RecordRequestDeviceOutcome(
251 UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_POLICY_DISABLED);
252 PostErrorCallback(blink::mojom::WebBluetoothError::
253 CHOOSER_NOT_SHOWN_API_LOCALLY_DISABLED);
254 return;
255 }
256 case ContentBrowserClient::AllowWebBluetoothResult::
257 BLOCK_GLOBALLY_DISABLED: {
258 // Log to the developer console.
259 web_contents_->GetMainFrame()->AddMessageToConsole(
260 content::CONSOLE_MESSAGE_LEVEL_LOG,
261 "Bluetooth permission has been blocked.");
262 // Block requests.
263 RecordRequestDeviceOutcome(
264 UMARequestDeviceOutcome::BLUETOOTH_GLOBALLY_DISABLED);
265 PostErrorCallback(blink::mojom::WebBluetoothError::
266 CHOOSER_NOT_SHOWN_API_GLOBALLY_DISABLED);
267 return;
268 }
269 case ContentBrowserClient::AllowWebBluetoothResult::ALLOW:
270 break;
271 }
272
273 BluetoothChooser::EventHandler chooser_event_handler =
274 base::Bind(&BluetoothDeviceProvider::OnBluetoothChooserEvent,
275 base::Unretained(this));
276
277 if (WebContentsDelegate* delegate = web_contents_->GetDelegate()) {
278 chooser_ = delegate->RunBluetoothChooser(render_frame_host_,
279 chooser_event_handler);
280 }
281
282 if (!chooser_.get()) {
283 LOG(WARNING)
284 << "No Bluetooth chooser implementation; falling back to first device.";
285 chooser_.reset(new FirstDeviceBluetoothChooser(chooser_event_handler));
286 }
287
288 if (!chooser_->CanAskForScanningPermission()) {
289 VLOG(1) << "Closing immediately because Chooser cannot obtain permission.";
290 OnBluetoothChooserEvent(BluetoothChooser::Event::DENIED_PERMISSION,
291 "" /* device_address */);
292 return;
293 }
294
295 // Populate the initial list of devices.
296 VLOG(1) << "Populating " << adapter_->GetDevices().size()
297 << " devices in chooser.";
298 for (const device::BluetoothDevice* device : adapter_->GetDevices()) {
299 AddFilteredDevice(*device);
300 }
301
302 if (!chooser_.get()) {
303 // If the dialog's closing, no need to do any of the rest of this.
304 return;
305 }
306
307 if (!adapter_->IsPowered()) {
308 chooser_->SetAdapterPresence(
309 BluetoothChooser::AdapterPresence::POWERED_OFF);
310 return;
311 }
312
313 StartDeviceDiscovery();
314 }
315
316 void BluetoothDeviceProvider::AddFilteredDevice(
317 const device::BluetoothDevice& device) {
318 if (chooser_.get() && MatchesFilters(device, options_->filters)) {
319 VLOG(1) << "Adding device to chooser: " << device.GetAddress();
320 chooser_->AddDevice(device.GetAddress(), device.GetName());
321 }
322 }
323
324 void BluetoothDeviceProvider::AdapterPoweredChanged(bool powered) {
325 if (!powered && discovery_session_.get()) {
326 StopDiscoverySession(std::move(discovery_session_));
327 }
328
329 if (chooser_.get()) {
330 chooser_->SetAdapterPresence(
331 powered ? BluetoothChooser::AdapterPresence::POWERED_ON
332 : BluetoothChooser::AdapterPresence::POWERED_OFF);
333 }
334
335 if (!powered) {
336 discovery_session_timer_.Stop();
337 }
338 }
339
340 void BluetoothDeviceProvider::StartDeviceDiscovery() {
341 DCHECK_CURRENTLY_ON(BrowserThread::UI);
342
343 if (discovery_session_.get() && discovery_session_->IsActive()) {
344 // Already running; just increase the timeout.
345 discovery_session_timer_.Reset();
346 return;
347 }
348
349 chooser_->ShowDiscoveryState(BluetoothChooser::DiscoveryState::DISCOVERING);
350 adapter_->StartDiscoverySessionWithFilter(
351 ComputeScanFilter(options_->filters),
352 base::Bind(&BluetoothDeviceProvider::OnStartDiscoverySessionSuccess,
353 weak_ptr_factory_.GetWeakPtr()),
354 base::Bind(&BluetoothDeviceProvider::OnStartDiscoverySessionFailed,
355 weak_ptr_factory_.GetWeakPtr()));
356 }
357
358 void BluetoothDeviceProvider::StopDeviceDiscovery() {
359 DCHECK_CURRENTLY_ON(BrowserThread::UI);
360 StopDiscoverySession(std::move(discovery_session_));
361 if (chooser_) {
362 chooser_->ShowDiscoveryState(BluetoothChooser::DiscoveryState::IDLE);
363 }
364 }
365
366 void BluetoothDeviceProvider::OnStartDiscoverySessionSuccess(
367 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
368 DCHECK_CURRENTLY_ON(BrowserThread::UI);
369 VLOG(1) << "Started discovery session.";
370 if (chooser_.get()) {
371 discovery_session_ = std::move(discovery_session);
372 discovery_session_timer_.Reset();
373 } else {
374 StopDiscoverySession(std::move(discovery_session));
375 }
376 }
377
378 void BluetoothDeviceProvider::OnStartDiscoverySessionFailed() {
379 if (chooser_.get()) {
380 chooser_->ShowDiscoveryState(
381 BluetoothChooser::DiscoveryState::FAILED_TO_START);
382 }
383 }
384
385 void BluetoothDeviceProvider::OnBluetoothChooserEvent(
386 BluetoothChooser::Event event,
387 const std::string& device_address) {
388 DCHECK_CURRENTLY_ON(BrowserThread::UI);
389 // Shouldn't recieve an event from a closed chooser.
390 DCHECK(chooser_.get());
391
392 switch (event) {
393 case BluetoothChooser::Event::RESCAN:
394 StartDeviceDiscovery();
395 // No need to close the chooser so we return.
396 return;
397 case BluetoothChooser::Event::DENIED_PERMISSION:
398 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
399 PostErrorCallback(blink::mojom::WebBluetoothError::
400 CHOOSER_NOT_SHOWN_USER_DENIED_PERMISSION_TO_SCAN);
401 break;
402 case BluetoothChooser::Event::CANCELLED:
403 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
404 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
405 break;
406 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP:
407 VLOG(1) << "Overview Help link pressed.";
408 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
409 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
410 break;
411 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP:
412 VLOG(1) << "Adapter Off Help link pressed.";
413 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
414 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
415 break;
416 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP:
417 VLOG(1) << "Need Location Help link pressed.";
418 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
419 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
420 break;
421 case BluetoothChooser::Event::SELECTED:
422 PostSuccessCallback(device_address);
423 break;
424 }
425 // Close chooser.
426 chooser_.reset();
427 }
428
429 void BluetoothDeviceProvider::PostSuccessCallback(
430 const std::string& device_address) {
431 if (!base::ThreadTaskRunnerHandle::Get()->PostTask(
432 FROM_HERE, base::Bind(success_callback_, device_address))) {
433 LOG(WARNING) << "No TaskRunner.";
434 }
435 }
436
437 void BluetoothDeviceProvider::PostErrorCallback(
438 blink::mojom::WebBluetoothError error) {
439 if (!base::ThreadTaskRunnerHandle::Get()->PostTask(
440 FROM_HERE, base::Bind(error_callback_, error))) {
441 LOG(WARNING) << "No TaskRunner.";
442 }
443 }
444
445 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698