Commit 366d988e authored by Stephen Lanham's avatar Stephen Lanham Committed by Commit Bot

[Chromecast] Add CastWebView::RunBluetoothChooser().

This method allows the CastWebView to further delegate this call from
WebContents to the owner of this CastWebView (usually a Cast Activity).
WebBluetooth is disabled for a particular CastWebView unless this
method is overriden by the delegate.

This also adds an implementation of content::BluetoothChooser which
selects devices based on approved access from a remote entity (usually
the Activity host).

BUG=827672

Bug: b/75967216
Test: test app
Change-Id: I7915583012be4abf41b426c789ef40e8714203ad
Reviewed-on: https://chromium-review.googlesource.com/1011417
Commit-Queue: Stephen Lanham <slan@chromium.org>
Reviewed-by: default avatarBailey Forrest <bcf@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550828}
parent 24880023
...@@ -17,6 +17,8 @@ cast_source_set("browser") { ...@@ -17,6 +17,8 @@ cast_source_set("browser") {
sources = [ sources = [
"application_media_capabilities.cc", "application_media_capabilities.cc",
"application_media_capabilities.h", "application_media_capabilities.h",
"bluetooth/cast_bluetooth_chooser.cc",
"bluetooth/cast_bluetooth_chooser.h",
"cast_browser_context.cc", "cast_browser_context.cc",
"cast_browser_context.h", "cast_browser_context.h",
"cast_browser_main_parts.cc", "cast_browser_main_parts.cc",
...@@ -99,6 +101,7 @@ cast_source_set("browser") { ...@@ -99,6 +101,7 @@ cast_source_set("browser") {
"//chromecast/base:cast_sys_info", "//chromecast/base:cast_sys_info",
"//chromecast/base:cast_version", "//chromecast/base:cast_version",
"//chromecast/browser:resources", "//chromecast/browser:resources",
"//chromecast/browser/bluetooth/public/interfaces",
"//chromecast/common", "//chromecast/common",
"//chromecast/common:interfaces", "//chromecast/common:interfaces",
"//chromecast/common/media", "//chromecast/common/media",
...@@ -367,6 +370,7 @@ cast_source_set("unittests") { ...@@ -367,6 +370,7 @@ cast_source_set("unittests") {
testonly = true testonly = true
sources = [ sources = [
"bluetooth/cast_bluetooth_chooser_unittest.cc",
"cast_media_blocker_unittest.cc", "cast_media_blocker_unittest.cc",
"cast_touch_device_manager_unittest.cc", "cast_touch_device_manager_unittest.cc",
"devtools/cast_devtools_manager_delegate_unittest.cc", "devtools/cast_devtools_manager_delegate_unittest.cc",
......
// Copyright 2018 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 "chromecast/browser/bluetooth/cast_bluetooth_chooser.h"
namespace chromecast {
CastBluetoothChooser::CastBluetoothChooser(
content::BluetoothChooser::EventHandler event_handler,
mojom::BluetoothDeviceAccessProviderPtr provider)
: event_handler_(std::move(event_handler)), binding_(this) {
DCHECK(event_handler_);
mojom::BluetoothDeviceAccessProviderClientPtr client;
binding_.Bind(mojo::MakeRequest(&client));
binding_.set_connection_error_handler(base::BindOnce(
&CastBluetoothChooser::OnClientConnectionError, base::Unretained(this)));
provider->RequestDeviceAccess(std::move(client));
}
CastBluetoothChooser::~CastBluetoothChooser() = default;
void CastBluetoothChooser::GrantAccess(const std::string& address) {
DCHECK(event_handler_);
if (all_devices_approved_) {
LOG(WARNING) << __func__ << " called after access granted to all devices!";
return;
}
if (available_devices_.find(address) != available_devices_.end()) {
RunEventHandlerAndResetBinding(Event::SELECTED, address);
return;
}
approved_devices_.insert(address);
}
void CastBluetoothChooser::GrantAccessToAllDevices() {
DCHECK(event_handler_);
all_devices_approved_ = true;
if (!available_devices_.empty()) {
RunEventHandlerAndResetBinding(Event::SELECTED,
*available_devices_.begin());
}
}
void CastBluetoothChooser::AddOrUpdateDevice(const std::string& device_id,
bool should_update_name,
const base::string16& device_name,
bool is_gatt_connected,
bool is_paired,
int signal_strength_level) {
DCHECK(event_handler_);
// Note: |device_id| is just a canonical Bluetooth address.
if (all_devices_approved_ ||
approved_devices_.find(device_id) != approved_devices_.end()) {
RunEventHandlerAndResetBinding(Event::SELECTED, device_id);
return;
}
available_devices_.insert(device_id);
}
void CastBluetoothChooser::RunEventHandlerAndResetBinding(
content::BluetoothChooser::Event event,
std::string address) {
DCHECK(event_handler_);
std::move(event_handler_).Run(event, std::move(address));
binding_.Close();
}
void CastBluetoothChooser::OnClientConnectionError() {
// If the DeviceAccessProvider has granted access to all devices, it may
// tear down the client immediately. In this case, do not run the event
// handler, as we may have not had the opportunity to select a device.
if (!all_devices_approved_ && event_handler_) {
RunEventHandlerAndResetBinding(Event::CANCELLED, "");
}
}
} // namespace chromecast
// Copyright 2018 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 CHROMECAST_BROWSER_BLUETOOTH_CAST_BLUETOOTH_CHOOSER_H_
#define CHROMECAST_BROWSER_BLUETOOTH_CAST_BLUETOOTH_CHOOSER_H_
#include <string>
#include <unordered_set>
#include "chromecast/browser/bluetooth/public/interfaces/web_bluetooth.mojom.h"
#include "content/public/browser/bluetooth_chooser.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h"
namespace chromecast {
// This class requests access from a remote BluetoothDeviceAccessProvider
// implemented by the Activity's host. The host will update this client with
// whitelisted devices via GrantAccess(). Meanwhile, the WebBluetooth stack will
// add devices that match the application's filter via AddOrUpdateDevice(). The
// first device which matches both will be selected.
class CastBluetoothChooser : public content::BluetoothChooser,
public mojom::BluetoothDeviceAccessProviderClient {
public:
// |event_handler| is called when an approved device is discovered, or when
// the |provider| destroys the connection to this client. |this| may destroy
// |provider| immediately after requesting access.
CastBluetoothChooser(content::BluetoothChooser::EventHandler event_handler,
mojom::BluetoothDeviceAccessProviderPtr provider);
~CastBluetoothChooser() override;
private:
// mojom::BluetoothDeviceAccessProviderClient implementation:
void GrantAccess(const std::string& address) override;
void GrantAccessToAllDevices() override;
// content::BluetoothChooser implementation:
void AddOrUpdateDevice(const std::string& device_id,
bool should_update_name,
const base::string16& device_name,
bool is_gatt_connected,
bool is_paired,
int signal_strength_level) override;
// Runs the event_handler and resets the client binding. After this is called,
// this class should not be used.
void RunEventHandlerAndResetBinding(content::BluetoothChooser::Event event,
std::string address);
// Called when the remote connection held by |binding_| is torn down.
void OnClientConnectionError();
content::BluetoothChooser::EventHandler event_handler_;
mojo::Binding<mojom::BluetoothDeviceAccessProviderClient> binding_;
std::unordered_set<std::string> available_devices_;
std::unordered_set<std::string> approved_devices_;
bool all_devices_approved_ = false;
DISALLOW_COPY_AND_ASSIGN(CastBluetoothChooser);
};
} // namespace chromecast
#endif // CHROMECAST_BROWSER_BLUETOOTH_CAST_BLUETOOTH_CHOOSER_H_
\ No newline at end of file
// Copyright 2018 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 "chromecast/browser/bluetooth/cast_bluetooth_chooser.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace {
class SimpleDeviceAccessProvider : public mojom::BluetoothDeviceAccessProvider {
public:
SimpleDeviceAccessProvider() = default;
~SimpleDeviceAccessProvider() override = default;
// mojom::BluetoothDeviceAccessProvider implementation:
void RequestDeviceAccess(
mojom::BluetoothDeviceAccessProviderClientPtr client) override {
DCHECK(!client_);
client.set_connection_error_handler(connection_closed_.Get());
for (const auto& address : approved_devices_)
client->GrantAccess(address);
client_ = std::move(client);
}
mojom::BluetoothDeviceAccessProviderClientPtr& client() { return client_; }
base::MockCallback<base::OnceClosure>& connection_closed() {
return connection_closed_;
}
std::vector<std::string>& approved_devices() { return approved_devices_; }
private:
mojom::BluetoothDeviceAccessProviderClientPtr client_;
base::MockCallback<base::OnceClosure> connection_closed_;
std::vector<std::string> approved_devices_;
DISALLOW_COPY_AND_ASSIGN(SimpleDeviceAccessProvider);
};
} // namespace
using testing::AnyOf;
class CastBluetoothChooserTest : public testing::Test {
public:
CastBluetoothChooserTest() : provider_binding_(&provider_) {
mojom::BluetoothDeviceAccessProviderPtr provider;
provider_binding_.Bind(mojo::MakeRequest(&provider));
cast_bluetooth_chooser_ = std::make_unique<CastBluetoothChooser>(
handler_.Get(), std::move(provider));
scoped_task_environment_.RunUntilIdle();
}
~CastBluetoothChooserTest() override = default;
void AddDeviceToChooser(const std::string& address) {
chooser().AddOrUpdateDevice(address, false, base::string16(), false, false,
0);
}
SimpleDeviceAccessProvider& provider() { return provider_; }
content::BluetoothChooser& chooser() { return *cast_bluetooth_chooser_; }
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::MockCallback<content::BluetoothChooser::EventHandler> handler_;
private:
SimpleDeviceAccessProvider provider_;
mojo::Binding<mojom::BluetoothDeviceAccessProvider> provider_binding_;
std::unique_ptr<CastBluetoothChooser> cast_bluetooth_chooser_;
DISALLOW_COPY_AND_ASSIGN(CastBluetoothChooserTest);
};
TEST_F(CastBluetoothChooserTest, GrantAccessBeforeDeviceAvailable) {
// No devices have been made available to |chooser| yet. Grant it access to a
// device. |handler| should not run yet.
EXPECT_TRUE(provider().client());
provider().client()->GrantAccess("aa:bb:cc:dd:ee:ff");
scoped_task_environment_.RunUntilIdle();
// Make some unapproved devices available. |handler| should not run yet.
AddDeviceToChooser("11:22:33:44:55:66");
AddDeviceToChooser("99:88:77:66:55:44");
// Now make the approved device available. |handler| should be called.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::SELECTED,
"aa:bb:cc:dd:ee:ff"));
EXPECT_CALL(provider().connection_closed(), Run());
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
scoped_task_environment_.RunUntilIdle();
}
TEST_F(CastBluetoothChooserTest, DiscoverDeviceBeforeAccessGranted) {
// Make some devices available before access is granted. |handler| should not
// run yet.
AddDeviceToChooser("11:22:33:44:55:66");
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
AddDeviceToChooser("00:00:00:11:00:00");
AddDeviceToChooser("99:88:77:66:55:44");
// Now approve one of those devices. |handler| should run.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::SELECTED,
"00:00:00:11:00:00"));
EXPECT_CALL(provider().connection_closed(), Run());
provider().client()->GrantAccess("00:00:00:11:00:00");
scoped_task_environment_.RunUntilIdle();
}
TEST_F(CastBluetoothChooserTest, GrantAccessToAllDevicesBeforeDiscovery) {
// Grant access to all devices. |handler| should not run until the first
// device is made available.
provider().client()->GrantAccessToAllDevices();
scoped_task_environment_.RunUntilIdle();
// Now make the some device available. |handler| should be called.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::SELECTED,
"aa:bb:cc:dd:ee:ff"));
EXPECT_CALL(provider().connection_closed(), Run());
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
scoped_task_environment_.RunUntilIdle();
}
TEST_F(CastBluetoothChooserTest, GrantAccessToAllDevicesAfterDiscovery) {
// Make some devices available before access is granted. |handler| should not
// run yet.
AddDeviceToChooser("11:22:33:44:55:66");
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
AddDeviceToChooser("00:00:00:11:00:00");
// Now grant access to all devices. |handler| should be called with one of the
// available devices.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::SELECTED,
AnyOf("11:22:33:44:55:66", "aa:bb:cc:dd:ee:ff",
"00:00:00:11:00:00")));
EXPECT_CALL(provider().connection_closed(), Run());
provider().client()->GrantAccessToAllDevices();
scoped_task_environment_.RunUntilIdle();
}
TEST_F(CastBluetoothChooserTest, TearDownClientAfterAllAccessGranted) {
// Grant access to all devices. |handler| should not run until the first
// device is made available.
provider().client()->GrantAccessToAllDevices();
scoped_task_environment_.RunUntilIdle();
// Tear down the client. Now that it has granted access to the client, it does
// not need to keep a reference to it. However, the chooser should stay alive
// and wait for devices to be made available.
provider().client().reset();
EXPECT_FALSE(provider().client());
scoped_task_environment_.RunUntilIdle();
// As soon as a device is available, run the handler.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::SELECTED,
"aa:bb:cc:dd:ee:ff"));
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
}
TEST_F(CastBluetoothChooserTest, TearDownClientBeforeApprovedDeviceDiscovered) {
// Make some devices available before access is granted. |handler| should not
// run yet.
AddDeviceToChooser("11:22:33:44:55:66");
AddDeviceToChooser("aa:bb:cc:dd:ee:ff");
// Tear the client down before any access is granted. |handler| should run,
// but with Event::CANCELLED.
EXPECT_CALL(handler_, Run(content::BluetoothChooser::Event::CANCELLED, ""));
provider().client().reset();
EXPECT_FALSE(provider().client());
scoped_task_environment_.RunUntilIdle();
}
} // namespace chromecast
\ No newline at end of file
# Copyright 2018 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.
import("//mojo/public/tools/bindings/mojom.gni")
# TODO(slan): This likely falls in the class of interfaces that are provided by
# the browser. We should consider putting these in a common location.
mojom("interfaces") {
sources = [
"web_bluetooth.mojom",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2018 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.
module chromecast.mojom;
// When WebBluetooth is enabled for a Cast Activity, this interface must be
// provided by the Activity's host, which may be running in a remote service.
// This interface is responsible for granting the Activity access to Bluetooth
// devices.
interface BluetoothDeviceAccessProvider {
// Request that the host begin granting |client| access to approved devices.
RequestDeviceAccess(BluetoothDeviceAccessProviderClient client);
};
// The client for BluetoothDeviceAccessProvider, provided by the Activity,
// and passed to the host via RequestDeviceAccess().
interface BluetoothDeviceAccessProviderClient {
// Grant the Activity access to the device at |address|. This is a Bluetooth
// mac address in canonical format. This method may be invoked multiple times,
// or zero times, during this object's lifetime.
GrantAccess(string address);
// Grant the Activity access to any device discovered by the WebBluetooth
// stack. GrantAccess() will not be called on this interface after this method
// is called.
GrantAccessToAllDevices();
};
\ No newline at end of file
...@@ -6,6 +6,13 @@ ...@@ -6,6 +6,13 @@
namespace chromecast { namespace chromecast {
std::unique_ptr<content::BluetoothChooser>
CastWebView::Delegate::RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler) {
return nullptr;
}
CastWebView::CastWebView() {} CastWebView::CastWebView() {}
CastWebView::~CastWebView() { CastWebView::~CastWebView() {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/time/time.h" #include "base/time/time.h"
#include "chromecast/browser/cast_content_window.h" #include "chromecast/browser/cast_content_window.h"
#include "chromecast/graphics/cast_window_manager.h" #include "chromecast/graphics/cast_window_manager.h"
#include "content/public/browser/bluetooth_chooser.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
namespace chromecast { namespace chromecast {
...@@ -45,6 +46,15 @@ class CastWebView { ...@@ -45,6 +46,15 @@ class CastWebView {
const base::string16& message, const base::string16& message,
int32_t line_no, int32_t line_no,
const base::string16& source_id) = 0; const base::string16& source_id) = 0;
// Invoked by CastWebView when WebContentsDelegate::RunBluetoothChooser is
// called. Returns a BluetoothChooser, a class used to solicit bluetooth
// device selection from the user for WebBluetooth applications. If a
// delegate does not provide an implementation, WebBluetooth will not be
// supported for that CastWebView.
virtual std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler);
}; };
// Observer interface for tracking CastWebView lifetime. // Observer interface for tracking CastWebView lifetime.
......
...@@ -269,6 +269,16 @@ void CastWebViewDefault::RequestMediaAccessPermission( ...@@ -269,6 +269,16 @@ void CastWebViewDefault::RequestMediaAccessPermission(
std::unique_ptr<content::MediaStreamUI>()); std::unique_ptr<content::MediaStreamUI>());
} }
std::unique_ptr<content::BluetoothChooser>
CastWebViewDefault::RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler) {
auto chooser = delegate_->RunBluetoothChooser(frame, event_handler);
return chooser
? std::move(chooser)
: WebContentsDelegate::RunBluetoothChooser(frame, event_handler);
}
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> base::android::ScopedJavaLocalRef<jobject>
CastWebViewDefault::GetContentVideoViewEmbedder() { CastWebViewDefault::GetContentVideoViewEmbedder() {
......
...@@ -94,6 +94,9 @@ class CastWebViewDefault : public CastWebView, ...@@ -94,6 +94,9 @@ class CastWebViewDefault : public CastWebView,
content::WebContents* web_contents, content::WebContents* web_contents,
const content::MediaStreamRequest& request, const content::MediaStreamRequest& request,
const content::MediaResponseCallback& callback) override; const content::MediaResponseCallback& callback) override;
std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler) override;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> GetContentVideoViewEmbedder() base::android::ScopedJavaLocalRef<jobject> GetContentVideoViewEmbedder()
override; override;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment