summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client.cc145
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client.h19
-rw-r--r--chrome/browser/copresence/chrome_whispernet_client_browsertest.cc155
-rw-r--r--chrome/browser/extensions/api/audio_modem/OWNERS2
-rw-r--r--chrome/browser/extensions/api/audio_modem/audio_modem_api.cc348
-rw-r--r--chrome/browser/extensions/api/audio_modem/audio_modem_api.h130
-rw-r--r--chrome/browser/extensions/api/audio_modem/audio_modem_api_unittest.cc380
-rw-r--r--chrome/browser/extensions/api/copresence_private/copresence_private_api.cc87
-rw-r--r--chrome/browser/extensions/api/copresence_private/copresence_private_api.h24
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/init.js58
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/nacl.js5
-rw-r--r--chrome/browser/resources/whispernet_proxy/js/wrapper.js88
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi2
-rw-r--r--chrome/common/extensions/api/_api_features.json4
-rw-r--r--chrome/common/extensions/api/_permission_features.json17
-rw-r--r--chrome/common/extensions/api/audio_modem.idl86
-rw-r--r--chrome/common/extensions/api/copresence_private.idl33
-rw-r--r--chrome/common/extensions/api/schemas.gypi1
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc1
-rw-r--r--chrome/test/BUILD.gn1
-rw-r--r--components/audio_modem.gypi2
-rw-r--r--components/audio_modem/BUILD.gn16
-rw-r--r--components/audio_modem/modem_impl.cc24
-rw-r--r--components/audio_modem/modem_impl.h8
-rw-r--r--components/audio_modem/public/audio_modem_types.h16
-rw-r--r--components/audio_modem/public/modem.h7
-rw-r--r--components/audio_modem/public/whispernet_client.h11
-rw-r--r--components/audio_modem/test/stub_modem.cc69
-rw-r--r--components/audio_modem/test/stub_modem.h51
-rw-r--r--components/audio_modem/test/stub_whispernet_client.cc14
-rw-r--r--components/audio_modem/test/stub_whispernet_client.h10
-rw-r--r--components/copresence/handlers/audio/audio_directive_handler_impl.cc11
-rw-r--r--components/copresence/handlers/audio/audio_directive_handler_unittest.cc30
-rw-r--r--extensions/browser/extension_function_histogram_value.h4
-rw-r--r--extensions/common/permissions/api_permission.h2
-rw-r--r--tools/metrics/histograms/histograms.xml4
38 files changed, 1477 insertions, 391 deletions
diff --git a/chrome/browser/copresence/chrome_whispernet_client.cc b/chrome/browser/copresence/chrome_whispernet_client.cc
index d8b4869..fea965c 100644
--- a/chrome/browser/copresence/chrome_whispernet_client.cc
+++ b/chrome/browser/copresence/chrome_whispernet_client.cc
@@ -22,8 +22,22 @@ using audio_modem::INAUDIBLE;
using audio_modem::SamplesCallback;
using audio_modem::SuccessCallback;
using audio_modem::TokensCallback;
+using audio_modem::TokenParameters;
-namespace copresence_private = extensions::api::copresence_private;
+using extensions::api::copresence_private::AudioParameters;
+using extensions::api::copresence_private::DecodeSamplesParameters;
+using extensions::api::copresence_private::EncodeTokenParameters;
+using ApiTokenParams = extensions::api::copresence_private::TokenParameters;
+
+namespace OnConfigAudio =
+ extensions::api::copresence_private::OnConfigAudio;
+namespace OnDecodeSamplesRequest =
+ extensions::api::copresence_private::OnDecodeSamplesRequest;
+namespace OnEncodeTokenRequest =
+ extensions::api::copresence_private::OnEncodeTokenRequest;
+
+using extensions::Event;
+using extensions::copresence_private::RegisterWhispernetClient;
namespace {
@@ -45,13 +59,22 @@ AudioParamData GetDefaultAudioConfig() {
return config_data;
}
+// ApiTokenParams is not copyable, so we must take it as an output argument.
+// TODO(ckehoe): Pass protos to Whispernet to avoid all these conversions.
+void ConvertTokenParams(const TokenParameters& in, ApiTokenParams* out) {
+ out->length = in.length;
+ out->crc = in.crc;
+ out->parity = in.parity;
+}
+
} // namespace
// static
const char ChromeWhispernetClient::kWhispernetProxyExtensionId[] =
"bpfmnplchembfbdgieamdodgaencleal";
-// Public:
+
+// Public functions.
ChromeWhispernetClient::ChromeWhispernetClient(
content::BrowserContext* browser_context)
@@ -59,81 +82,67 @@ ChromeWhispernetClient::ChromeWhispernetClient(
event_router_(extensions::EventRouter::Get(browser_context)),
extension_loaded_(false) {
DCHECK(browser_context_);
- DCHECK(event_router_);
}
-ChromeWhispernetClient::~ChromeWhispernetClient() {
-}
+ChromeWhispernetClient::~ChromeWhispernetClient() {}
void ChromeWhispernetClient::Initialize(
const SuccessCallback& init_callback) {
DVLOG(3) << "Initializing whispernet proxy client.";
+
init_callback_ = init_callback;
+ DCHECK(!init_callback_.is_null());
- extensions::ExtensionSystem* es =
- extensions::ExtensionSystem::Get(browser_context_);
- DCHECK(es);
- ExtensionService* service = es->extension_service();
- DCHECK(service);
- extensions::ComponentLoader* loader = service->component_loader();
+ extensions::ComponentLoader* loader =
+ extensions::ExtensionSystem::Get(browser_context_)
+ ->extension_service()->component_loader();
DCHECK(loader);
- // This callback is cancelled in Shutdown().
- extension_loaded_callback_ = base::Bind(
- &ChromeWhispernetClient::OnExtensionLoaded, base::Unretained(this));
-
if (!loader->Exists(kWhispernetProxyExtensionId)) {
DVLOG(3) << "Loading Whispernet proxy.";
loader->Add(IDR_WHISPERNET_PROXY_MANIFEST,
base::FilePath(FILE_PATH_LITERAL("whispernet_proxy")));
} else {
init_callback_.Run(true);
+ extension_loaded_ = true;
}
+ client_id_ = RegisterWhispernetClient(this);
AudioConfiguration(GetDefaultAudioConfig());
}
-void ChromeWhispernetClient::EncodeToken(const std::string& token_str,
- AudioType type) {
- copresence_private::EncodeTokenParameters params;
+void ChromeWhispernetClient::EncodeToken(
+ const std::string& token_str,
+ AudioType type,
+ const TokenParameters token_params[2]) {
+ DCHECK(type == AUDIBLE || type == INAUDIBLE);
+
+ EncodeTokenParameters params;
params.token.token = token_str;
- params.token.audible = type == AUDIBLE;
- scoped_ptr<extensions::Event> event(new extensions::Event(
- copresence_private::OnEncodeTokenRequest::kEventName,
- copresence_private::OnEncodeTokenRequest::Create(params),
- browser_context_));
+ params.token.audible = (type == AUDIBLE);
+ ConvertTokenParams(token_params[type], &params.token_params);
- SendEventIfLoaded(event.Pass());
+ SendEventIfLoaded(make_scoped_ptr(new Event(
+ OnEncodeTokenRequest::kEventName,
+ OnEncodeTokenRequest::Create(client_id_, params),
+ browser_context_)));
}
-void ChromeWhispernetClient::DecodeSamples(AudioType type,
- const std::string& samples,
- const size_t token_length[2]) {
- copresence_private::DecodeSamplesParameters params;
+void ChromeWhispernetClient::DecodeSamples(
+ AudioType type,
+ const std::string& samples,
+ const TokenParameters token_params[2]) {
+ DecodeSamplesParameters params;
params.samples.assign(samples.begin(), samples.end());
- params.decode_audible =
- type == AUDIBLE || type == BOTH;
- params.decode_inaudible =
- type == INAUDIBLE || type == BOTH;
- params.audible_token_length = token_length[AUDIBLE];
- params.inaudible_token_length = token_length[INAUDIBLE];
-
- scoped_ptr<extensions::Event> event(new extensions::Event(
- copresence_private::OnDecodeSamplesRequest::kEventName,
- copresence_private::OnDecodeSamplesRequest::Create(
- params),
- browser_context_));
-
- SendEventIfLoaded(event.Pass());
-}
+ params.decode_audible = (type == AUDIBLE || type == BOTH);
+ params.decode_inaudible = (type == INAUDIBLE || type == BOTH);
+ ConvertTokenParams(token_params[AUDIBLE], &params.audible_token_params);
+ ConvertTokenParams(token_params[INAUDIBLE], &params.inaudible_token_params);
-void ChromeWhispernetClient::DetectBroadcast() {
- scoped_ptr<extensions::Event> event(new extensions::Event(
- copresence_private::OnDetectBroadcastRequest::kEventName,
- make_scoped_ptr(new base::ListValue()),
- browser_context_));
-
- SendEventIfLoaded(event.Pass());
+ SendEventIfLoaded(make_scoped_ptr(new Event(
+ OnDecodeSamplesRequest::kEventName,
+ OnDecodeSamplesRequest::Create(client_id_, params),
+ browser_context_)));
}
void ChromeWhispernetClient::RegisterTokensCallback(
@@ -146,11 +155,6 @@ void ChromeWhispernetClient::RegisterSamplesCallback(
samples_callback_ = samples_callback;
}
-void ChromeWhispernetClient::RegisterDetectBroadcastCallback(
- const SuccessCallback& db_callback) {
- db_callback_ = db_callback;
-}
-
TokensCallback ChromeWhispernetClient::GetTokensCallback() {
return tokens_callback_;
}
@@ -159,18 +163,16 @@ SamplesCallback ChromeWhispernetClient::GetSamplesCallback() {
return samples_callback_;
}
-SuccessCallback ChromeWhispernetClient::GetDetectBroadcastCallback() {
- return db_callback_;
-}
-
SuccessCallback ChromeWhispernetClient::GetInitializedCallback() {
- return extension_loaded_callback_;
+ return base::Bind(&ChromeWhispernetClient::OnExtensionLoaded,
+ base::Unretained(this));
}
-// Private:
+
+// Private functions.
void ChromeWhispernetClient::AudioConfiguration(const AudioParamData& params) {
- copresence_private::AudioParameters audio_params;
+ AudioParameters audio_params;
// We serialize AudioConfigData to a string and send it to the whispernet
// nacl wrapper.
@@ -178,16 +180,16 @@ void ChromeWhispernetClient::AudioConfiguration(const AudioParamData& params) {
audio_params.param_data.resize(params_size);
memcpy(vector_as_array(&audio_params.param_data), &params, params_size);
- scoped_ptr<extensions::Event> event(new extensions::Event(
- copresence_private::OnConfigAudio::kEventName,
- copresence_private::OnConfigAudio::Create(audio_params),
- browser_context_));
-
- SendEventIfLoaded(event.Pass());
+ SendEventIfLoaded(make_scoped_ptr(new Event(
+ OnConfigAudio::kEventName,
+ OnConfigAudio::Create(client_id_, audio_params),
+ browser_context_)));
}
void ChromeWhispernetClient::SendEventIfLoaded(
scoped_ptr<extensions::Event> event) {
+ DCHECK(event_router_);
+
if (extension_loaded_) {
event_router_->DispatchEventToExtension(kWhispernetProxyExtensionId,
event.Pass());
@@ -198,15 +200,16 @@ void ChromeWhispernetClient::SendEventIfLoaded(
}
void ChromeWhispernetClient::OnExtensionLoaded(bool success) {
- if (!init_callback_.is_null())
- init_callback_.Run(success);
+ DCHECK(!init_callback_.is_null());
+ init_callback_.Run(success);
DVLOG(3) << "Sending " << queued_events_.size()
<< " queued requests to whispernet";
// In this loop, ownership of each Event is passed to a scoped_ptr instead.
// Thus we can just discard the pointers at the end.
- for (extensions::Event* event : queued_events_) {
+ DCHECK(event_router_);
+ for (Event* event : queued_events_) {
event_router_->DispatchEventToExtension(kWhispernetProxyExtensionId,
make_scoped_ptr(event));
}
diff --git a/chrome/browser/copresence/chrome_whispernet_client.h b/chrome/browser/copresence/chrome_whispernet_client.h
index 74c1e7d..5548104 100644
--- a/chrome/browser/copresence/chrome_whispernet_client.h
+++ b/chrome/browser/copresence/chrome_whispernet_client.h
@@ -45,21 +45,19 @@ class ChromeWhispernetClient final : public audio_modem::WhispernetClient {
// WhispernetClient overrides:
void Initialize(const audio_modem::SuccessCallback& init_callback) override;
void EncodeToken(const std::string& token_str,
- audio_modem::AudioType type) override;
- void DecodeSamples(audio_modem::AudioType type,
- const std::string& samples,
- const size_t token_length[2]) override;
- void DetectBroadcast() override;
+ audio_modem::AudioType type,
+ const audio_modem::TokenParameters token_params[2]) override;
+ void DecodeSamples(
+ audio_modem::AudioType type,
+ const std::string& samples,
+ const audio_modem::TokenParameters token_params[2]) override;
void RegisterTokensCallback(
const audio_modem::TokensCallback& tokens_callback) override;
void RegisterSamplesCallback(
const audio_modem::SamplesCallback& samples_callback) override;
- void RegisterDetectBroadcastCallback(
- const audio_modem::SuccessCallback& db_callback) override;
audio_modem::TokensCallback GetTokensCallback() override;
audio_modem::SamplesCallback GetSamplesCallback() override;
- audio_modem::SuccessCallback GetDetectBroadcastCallback() override;
audio_modem::SuccessCallback GetInitializedCallback() override;
static const char kWhispernetProxyExtensionId[];
@@ -70,19 +68,18 @@ class ChromeWhispernetClient final : public audio_modem::WhispernetClient {
void SendEventIfLoaded(scoped_ptr<extensions::Event> event);
- // This gets called twice; once when the proxy extension loads, the second
- // time when we have initialized the proxy extension's encoder and decoder.
+ // This gets called when the proxy extension loads.
void OnExtensionLoaded(bool success);
content::BrowserContext* const browser_context_;
extensions::EventRouter* const event_router_;
+ std::string client_id_;
audio_modem::SuccessCallback extension_loaded_callback_;
audio_modem::SuccessCallback init_callback_;
audio_modem::TokensCallback tokens_callback_;
audio_modem::SamplesCallback samples_callback_;
- audio_modem::SuccessCallback db_callback_;
ScopedVector<extensions::Event> queued_events_;
bool extension_loaded_;
diff --git a/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc
index 5c1f5b8..c6a9bc7 100644
--- a/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc
+++ b/chrome/browser/copresence/chrome_whispernet_client_browsertest.cc
@@ -23,6 +23,7 @@
using audio_modem::WhispernetClient;
using audio_modem::AUDIBLE;
using audio_modem::INAUDIBLE;
+using audio_modem::TokenParameters;
namespace {
@@ -31,13 +32,7 @@ const char kSixZeros[] = "MDAwMDAw";
const char kEightZeros[] = "MDAwMDAwMDA";
const char kNineZeros[] = "MDAwMDAwMDAw";
-const size_t kTokenLengths[] = {6, 6};
-
-WhispernetClient* GetWhispernetClient(content::BrowserContext* context) {
- extensions::CopresenceService* service =
- extensions::CopresenceService::GetFactoryInstance()->Get(context);
- return service ? service->whispernet_client() : NULL;
-}
+const size_t kTokenLengths[2] = {6, 6};
// Copied from src/components/copresence/mediums/audio/audio_recorder.cc
std::string AudioBusToString(scoped_refptr<media::AudioBusRefCounted> source) {
@@ -54,29 +49,38 @@ std::string AudioBusToString(scoped_refptr<media::AudioBusRefCounted> source) {
return buffer;
}
+void GetTokenParamsForLengths(const size_t token_lengths[2],
+ TokenParameters* params) {
+ params[0].length = token_lengths[0];
+ params[1].length = token_lengths[1];
+}
+
+void IgnoreResult(bool success) {}
+
} // namespace
class ChromeWhispernetClientTest : public ExtensionBrowserTest {
protected:
ChromeWhispernetClientTest()
- : context_(NULL), expected_audible_(false), initialized_(false) {}
+ : initialized_(false), expected_audible_(false) {}
~ChromeWhispernetClientTest() override {}
void InitializeWhispernet() {
- context_ = browser()->profile();
- run_loop_.reset(new base::RunLoop());
- GetWhispernetClient(context_)->Initialize(base::Bind(
+ scoped_ptr<WhispernetClient> client(
+ new ChromeWhispernetClient(browser()->profile()));
+ client->Initialize(base::Bind(
&ChromeWhispernetClientTest::InitCallback, base::Unretained(this)));
+
+ run_loop_.reset(new base::RunLoop());
run_loop_->Run();
EXPECT_TRUE(initialized_);
}
- void EncodeTokenAndSaveSamples(bool audible, const std::string& token) {
- WhispernetClient* client = GetWhispernetClient(context_);
- ASSERT_TRUE(client);
-
+ void EncodeTokenAndSaveSamples(WhispernetClient* client,
+ bool audible,
+ const std::string& token) {
run_loop_.reset(new base::RunLoop());
client->RegisterSamplesCallback(
base::Bind(&ChromeWhispernetClientTest::SamplesCallback,
@@ -84,18 +88,17 @@ class ChromeWhispernetClientTest : public ExtensionBrowserTest {
expected_token_ = token;
expected_audible_ = audible;
- client->EncodeToken(token, audible ? AUDIBLE : INAUDIBLE);
+ TokenParameters token_params[2];
+ client->EncodeToken(token, audible ? AUDIBLE : INAUDIBLE, token_params);
run_loop_->Run();
EXPECT_GT(saved_samples_->frames(), 0);
}
- void DecodeSamplesAndVerifyToken(bool expect_audible,
+ void DecodeSamplesAndVerifyToken(WhispernetClient* client,
+ bool expect_audible,
const std::string& expected_token,
- const size_t token_length[2]) {
- WhispernetClient* client = GetWhispernetClient(context_);
- ASSERT_TRUE(client);
-
+ const TokenParameters token_params[2]) {
run_loop_.reset(new base::RunLoop());
client->RegisterTokensCallback(base::Bind(
&ChromeWhispernetClientTest::TokensCallback, base::Unretained(this)));
@@ -115,21 +118,9 @@ class ChromeWhispernetClientTest : public ExtensionBrowserTest {
saved_samples_->channel(0),
sizeof(float) * saved_samples_->frames());
- client->DecodeSamples(
- expect_audible ? AUDIBLE : INAUDIBLE,
- AudioBusToString(samples_bus), token_length);
- run_loop_->Run();
- }
-
- void DetectBroadcast() {
- WhispernetClient* client = GetWhispernetClient(context_);
- ASSERT_TRUE(client);
-
- run_loop_.reset(new base::RunLoop());
- client->RegisterDetectBroadcastCallback(
- base::Bind(&ChromeWhispernetClientTest::DetectBroadcastCallback,
- base::Unretained(this)));
- client->DetectBroadcast();
+ client->DecodeSamples(expect_audible ? AUDIBLE : INAUDIBLE,
+ AudioBusToString(samples_bus),
+ token_params);
run_loop_->Run();
}
@@ -159,77 +150,83 @@ class ChromeWhispernetClientTest : public ExtensionBrowserTest {
EXPECT_EQ(expected_audible_, tokens[0].audible);
}
- void DetectBroadcastCallback(bool success) {
- EXPECT_TRUE(success);
- ASSERT_TRUE(run_loop_);
- run_loop_->Quit();
- }
-
- private:
scoped_ptr<base::RunLoop> run_loop_;
- content::BrowserContext* context_;
+ bool initialized_;
+ private:
std::string expected_token_;
bool expected_audible_;
scoped_refptr<media::AudioBusRefCounted> saved_samples_;
- bool initialized_;
-
DISALLOW_COPY_AND_ASSIGN(ChromeWhispernetClientTest);
};
// These tests are irrelevant if NACL is disabled. See crbug.com/449198
#if defined(DISABLE_NACL)
#define MAYBE_Initialize DISABLED_Initialize
-#define MAYBE_EncodeToken DISABLED_EncodeToken
-#define MAYBE_DecodeSamples DISABLED_DecodeSamples
-#define MAYBE_DetectBroadcast DISABLED_DetectBroadcast
-#define MAYBE_Audible DISABLED_Audible
+#define MAYBE_EncodeAndDecode DISABLED_EncodeAndDecode
#define MAYBE_TokenLengths DISABLED_TokenLengths
+#define MAYBE_MultipleClients DISABLED_MultipleClients
#else
#define MAYBE_Initialize Initialize
-#define MAYBE_EncodeToken EncodeToken
-#define MAYBE_DecodeSamples DecodeSamples
-#define MAYBE_DetectBroadcast DetectBroadcast
-#define MAYBE_Audible Audible
+#define MAYBE_EncodeAndDecode EncodeAndDecode
#define MAYBE_TokenLengths TokenLengths
+#define MAYBE_MultipleClients MultipleClients
#endif
IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_Initialize) {
InitializeWhispernet();
}
-IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_EncodeToken) {
- InitializeWhispernet();
- EncodeTokenAndSaveSamples(false, kSixZeros);
-}
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_EncodeAndDecode) {
+ scoped_ptr<WhispernetClient> client(
+ new ChromeWhispernetClient(browser()->profile()));
+ client->Initialize(base::Bind(&IgnoreResult));
-IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_DecodeSamples) {
- InitializeWhispernet();
- EncodeTokenAndSaveSamples(false, kSixZeros);
- DecodeSamplesAndVerifyToken(false, kSixZeros, kTokenLengths);
-}
+ TokenParameters token_params[2];
+ GetTokenParamsForLengths(kTokenLengths, token_params);
-IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_DetectBroadcast) {
- InitializeWhispernet();
- EncodeTokenAndSaveSamples(false, kSixZeros);
- DecodeSamplesAndVerifyToken(false, kSixZeros, kTokenLengths);
- DetectBroadcast();
-}
+ EncodeTokenAndSaveSamples(client.get(), true, kSixZeros);
+ DecodeSamplesAndVerifyToken(client.get(), true, kSixZeros, token_params);
-IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_Audible) {
- InitializeWhispernet();
- EncodeTokenAndSaveSamples(true, kSixZeros);
- DecodeSamplesAndVerifyToken(true, kSixZeros, kTokenLengths);
+ EncodeTokenAndSaveSamples(client.get(), false, kSixZeros);
+ DecodeSamplesAndVerifyToken(client.get(), false, kSixZeros, token_params);
}
IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_TokenLengths) {
- InitializeWhispernet();
- size_t kLongTokenLengths[2] = {8, 9};
+ scoped_ptr<WhispernetClient> client(
+ new ChromeWhispernetClient(browser()->profile()));
+ client->Initialize(base::Bind(&IgnoreResult));
+
+ const size_t kLongTokenLengths[2] = {8, 9};
+ TokenParameters token_params[2];
+ GetTokenParamsForLengths(kLongTokenLengths, token_params);
+ EncodeTokenAndSaveSamples(client.get(), true, kEightZeros);
+ DecodeSamplesAndVerifyToken(client.get(), true, kEightZeros, token_params);
+
+ EncodeTokenAndSaveSamples(client.get(), false, kNineZeros);
+ DecodeSamplesAndVerifyToken(client.get(), false, kNineZeros, token_params);
+}
- EncodeTokenAndSaveSamples(true, kEightZeros);
- DecodeSamplesAndVerifyToken(true, kEightZeros, kLongTokenLengths);
+IN_PROC_BROWSER_TEST_F(ChromeWhispernetClientTest, MAYBE_MultipleClients) {
+ scoped_ptr<WhispernetClient> client_1(
+ new ChromeWhispernetClient(browser()->profile()));
+ scoped_ptr<WhispernetClient> client_2(
+ new ChromeWhispernetClient(browser()->profile()));
- EncodeTokenAndSaveSamples(false, kNineZeros);
- DecodeSamplesAndVerifyToken(false, kNineZeros, kLongTokenLengths);
+ TokenParameters token_params[2];
+ GetTokenParamsForLengths(kTokenLengths, token_params);
+
+ client_1->Initialize(base::Bind(&IgnoreResult));
+
+ EncodeTokenAndSaveSamples(client_1.get(), true, kSixZeros);
+ DecodeSamplesAndVerifyToken(client_1.get(), true, kSixZeros, token_params);
+
+ client_2->Initialize(base::Bind(&IgnoreResult));
+
+ EncodeTokenAndSaveSamples(client_2.get(), true, kSixZeros);
+ DecodeSamplesAndVerifyToken(client_2.get(), true, kSixZeros, token_params);
}
+
+// TODO(ckehoe): Test crc and parity
+// TODO(ckehoe): More multi-client testing
diff --git a/chrome/browser/extensions/api/audio_modem/OWNERS b/chrome/browser/extensions/api/audio_modem/OWNERS
new file mode 100644
index 0000000..9364bf7
--- /dev/null
+++ b/chrome/browser/extensions/api/audio_modem/OWNERS
@@ -0,0 +1,2 @@
+ckehoe@chromium.org
+rkc@chromium.org
diff --git a/chrome/browser/extensions/api/audio_modem/audio_modem_api.cc b/chrome/browser/extensions/api/audio_modem/audio_modem_api.cc
new file mode 100644
index 0000000..8521f63
--- /dev/null
+++ b/chrome/browser/extensions/api/audio_modem/audio_modem_api.cc
@@ -0,0 +1,348 @@
+// 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 <map>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/copresence/chrome_whispernet_client.h"
+#include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h"
+#include "chrome/common/extensions/api/audio_modem.h"
+#include "extensions/browser/event_router.h"
+
+// TODO(ckehoe): Implement transmit fail checking.
+
+namespace extensions {
+
+using api::audio_modem::AUDIOBAND_AUDIBLE;
+using api::audio_modem::AUDIOBAND_INAUDIBLE;
+using api::audio_modem::Audioband;
+using api::audio_modem::STATUS_CODERERROR;
+using api::audio_modem::STATUS_INUSE;
+using api::audio_modem::STATUS_INVALIDREQUEST;
+using api::audio_modem::STATUS_SUCCESS;
+using api::audio_modem::ReceivedToken;
+using api::audio_modem::RequestParams;
+using api::audio_modem::Status;
+
+namespace Transmit = api::audio_modem::Transmit;
+namespace StopTransmit = api::audio_modem::StopTransmit;
+namespace Receive = api::audio_modem::Receive;
+namespace StopReceive = api::audio_modem::StopReceive;
+namespace OnReceived = api::audio_modem::OnReceived;
+
+using audio_modem::AUDIBLE;
+using audio_modem::AudioToken;
+using audio_modem::AudioType;
+using audio_modem::INAUDIBLE;
+using audio_modem::TokenParameters;
+
+namespace {
+
+const char kInitFailedError[] = "The audio modem is not available. "
+ "Failed to initialize the token encoder/decoder.";
+const char kInvalidTokenLengthError[] =
+ "The token length must be greater than zero.";
+const char kIncorrectTokenLengthError[] =
+ "The token provided did not match the declared token length.";
+const char kInvalidTimeoutError[] =
+ "Transmit and receive timeouts must be greater than zero.";
+
+const int kMaxTransmitTimeout = 10 * 60 * 1000; // 10 minutes
+const int kMaxReceiveTimeout = 60 * 60 * 1000; // 1 hour
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<AudioModemAPI>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+AudioType AudioTypeForBand(Audioband band) {
+ switch (band) {
+ case AUDIOBAND_AUDIBLE:
+ return AUDIBLE;
+ case AUDIOBAND_INAUDIBLE:
+ return INAUDIBLE;
+ default:
+ NOTREACHED();
+ return audio_modem::AUDIO_TYPE_UNKNOWN;
+ }
+}
+
+TokenParameters TokenParamsForEncoding(
+ const api::audio_modem::TokenEncoding& encoding) {
+ return TokenParameters(encoding.token_length,
+ encoding.crc ? *encoding.crc : false,
+ encoding.parity ? *encoding.parity : true);
+}
+
+const std::string DecodeBase64Token(std::string encoded_token) {
+ // Make sure the token is padded correctly.
+ while (encoded_token.size() % 4 > 0)
+ encoded_token += "=";
+
+ // Decode and return the token.
+ std::string raw_token;
+ bool decode_success = base::Base64Decode(encoded_token, &raw_token);
+ DCHECK(decode_success);
+ return raw_token;
+}
+
+} // namespace
+
+
+// Public functions.
+
+AudioModemAPI::AudioModemAPI(content::BrowserContext* context)
+ : AudioModemAPI(context,
+ make_scoped_ptr(new ChromeWhispernetClient(context)),
+ audio_modem::Modem::Create()) {}
+
+AudioModemAPI::AudioModemAPI(
+ content::BrowserContext* context,
+ scoped_ptr<audio_modem::WhispernetClient> whispernet_client,
+ scoped_ptr<audio_modem::Modem> modem)
+ : browser_context_(context),
+ whispernet_client_(whispernet_client.Pass()),
+ modem_(modem.Pass()),
+ init_failed_(false) {
+ // We own these objects, so these callbacks will not outlive us.
+ whispernet_client_->Initialize(
+ base::Bind(&AudioModemAPI::WhispernetInitComplete,
+ base::Unretained(this)));
+ modem_->Initialize(whispernet_client_.get(),
+ base::Bind(&AudioModemAPI::TokensReceived,
+ base::Unretained(this)));
+}
+
+AudioModemAPI::~AudioModemAPI() {
+ for (const auto& timer_entry : receive_timers_[0])
+ delete timer_entry.second;
+ for (const auto& timer_entry : receive_timers_[1])
+ delete timer_entry.second;
+}
+
+Status AudioModemAPI::StartTransmit(const std::string& app_id,
+ const RequestParams& params,
+ const std::string& token) {
+ AudioType audio_type = AudioTypeForBand(params.band);
+ if (transmitters_[audio_type].empty())
+ transmitters_[audio_type] = app_id;
+ else if (transmitters_[audio_type] != app_id)
+ return STATUS_INUSE;
+
+ DVLOG(3) << "Starting transmit for app " << app_id;
+
+ std::string encoded_token;
+ base::Base64Encode(token, &encoded_token);
+ base::RemoveChars(encoded_token, "=", &encoded_token);
+
+ modem_->SetToken(audio_type, encoded_token);
+ modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding));
+ modem_->StartPlaying(audio_type);
+
+ transmit_timers_[audio_type].Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(params.timeout_millis),
+ base::Bind(base::IgnoreResult(&AudioModemAPI::StopTransmit),
+ base::Unretained(this),
+ app_id,
+ audio_type));
+ return STATUS_SUCCESS;
+}
+
+Status AudioModemAPI::StopTransmit(const std::string& app_id,
+ AudioType audio_type) {
+ if (transmitters_[audio_type] != app_id)
+ return STATUS_INVALIDREQUEST;
+
+ DVLOG(3) << "Stopping transmit for app " << app_id;
+ transmitters_[audio_type].clear();
+ modem_->StopPlaying(audio_type);
+ return STATUS_SUCCESS;
+}
+
+void AudioModemAPI::StartReceive(const std::string& app_id,
+ const RequestParams& params) {
+ DVLOG(3) << "Starting receive for app " << app_id;
+
+ AudioType audio_type = AudioTypeForBand(params.band);
+ modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding));
+ modem_->StartRecording(audio_type);
+
+ if (receive_timers_[audio_type].count(app_id) == 0)
+ receive_timers_[audio_type][app_id] = new base::OneShotTimer<AudioModemAPI>;
+ DCHECK(receive_timers_[audio_type][app_id]);
+ receive_timers_[audio_type][app_id]->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(params.timeout_millis),
+ base::Bind(base::IgnoreResult(&AudioModemAPI::StopReceive),
+ base::Unretained(this),
+ app_id,
+ audio_type));
+}
+
+Status AudioModemAPI::StopReceive(const std::string& app_id,
+ AudioType audio_type) {
+ if (receive_timers_[audio_type].count(app_id) == 0)
+ return STATUS_INVALIDREQUEST;
+
+ DCHECK(receive_timers_[audio_type][app_id]);
+ delete receive_timers_[audio_type][app_id];
+ receive_timers_[audio_type].erase(app_id);
+
+ DVLOG(3) << "Stopping receive for app " << app_id;
+ if (receive_timers_[audio_type].empty())
+ modem_->StopRecording(audio_type);
+ return STATUS_SUCCESS;
+}
+
+// static
+BrowserContextKeyedAPIFactory<AudioModemAPI>*
+AudioModemAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+
+// Private functions.
+
+void AudioModemAPI::WhispernetInitComplete(bool success) {
+ if (success) {
+ VLOG(2) << "Whispernet initialized successfully.";
+ } else {
+ LOG(ERROR) << "Failed to initialize Whispernet!";
+ init_failed_ = true;
+ }
+}
+
+void AudioModemAPI::TokensReceived(const std::vector<AudioToken>& tokens) {
+ // Distribute the tokens to the appropriate app(s).
+ std::map<std::string, std::vector<linked_ptr<ReceivedToken>>> tokens_by_app;
+ for (const AudioToken& token : tokens) {
+ linked_ptr<ReceivedToken> api_token(new ReceivedToken);
+ const std::string& raw_token = DecodeBase64Token(token.token);
+ api_token->token.assign(raw_token.c_str(),
+ raw_token.c_str() + raw_token.size());
+ api_token->band = token.audible ? AUDIOBAND_AUDIBLE : AUDIOBAND_INAUDIBLE;
+ for (const auto& receiver :
+ receive_timers_[token.audible ? AUDIBLE : INAUDIBLE]) {
+ tokens_by_app[receiver.first].push_back(api_token);
+ }
+ }
+
+ // Send events to the appropriate app(s).
+ for (const auto& app_entry : tokens_by_app) {
+ const std::string& app_id = app_entry.first;
+ const auto& tokens = app_entry.second;
+ if (app_id.empty())
+ continue;
+
+ EventRouter::Get(browser_context_)->DispatchEventToExtension(
+ app_id,
+ make_scoped_ptr(new Event(OnReceived::kEventName,
+ OnReceived::Create(tokens))));
+ }
+}
+
+
+// Functions outside the API scope.
+
+template <>
+void
+BrowserContextKeyedAPIFactory<AudioModemAPI>::DeclareFactoryDependencies() {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+}
+
+ExtensionFunction::ResponseAction AudioModemTransmitFunction::Run() {
+ scoped_ptr<Transmit::Params> params(Transmit::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ AudioModemAPI* api =
+ AudioModemAPI::GetFactoryInstance()->Get(browser_context());
+ if (api->init_failed()) {
+ return RespondNow(ErrorWithArguments(
+ Transmit::Results::Create(STATUS_CODERERROR),
+ kInitFailedError));
+ }
+
+ // Check the token length.
+ int token_length = params->params.encoding.token_length;
+ if (token_length <= 0) {
+ return RespondNow(ErrorWithArguments(
+ Transmit::Results::Create(STATUS_INVALIDREQUEST),
+ kInvalidTokenLengthError));
+ }
+ const char* token = vector_as_array(&params->token);
+ std::string token_str(token, params->token.size());
+ if (static_cast<int>(token_str.size()) != token_length) {
+ return RespondNow(ErrorWithArguments(
+ Transmit::Results::Create(STATUS_INVALIDREQUEST),
+ kIncorrectTokenLengthError));
+ }
+
+ // Check the timeout.
+ int64_t timeout_millis = params->params.timeout_millis;
+ if (timeout_millis <= 0) {
+ return RespondNow(ErrorWithArguments(
+ Transmit::Results::Create(STATUS_INVALIDREQUEST),
+ kInvalidTimeoutError));
+ }
+ if (timeout_millis > kMaxTransmitTimeout)
+ timeout_millis = kMaxTransmitTimeout;
+
+ // Start transmission.
+ Status status = api->StartTransmit(extension_id(), params->params, token_str);
+ return RespondNow(ArgumentList(Transmit::Results::Create(status)));
+}
+
+ExtensionFunction::ResponseAction AudioModemStopTransmitFunction::Run() {
+ scoped_ptr<StopTransmit::Params> params(StopTransmit::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context())
+ ->StopTransmit(extension_id(), AudioTypeForBand(params->band));
+ return RespondNow(ArgumentList(StopTransmit::Results::Create(status)));
+}
+
+ExtensionFunction::ResponseAction AudioModemReceiveFunction::Run() {
+ scoped_ptr<Receive::Params> params(Receive::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ AudioModemAPI* api =
+ AudioModemAPI::GetFactoryInstance()->Get(browser_context());
+ if (api->init_failed()) {
+ return RespondNow(ErrorWithArguments(
+ Transmit::Results::Create(STATUS_CODERERROR),
+ kInitFailedError));
+ }
+
+ // Check the timeout.
+ int64_t timeout_millis = params->params.timeout_millis;
+ if (timeout_millis <= 0) {
+ return RespondNow(ErrorWithArguments(
+ Receive::Results::Create(STATUS_INVALIDREQUEST),
+ kInvalidTimeoutError));
+ }
+ if (timeout_millis > kMaxReceiveTimeout)
+ timeout_millis = kMaxReceiveTimeout;
+
+ // Start receiving.
+ api->StartReceive(extension_id(), params->params);
+ return RespondNow(ArgumentList(Receive::Results::Create(STATUS_SUCCESS)));
+}
+
+ExtensionFunction::ResponseAction AudioModemStopReceiveFunction::Run() {
+ scoped_ptr<StopReceive::Params> params(StopReceive::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context())
+ ->StopReceive(extension_id(), AudioTypeForBand(params->band));
+ return RespondNow(ArgumentList(StopReceive::Results::Create(status)));
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/audio_modem/audio_modem_api.h b/chrome/browser/extensions/api/audio_modem/audio_modem_api.h
new file mode 100644
index 0000000..557ecef
--- /dev/null
+++ b/chrome/browser/extensions/api/audio_modem/audio_modem_api.h
@@ -0,0 +1,130 @@
+// 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 CHROME_BROWSER_EXTENSIONS_API_AUDIO_MODEM_AUDIO_MODEM_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_AUDIO_MODEM_AUDIO_MODEM_API_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/api/audio_modem.h"
+#include "components/audio_modem/public/modem.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+
+namespace extensions {
+
+// Implementation of the chrome.audioModem API.
+class AudioModemAPI final : public BrowserContextKeyedAPI {
+ public:
+ // Default constructor.
+ explicit AudioModemAPI(content::BrowserContext* context);
+
+ // Testing constructor: pass in dependencies.
+ AudioModemAPI(content::BrowserContext* context,
+ scoped_ptr<audio_modem::WhispernetClient> whispernet_client,
+ scoped_ptr<audio_modem::Modem> modem);
+
+ ~AudioModemAPI() override;
+
+ // Starts transmitting a token, and returns the associated API status.
+ // Fails if another app is already transmitting the same AudioType.
+ api::audio_modem::Status StartTransmit(
+ const std::string& app_id,
+ const api::audio_modem::RequestParams& params,
+ const std::string& token);
+
+ // Stops an in-progress transmit, and returns the associated API status.
+ // Fails if the specified app is not currently transmitting.
+ api::audio_modem::Status StopTransmit(const std::string& app_id,
+ audio_modem::AudioType audio_type);
+
+ // Starts receiving for the specified app.
+ // Multiple apps may receive the same AudioType simultaneously.
+ void StartReceive(const std::string& app_id,
+ const api::audio_modem::RequestParams& params);
+
+ // Stops receiving for the specified app, and returns the associated
+ // API status. Fails if that app is not currently receiving.
+ api::audio_modem::Status StopReceive(const std::string& app_id,
+ audio_modem::AudioType audio_type);
+
+ bool init_failed() const { return init_failed_; }
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<AudioModemAPI>* GetFactoryInstance();
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<AudioModemAPI>;
+
+ void WhispernetInitComplete(bool success);
+ void TokensReceived(const std::vector<audio_modem::AudioToken>& tokens);
+
+ content::BrowserContext* const browser_context_;
+ scoped_ptr<audio_modem::WhispernetClient> whispernet_client_;
+ scoped_ptr<audio_modem::Modem> modem_;
+ bool init_failed_;
+
+ // IDs for the currently transmitting app (if any), indexed by AudioType.
+ std::string transmitters_[2];
+
+ // Timeouts for the currently active transmits, indexed by AudioType.
+ base::OneShotTimer<AudioModemAPI> transmit_timers_[2];
+
+ // Maps of currently receiving app ID => timeouts. Indexed by AudioType.
+ // We own all of these pointers. Do not remove them without calling delete.
+ std::map<std::string, base::OneShotTimer<AudioModemAPI>*> receive_timers_[2];
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "AudioModemAPI"; }
+
+ DISALLOW_COPY_AND_ASSIGN(AudioModemAPI);
+};
+
+template<>
+void BrowserContextKeyedAPIFactory<AudioModemAPI>::DeclareFactoryDependencies();
+
+class AudioModemTransmitFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audioModem.transmit", AUDIOMODEM_TRANSMIT);
+
+ protected:
+ ~AudioModemTransmitFunction() override {}
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class AudioModemStopTransmitFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audioModem.stopTransmit",
+ AUDIOMODEM_STOPTRANSMIT);
+
+ protected:
+ ~AudioModemStopTransmitFunction() override {}
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class AudioModemReceiveFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audioModem.receive", AUDIOMODEM_RECEIVE);
+
+ protected:
+ ~AudioModemReceiveFunction() override {}
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class AudioModemStopReceiveFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audioModem.stopReceive", AUDIOMODEM_STOPRECEIVE);
+
+ protected:
+ ~AudioModemStopReceiveFunction() override {}
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_AUDIO_MODEM_AUDIO_MODEM_API_H_
diff --git a/chrome/browser/extensions/api/audio_modem/audio_modem_api_unittest.cc b/chrome/browser/extensions/api/audio_modem/audio_modem_api_unittest.cc
new file mode 100644
index 0000000..f1fa5ac
--- /dev/null
+++ b/chrome/browser/extensions/api/audio_modem/audio_modem_api_unittest.cc
@@ -0,0 +1,380 @@
+// 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 <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h"
+#include "chrome/browser/extensions/extension_api_unittest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "components/audio_modem/public/modem.h"
+#include "components/audio_modem/test/stub_modem.h"
+#include "components/audio_modem/test/stub_whispernet_client.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/event_router.h"
+
+using audio_modem::AUDIBLE;
+using audio_modem::AudioToken;
+using audio_modem::INAUDIBLE;
+using audio_modem::StubModem;
+using audio_modem::StubWhispernetClient;
+
+using base::BinaryValue;
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringValue;
+using base::Value;
+
+using content::BrowserContext;
+
+namespace ext_test_utils = extension_function_test_utils;
+
+namespace extensions {
+
+namespace {
+
+// The TestingFactoryFunction uses a BrowserContext as its context pointer.
+// But each BrowserContext is still associated with a unit test.
+// So we store the StubModem created in each test.
+std::map<BrowserContext*, StubModem*> g_modems;
+
+// Create a test AudioModemAPI and store the modem it uses.
+KeyedService* ApiFactoryFunction(BrowserContext* context) {
+ StubModem* modem = new StubModem;
+ g_modems[context] = modem;
+ return new AudioModemAPI(
+ context,
+ make_scoped_ptr<audio_modem::WhispernetClient>(new StubWhispernetClient),
+ make_scoped_ptr<audio_modem::Modem>(modem));
+}
+
+DictionaryValue* CreateParams(const std::string& audio_band) {
+ DictionaryValue* params = new DictionaryValue;
+ params->SetInteger("timeoutMillis", 60000);
+ params->SetString("band", audio_band);
+ params->SetInteger("encoding.tokenLength", 4);
+ return params;
+}
+
+BinaryValue* CreateToken(const std::string& token) {
+ return BinaryValue::CreateWithCopiedBuffer(token.c_str(), token.size());
+}
+
+scoped_ptr<ListValue> CreateList(Value* single_elt) {
+ scoped_ptr<ListValue> list(new ListValue);
+ list->Append(single_elt);
+ return list.Pass();
+}
+
+scoped_ptr<ListValue> CreateList(Value* elt1, Value* elt2) {
+ scoped_ptr<ListValue> list(new ListValue);
+ list->Append(elt1);
+ list->Append(elt2);
+ return list.Pass();
+}
+
+DictionaryValue* CreateReceivedToken(const std::string& token,
+ const std::string& audio_band) {
+ DictionaryValue* out = new DictionaryValue;
+ out->Set("token", CreateToken(token));
+ out->SetString("band", audio_band);
+ return out;
+}
+
+bool ReceivedSingleToken(const Event* event,
+ const DictionaryValue* expected_token) {
+ ListValue* received_tokens;
+ event->event_args->GetList(0, &received_tokens);
+ if (received_tokens->GetSize() != 1)
+ return false;
+
+ DictionaryValue* received_token;
+ received_tokens->GetDictionary(0, &received_token);
+ return received_token->Equals(expected_token);
+}
+
+// TODO(ckehoe): Put this in //extensions/test.
+// Then replace the other EventRouter mocks.
+class StubEventRouter : public EventRouter {
+ public:
+ // Callback to receive events. First argument is
+ // the extension id to receive the event.
+ using EventCallback = base::Callback<void(const std::string&,
+ scoped_ptr<Event>)>;
+
+ StubEventRouter(BrowserContext* context, EventCallback event_callback)
+ : EventRouter(context, nullptr),
+ event_callback_(event_callback) {}
+
+ void DispatchEventToExtension(const std::string& extension_id,
+ scoped_ptr<Event> event) override {
+ event_callback_.Run(extension_id, event.Pass());
+ }
+
+ void ClearEventCallback() {
+ event_callback_.Reset();
+ }
+
+ private:
+ EventCallback event_callback_;
+};
+
+} // namespace
+
+class AudioModemApiUnittest : public ExtensionApiUnittest {
+ public:
+ AudioModemApiUnittest() {}
+ ~AudioModemApiUnittest() override {
+ for (const auto& events : events_by_extension_id_) {
+ for (const Event* event : events.second)
+ delete event;
+ }
+ }
+
+ protected:
+ template<typename Function>
+ const std::string RunFunction(scoped_ptr<ListValue> args,
+ const Extension* extension) {
+ scoped_refptr<UIThreadExtensionFunction> function(new Function);
+ function->set_extension(extension);
+ function->set_browser_context(profile());
+ function->set_has_callback(true);
+ ext_test_utils::RunFunction(
+ function.get(), args.Pass(), browser(), ext_test_utils::NONE);
+
+ std::string result_status;
+ CHECK(function->GetResultList()->GetString(0, &result_status));
+ return result_status;
+ }
+
+ template<typename Function>
+ const std::string RunFunction(scoped_ptr<ListValue> args) {
+ return RunFunction<Function>(args.Pass(), GetExtension(std::string()));
+ }
+
+ StubModem* GetModem() const {
+ return g_modems[profile()];
+ }
+
+ const Extension* GetExtension(const std::string& name) {
+ if (!extensions_by_name_[name].get()) {
+ scoped_ptr<DictionaryValue> extension_definition(new DictionaryValue);
+ extension_definition->SetString("name", name);
+ extension_definition->SetString("version", "1.0");
+ extensions_by_name_[name] = api_test_utils::CreateExtension(
+ Manifest::INTERNAL, extension_definition.get(), name);
+ DVLOG(2) << "Created extension " << extensions_by_name_[name]->id();
+ }
+ return extensions_by_name_[name].get();
+ }
+
+ const std::vector<const Event*>&
+ GetEventsForExtension(const std::string& name) {
+ const Extension* extension = extensions_by_name_[name].get();
+ DCHECK(extension);
+ return events_by_extension_id_[extension->id()];
+ }
+
+ const std::vector<const Event*>& GetEvents() {
+ return GetEventsForExtension(std::string());
+ }
+
+ private:
+ void SetUp() override {
+ ExtensionApiUnittest::SetUp();
+ AudioModemAPI::GetFactoryInstance()->SetTestingFactory(
+ profile(), &ApiFactoryFunction);
+
+ scoped_ptr<EventRouter> router(new StubEventRouter(
+ profile(),
+ // The EventRouter is deleted in TearDown().
+ // It will lose this callback before we are destructed.
+ base::Bind(&AudioModemApiUnittest::CaptureEvent,
+ base::Unretained(this))));
+ static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()))
+ ->SetEventRouter(router.Pass());
+ }
+
+ void CaptureEvent(const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ // Since scoped_ptr (and ScopedVector) do not work inside STL containers,
+ // we must manage this memory manually. It is cleaned up by the destructor.
+ events_by_extension_id_[extension_id].push_back(event.release());
+ }
+
+ std::map<std::string, scoped_refptr<Extension>> extensions_by_name_;
+
+ // We own all of these pointers.
+ // Do not remove them from the map without calling delete.
+ std::map<std::string, std::vector<const Event*>> events_by_extension_id_;
+};
+
+TEST_F(AudioModemApiUnittest, TransmitBasic) {
+ // Start transmitting inaudibly.
+ EXPECT_EQ("success", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("inaudible"), CreateToken("1234"))));
+ EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE));
+
+ // Can't cancel audible transmit - we haven't started it yet.
+ EXPECT_EQ("invalidRequest", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("audible"))));
+
+ // Start transmitting audibly.
+ EXPECT_EQ("success", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("audible"), CreateToken("ABCD"))));
+ EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE));
+
+ // Stop audible transmit. We're still transmitting inaudibly.
+ EXPECT_EQ("success", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("audible"))));
+ EXPECT_FALSE(GetModem()->IsPlaying(AUDIBLE));
+ EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE));
+
+ // Stop inaudible transmit.
+ EXPECT_EQ("success", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("inaudible"))));
+ EXPECT_FALSE(GetModem()->IsPlaying(INAUDIBLE));
+}
+
+TEST_F(AudioModemApiUnittest, ReceiveBasic) {
+ // Start listening for audible tokens.
+ EXPECT_EQ("success", RunFunction<AudioModemReceiveFunction>(
+ CreateList(CreateParams("audible"))));
+ EXPECT_TRUE(GetModem()->IsRecording(AUDIBLE));
+
+ // Can't cancel inaudible receive - we haven't started it yet.
+ EXPECT_EQ("invalidRequest", RunFunction<AudioModemStopReceiveFunction>(
+ CreateList(new StringValue("inaudible"))));
+
+ // Send some audible tokens.
+ std::vector<AudioToken> tokens;
+ tokens.push_back(AudioToken("1234", true));
+ tokens.push_back(AudioToken("ABCD", true));
+ tokens.push_back(AudioToken("abcd", false));
+ GetModem()->DeliverTokens(tokens);
+
+ // Check the tokens received.
+ EXPECT_EQ(1u, GetEvents().size());
+ scoped_ptr<ListValue> expected_tokens(new ListValue);
+ expected_tokens->Append(CreateReceivedToken("1234", "audible"));
+ expected_tokens->Append(CreateReceivedToken("ABCD", "audible"));
+ ListValue* received_tokens;
+ GetEvents()[0]->event_args->GetList(0, &received_tokens);
+ EXPECT_TRUE(received_tokens->Equals(expected_tokens.get()));
+
+ // Start listening for inaudible tokens.
+ EXPECT_EQ("success", RunFunction<AudioModemReceiveFunction>(
+ CreateList(CreateParams("inaudible"))));
+ EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE));
+
+ // Send some more tokens.
+ tokens.push_back(AudioToken("5678", false));
+ GetModem()->DeliverTokens(tokens);
+
+ // Check the tokens received.
+ EXPECT_EQ(2u, GetEvents().size());
+ expected_tokens->Append(CreateReceivedToken("abcd", "inaudible"));
+ expected_tokens->Append(CreateReceivedToken("5678", "inaudible"));
+ GetEvents()[1]->event_args->GetList(0, &received_tokens);
+ EXPECT_TRUE(received_tokens->Equals(expected_tokens.get()));
+
+ // Stop audible receive. We're still receiving inaudible.
+ EXPECT_EQ("success", RunFunction<AudioModemStopReceiveFunction>(
+ CreateList(new StringValue("audible"))));
+ EXPECT_FALSE(GetModem()->IsRecording(AUDIBLE));
+ EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE));
+
+ // Stop inaudible receive.
+ EXPECT_EQ("success", RunFunction<AudioModemStopReceiveFunction>(
+ CreateList(new StringValue("inaudible"))));
+ EXPECT_FALSE(GetModem()->IsRecording(INAUDIBLE));
+}
+
+TEST_F(AudioModemApiUnittest, TransmitMultiple) {
+ // Start transmit.
+ EXPECT_EQ("success", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("audible"), CreateToken("1234")),
+ GetExtension("ext1")));
+ EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE));
+
+ // Another extension can't interfere with the first one.
+ EXPECT_EQ("inUse", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("audible"), CreateToken("ABCD")),
+ GetExtension("ext2")));
+ EXPECT_EQ("invalidRequest", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("audible")), GetExtension("ext2")));
+ EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE));
+
+ // The other extension can use the other audio band, however.
+ EXPECT_EQ("success", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("inaudible"), CreateToken("ABCD")),
+ GetExtension("ext2")));
+ EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE));
+
+ // The first extension can change its token.
+ // But the other band is still in use.
+ EXPECT_EQ("success", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("audible"), CreateToken("abcd")),
+ GetExtension("ext1")));
+ EXPECT_EQ("inUse", RunFunction<AudioModemTransmitFunction>(
+ CreateList(CreateParams("inaudible"), CreateToken("1234")),
+ GetExtension("ext1")));
+
+ // Stop transmission.
+ EXPECT_EQ("success", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("audible")), GetExtension("ext1")));
+ EXPECT_FALSE(GetModem()->IsPlaying(AUDIBLE));
+ EXPECT_EQ("success", RunFunction<AudioModemStopTransmitFunction>(
+ CreateList(new StringValue("inaudible")), GetExtension("ext2")));
+ EXPECT_FALSE(GetModem()->IsPlaying(INAUDIBLE));
+}
+
+TEST_F(AudioModemApiUnittest, ReceiveMultiple) {
+ // Start receive. Multiple extensions can receive on the same band.
+ EXPECT_EQ("success", RunFunction<AudioModemReceiveFunction>(
+ CreateList(CreateParams("inaudible")), GetExtension("ext1")));
+ EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE));
+ EXPECT_EQ("success", RunFunction<AudioModemReceiveFunction>(
+ CreateList(CreateParams("inaudible")), GetExtension("ext2")));
+
+ // Receive a token.
+ GetModem()->DeliverTokens(std::vector<AudioToken>(
+ 1, AudioToken("abcd", false)));
+ EXPECT_EQ(1u, GetEventsForExtension("ext1").size());
+ EXPECT_EQ(1u, GetEventsForExtension("ext2").size());
+
+ // Check the token received.
+ scoped_ptr<DictionaryValue> expected_token(
+ CreateReceivedToken("abcd", "inaudible"));
+ EXPECT_TRUE(ReceivedSingleToken(
+ GetEventsForExtension("ext1")[0], expected_token.get()));
+ EXPECT_TRUE(ReceivedSingleToken(
+ GetEventsForExtension("ext2")[0], expected_token.get()));
+
+ // If one extension stops, the modem is still receiving for the other.
+ EXPECT_EQ("success", RunFunction<AudioModemStopReceiveFunction>(
+ CreateList(new StringValue("inaudible")), GetExtension("ext1")));
+ EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE));
+
+ // Receive another token. Should only go to ext2.
+ GetModem()->DeliverTokens(std::vector<AudioToken>(
+ 1, AudioToken("1234", false)));
+ EXPECT_EQ(1u, GetEventsForExtension("ext1").size());
+ EXPECT_EQ(2u, GetEventsForExtension("ext2").size());
+ expected_token.reset(CreateReceivedToken("1234", "inaudible"));
+ EXPECT_TRUE(ReceivedSingleToken(
+ GetEventsForExtension("ext2")[1], expected_token.get()));
+
+ EXPECT_EQ("success", RunFunction<AudioModemStopReceiveFunction>(
+ CreateList(new StringValue("inaudible")), GetExtension("ext2")));
+ EXPECT_FALSE(GetModem()->IsRecording(INAUDIBLE));
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc
index fd6e508..54fba44 100644
--- a/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc
+++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.cc
@@ -4,59 +4,77 @@
#include "chrome/browser/extensions/api/copresence_private/copresence_private_api.h"
+#include <map>
+#include <string>
#include <vector>
+#include "base/guid.h"
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "chrome/browser/copresence/chrome_whispernet_client.h"
-#include "chrome/browser/extensions/api/copresence/copresence_api.h"
#include "chrome/common/extensions/api/copresence_private.h"
#include "media/base/audio_bus.h"
+using audio_modem::WhispernetClient;
+
+namespace {
+
+base::LazyInstance<std::map<std::string, WhispernetClient*>>
+g_whispernet_clients = LAZY_INSTANCE_INITIALIZER;
+
+WhispernetClient* GetWhispernetClient(const std::string& id) {
+ WhispernetClient* client = g_whispernet_clients.Get()[id];
+ DCHECK(client);
+ return client;
+}
+
+} // namespace
+
namespace extensions {
namespace SendFound = api::copresence_private::SendFound;
namespace SendSamples = api::copresence_private::SendSamples;
-namespace SendDetect = api::copresence_private::SendDetect;
namespace SendInitialized = api::copresence_private::SendInitialized;
-// Copresence Private functions.
+namespace copresence_private {
-audio_modem::WhispernetClient*
-CopresencePrivateFunction::GetWhispernetClient() {
- CopresenceService* service =
- CopresenceService::GetFactoryInstance()->Get(browser_context());
- return service ? service->whispernet_client() : NULL;
+const std::string RegisterWhispernetClient(WhispernetClient* client) {
+ std::string id = base::GenerateGUID();
+ g_whispernet_clients.Get()[id] = client;
+ return id;
}
+} // namespace copresence_private
+
+// Copresence Private functions.
+
// CopresenceSendFoundFunction implementation:
ExtensionFunction::ResponseAction CopresencePrivateSendFoundFunction::Run() {
- if (!GetWhispernetClient() ||
- GetWhispernetClient()->GetTokensCallback().is_null()) {
- return RespondNow(NoArguments());
- }
-
scoped_ptr<SendFound::Params> params(SendFound::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ WhispernetClient* whispernet_client = GetWhispernetClient(params->client_id);
+ if (whispernet_client->GetTokensCallback().is_null())
+ return RespondNow(NoArguments());
+
std::vector<audio_modem::AudioToken> tokens;
for (size_t i = 0; i < params->tokens.size(); ++i) {
tokens.push_back(audio_modem::AudioToken(params->tokens[i]->token,
params->tokens[i]->audible));
}
- GetWhispernetClient()->GetTokensCallback().Run(tokens);
+ whispernet_client->GetTokensCallback().Run(tokens);
return RespondNow(NoArguments());
}
// CopresenceSendEncodedFunction implementation:
ExtensionFunction::ResponseAction CopresencePrivateSendSamplesFunction::Run() {
- if (!GetWhispernetClient() ||
- GetWhispernetClient()->GetSamplesCallback().is_null()) {
- return RespondNow(NoArguments());
- }
-
scoped_ptr<SendSamples::Params> params(SendSamples::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
+ WhispernetClient* whispernet_client = GetWhispernetClient(params->client_id);
+ if (whispernet_client->GetSamplesCallback().is_null())
+ return RespondNow(NoArguments());
+
scoped_refptr<media::AudioBusRefCounted> samples =
media::AudioBusRefCounted::Create(1, // Mono
params->samples.size() / sizeof(float));
@@ -64,39 +82,28 @@ ExtensionFunction::ResponseAction CopresencePrivateSendSamplesFunction::Run() {
memcpy(samples->channel(0), vector_as_array(&params->samples),
params->samples.size());
- GetWhispernetClient()->GetSamplesCallback().Run(
+ whispernet_client->GetSamplesCallback().Run(
params->token.audible ? audio_modem::AUDIBLE : audio_modem::INAUDIBLE,
params->token.token, samples);
return RespondNow(NoArguments());
}
-// CopresenceSendDetectFunction implementation:
-ExtensionFunction::ResponseAction CopresencePrivateSendDetectFunction::Run() {
- if (!GetWhispernetClient() ||
- GetWhispernetClient()->GetDetectBroadcastCallback().is_null()) {
- return RespondNow(NoArguments());
- }
-
- scoped_ptr<SendDetect::Params> params(SendDetect::Params::Create(*args_));
- EXTENSION_FUNCTION_VALIDATE(params.get());
-
- GetWhispernetClient()->GetDetectBroadcastCallback().Run(params->detected);
- return RespondNow(NoArguments());
-}
-
// CopresenceSendInitializedFunction implementation:
ExtensionFunction::ResponseAction
CopresencePrivateSendInitializedFunction::Run() {
- if (!GetWhispernetClient() ||
- GetWhispernetClient()->GetInitializedCallback().is_null()) {
- return RespondNow(NoArguments());
- }
-
scoped_ptr<SendInitialized::Params> params(
SendInitialized::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
- GetWhispernetClient()->GetInitializedCallback().Run(params->success);
+ DVLOG(2) << "Forwarding init callback to "
+ << g_whispernet_clients.Get().size() << " clients";
+ for (auto client_entry : g_whispernet_clients.Get()) {
+ audio_modem::SuccessCallback init_callback =
+ client_entry.second->GetInitializedCallback();
+ if (!init_callback.is_null())
+ init_callback.Run(params->success);
+ }
+
return RespondNow(NoArguments());
}
diff --git a/chrome/browser/extensions/api/copresence_private/copresence_private_api.h b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h
index 3377336..d4c2098 100644
--- a/chrome/browser/extensions/api/copresence_private/copresence_private_api.h
+++ b/chrome/browser/extensions/api/copresence_private/copresence_private_api.h
@@ -5,7 +5,9 @@
#ifndef CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_COPRESENCE_PRIVATE_COPRESENCE_PRIVATE_API_H_
-#include "chrome/browser/extensions/chrome_extension_function.h"
+#include <string>
+
+#include "extensions/browser/extension_function.h"
namespace audio_modem {
class WhispernetClient;
@@ -13,13 +15,15 @@ class WhispernetClient;
namespace extensions {
-class CopresencePrivateFunction : public ChromeUIThreadExtensionFunction {
- protected:
- audio_modem::WhispernetClient* GetWhispernetClient();
- ~CopresencePrivateFunction() override {}
-};
+namespace copresence_private {
+
+// Register a client to receive events from Whispernet.
+const std::string
+RegisterWhispernetClient(audio_modem::WhispernetClient* client);
+
+} // namespace copresence_private
-class CopresencePrivateSendFoundFunction : public CopresencePrivateFunction {
+class CopresencePrivateSendFoundFunction : public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendFound",
COPRESENCEPRIVATE_SENDFOUND);
@@ -29,7 +33,7 @@ class CopresencePrivateSendFoundFunction : public CopresencePrivateFunction {
ExtensionFunction::ResponseAction Run() override;
};
-class CopresencePrivateSendSamplesFunction : public CopresencePrivateFunction {
+class CopresencePrivateSendSamplesFunction : public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendSamples",
COPRESENCEPRIVATE_SENDSAMPLES);
@@ -39,7 +43,7 @@ class CopresencePrivateSendSamplesFunction : public CopresencePrivateFunction {
ExtensionFunction::ResponseAction Run() override;
};
-class CopresencePrivateSendDetectFunction : public CopresencePrivateFunction {
+class CopresencePrivateSendDetectFunction : public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendDetect",
COPRESENCEPRIVATE_SENDDETECT);
@@ -50,7 +54,7 @@ class CopresencePrivateSendDetectFunction : public CopresencePrivateFunction {
};
class CopresencePrivateSendInitializedFunction
- : public CopresencePrivateFunction {
+ : public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("copresencePrivate.sendInitialized",
COPRESENCEPRIVATE_SENDINITIALIZED);
diff --git a/chrome/browser/resources/whispernet_proxy/js/init.js b/chrome/browser/resources/whispernet_proxy/js/init.js
index 87abe26..fada4f6 100644
--- a/chrome/browser/resources/whispernet_proxy/js/init.js
+++ b/chrome/browser/resources/whispernet_proxy/js/init.js
@@ -4,42 +4,40 @@
'use strict';
-// Globals holding our encoder and decoder. We will never have more than one
-// Global variable that will be used to access this Nacl bridge.
+// Global holding our NaclBridge.
var whispernetNacl = null;
-// copy of an encoder or a decoder at a time.
-var whisperEncoder = null;
-var whisperDecoder = null;
+// Encoders and decoders for each client.
+var whisperEncoders = {};
+var whisperDecoders = {};
/**
* Initialize the whispernet encoder and decoder.
- * @param {Object} audioParams Object containing the parameters needed for
- * setting up audio config.
+ * Call this before any other functions.
+ * @param {string} clientId A string identifying the requester.
+ * @param {Object} audioParams Audio parameters for token encoding and decoding.
*/
-function audioConfig(audioParams) {
+function audioConfig(clientId, audioParams) {
if (!whispernetNacl) {
chrome.copresencePrivate.sendInitialized(false);
return;
}
- console.log('Configuring encoder!');
- whisperEncoder = new WhisperEncoder(audioParams.paramData, whispernetNacl);
- whisperEncoder.setAudioDataCallback(chrome.copresencePrivate.sendSamples);
-
- console.log('Configuring decoder!');
- whisperDecoder = new WhisperDecoder(audioParams.paramData, whispernetNacl);
- whisperDecoder.setReceiveCallback(chrome.copresencePrivate.sendFound);
- whisperDecoder.onDetectBroadcast(chrome.copresencePrivate.sendDetect);
+ console.log('Configuring encoder and decoder!');
+ whisperEncoders[clientId] =
+ new WhisperEncoder(audioParams.paramData, whispernetNacl, clientId);
+ whisperDecoders[clientId] =
+ new WhisperDecoder(audioParams.paramData, whispernetNacl, clientId);
}
/**
* Sends a request to whispernet to encode a token.
+ * @param {string} clientId A string identifying the requester.
* @param {Object} params Encode token parameters object.
*/
-function encodeTokenRequest(params) {
- if (whisperEncoder) {
- whisperEncoder.encode(params);
+function encodeTokenRequest(clientId, params) {
+ if (whisperEncoders[clientId]) {
+ whisperEncoders[clientId].encode(params);
} else {
console.error('encodeTokenRequest: Whisper not initialized!');
}
@@ -47,29 +45,19 @@ function encodeTokenRequest(params) {
/**
* Sends a request to whispernet to decode samples.
+ * @param {string} clientId A string identifying the requester.
* @param {Object} params Process samples parameters object.
*/
-function decodeSamplesRequest(params) {
- if (whisperDecoder) {
- whisperDecoder.processSamples(params);
+function decodeSamplesRequest(clientId, params) {
+ if (whisperDecoders[clientId]) {
+ whisperDecoders[clientId].processSamples(params);
} else {
console.error('decodeSamplesRequest: Whisper not initialized!');
}
}
/**
- * Sends a request to whispernet to detect broadcast.
- */
-function detectBroadcastRequest() {
- if (whisperDecoder) {
- whisperDecoder.detectBroadcast();
- } else {
- console.error('detectBroadcastRequest: Whisper not initialized!');
- }
-}
-
-/**
- * Initialize our listerners and signal that the extension is loaded.
+ * Initialize our listeners and signal that the extension is loaded.
*/
function onWhispernetLoaded() {
console.log('init: Nacl ready!');
@@ -79,8 +67,6 @@ function onWhispernetLoaded() {
chrome.copresencePrivate.onEncodeTokenRequest.addListener(encodeTokenRequest);
chrome.copresencePrivate.onDecodeSamplesRequest.addListener(
decodeSamplesRequest);
- chrome.copresencePrivate.onDetectBroadcastRequest.addListener(
- detectBroadcastRequest);
// This first initialized is sent to indicate that the library is loaded.
// Every other time, it will be sent only when Chrome wants to reinitialize
diff --git a/chrome/browser/resources/whispernet_proxy/js/nacl.js b/chrome/browser/resources/whispernet_proxy/js/nacl.js
index 6505728..06f566c 100644
--- a/chrome/browser/resources/whispernet_proxy/js/nacl.js
+++ b/chrome/browser/resources/whispernet_proxy/js/nacl.js
@@ -81,14 +81,13 @@ NaclBridge.prototype.loadNacl_ = function(manifestUrl) {
};
/**
- * Callback that is called when the Whispernet wrapper is loaded and forward
- * that status to the callback registered with us in the constructor.
+ * Called when the Whispernet wrapper is loaded.
* @private
*/
NaclBridge.prototype.onNaclReady_ = function() {
+ this.isEnabled_ = true;
if (this.readyCallback_)
this.readyCallback_();
- this.isEnabled_ = true;
};
/**
diff --git a/chrome/browser/resources/whispernet_proxy/js/wrapper.js b/chrome/browser/resources/whispernet_proxy/js/wrapper.js
index 6bde1b3..d5f5701 100644
--- a/chrome/browser/resources/whispernet_proxy/js/wrapper.js
+++ b/chrome/browser/resources/whispernet_proxy/js/wrapper.js
@@ -32,14 +32,15 @@ function stringToArray(str) {
/**
* Creates a whispernet encoder.
* @constructor
- * @param {Object} params Dictionary of parameters used to initialize the
- * whispernet encoder.
- * @param {Object} whisperNacl The NaclBridge object to use to communicate with
- * the whispernet wrapper.
+ * @param {Object} params Audio parameters for the whispernet encoder.
+ * @param {Object} whisperNacl The NaclBridge object, used to communicate with
+ * the whispernet wrapper.
+ * @param {string} clientId A string identifying the requester.
*/
-function WhisperEncoder(params, whisperNacl) {
+function WhisperEncoder(params, whisperNacl, clientId) {
this.whisperNacl_ = whisperNacl;
this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+ this.clientId_ = clientId;
var msg = {
type: 'initialize_encoder',
@@ -61,29 +62,22 @@ WhisperEncoder.prototype.encode = function(params) {
var msg = {
type: 'encode_token',
+ client_id: this.clientId_,
// Trying to send the token in binary form to Nacl doesn't work correctly.
// We end up with the correct string + a bunch of extra characters. This is
// true of returning a binary string too; hence we communicate back and
// forth by converting the bytes into an array of integers.
token: stringToArray(atob(token)),
repetitions: params.repetitions,
- use_dtmf: params.token.audible
+ use_dtmf: params.token.audible,
+ use_crc: params.tokenParams.crc,
+ use_parity: params.tokenParams.parity
};
this.whisperNacl_.send(msg);
};
/**
- * Method to set the callback for encoded audio data received from the encoder
- * when we finish encoding a token.
- * @param {function(string, ArrayBuffer)} callback Callback which will receive
- * the audio samples.
- */
-WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
- this.audioDataCallback_ = callback;
-};
-
-/**
* Method to handle messages from the whispernet NaCl wrapper.
* @param {Event} e Event from the whispernet wrapper.
* @private
@@ -91,7 +85,7 @@ WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
WhisperEncoder.prototype.onNaclMessage_ = function(e) {
var msg = e.data;
if (msg.type == 'encode_token_response') {
- this.audioDataCallback_(
+ chrome.copresencePrivate.sendSamples(this.clientId_,
{ token: bytesToBase64(msg.token), audible: msg.audible }, msg.samples);
}
};
@@ -99,14 +93,15 @@ WhisperEncoder.prototype.onNaclMessage_ = function(e) {
/**
* Creates a whispernet decoder.
* @constructor
- * @param {Object} params Dictionary of parameters used to initialize the
- * whispernet decoder.
- * @param {Object} whisperNacl The NaclBridge object to use to communicate with
- * the whispernet wrapper.
+ * @param {Object} params Audio parameters for the whispernet decoder.
+ * @param {Object} whisperNacl The NaclBridge object, used to communicate with
+ * the whispernet wrapper.
+ * @param {string} clientId A string identifying the requester.
*/
-function WhisperDecoder(params, whisperNacl) {
+function WhisperDecoder(params, whisperNacl, clientId) {
this.whisperNacl_ = whisperNacl;
this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
+ this.clientId_ = clientId;
var msg = {
type: 'initialize_decoder',
@@ -126,52 +121,30 @@ WhisperDecoder.prototype.wipeDecoder = function() {
};
/**
- * Method to request the decoder to detect a broadcast.
- */
-WhisperDecoder.prototype.detectBroadcast = function() {
- var msg = {
- type: 'detect_broadcast'
- };
- this.whisperNacl_.send(msg);
-};
-
-/**
* Method to request the decoder to process samples.
* @param {Object} params Process samples parameters object.
*/
WhisperDecoder.prototype.processSamples = function(params) {
var msg = {
type: 'decode_tokens',
+ client_id: this.clientId_,
+ data: params.samples,
+
decode_audible: params.decodeAudible,
+ token_length_dtmf: params.audibleTokenParams.length,
+ crc_dtmf: params.audibleTokenParams.crc,
+ parity_dtmf: params.audibleTokenParams.parity,
+
decode_inaudible: params.decodeInaudible,
- data: params.samples,
- token_length_dtmf: params.audibleTokenLength,
- token_length_dsss: params.inaudibleTokenLength
+ token_length_dsss: params.inaudibleTokenParams.length,
+ crc_dsss: params.inaudibleTokenParams.crc,
+ parity_dsss: params.inaudibleTokenParams.parity,
};
this.whisperNacl_.send(msg);
};
/**
- * Method to set the callback for decoded tokens received from the decoder.
- * @param {function(!Array.string)} callback Callback to receive the list of
- * decoded tokens.
- */
-WhisperDecoder.prototype.setReceiveCallback = function(callback) {
- this.tokenCallback_ = callback;
-};
-
-/**
- * Method to set the callback for receiving the detect callback status received
- * from the decoder.
- * @param {function()} callback Callback to set to receive the detect broadcast
- * status.
- */
-WhisperDecoder.prototype.onDetectBroadcast = function(callback) {
- this.detectBroadcastCallback_ = callback;
-};
-
-/**
* Method to handle messages from the whispernet NaCl wrapper.
* @param {Event} e Event from the whispernet wrapper.
* @private
@@ -180,8 +153,6 @@ WhisperDecoder.prototype.onNaclMessage_ = function(e) {
var msg = e.data;
if (msg.type == 'decode_tokens_response') {
this.handleCandidates_(msg.tokens, msg.audible);
- } else if (msg.type == 'detect_broadcast_response') {
- this.detectBroadcastCallback_(msg.detected);
}
};
@@ -194,7 +165,7 @@ WhisperDecoder.prototype.onNaclMessage_ = function(e) {
* @private
*/
WhisperDecoder.prototype.handleCandidates_ = function(candidates, audible) {
- if (!this.tokenCallback_ || !candidates || candidates.length == 0)
+ if (!candidates || candidates.length == 0)
return;
var returnCandidates = [];
@@ -202,6 +173,5 @@ WhisperDecoder.prototype.handleCandidates_ = function(candidates, audible) {
returnCandidates[i] = { token: bytesToBase64(candidates[i]),
audible: audible };
}
- this.tokenCallback_(returnCandidates);
+ chrome.copresencePrivate.sendFound(this.clientId_, returnCandidates);
};
-
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 1d9fda9..caf0fc3 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -110,6 +110,8 @@
'browser/extensions/activity_log/uma_policy.h',
'browser/extensions/api/activity_log_private/activity_log_private_api.cc',
'browser/extensions/api/activity_log_private/activity_log_private_api.h',
+ 'browser/extensions/api/audio_modem/audio_modem_api.cc',
+ 'browser/extensions/api/audio_modem/audio_modem_api.h',
'browser/extensions/api/automation_internal/automation_action_adapter.h',
'browser/extensions/api/automation_internal/automation_internal_api.cc',
'browser/extensions/api/automation_internal/automation_internal_api.h',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index f561266..b76008a 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -657,6 +657,7 @@
'browser/extensions/activity_log/hashed_ad_network_database_unittest.cc',
'browser/extensions/activity_log/uma_policy_unittest.cc',
'browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc',
+ 'browser/extensions/api/audio_modem/audio_modem_api_unittest.cc',
'browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc',
'browser/extensions/api/content_settings/content_settings_store_unittest.cc',
'browser/extensions/api/content_settings/content_settings_unittest.cc',
@@ -2165,6 +2166,7 @@
'sources': [ '<@(chrome_unit_tests_extensions_sources)' ],
'dependencies': [
'common/extensions/api/api.gyp:chrome_api',
+ '../components/components.gyp:audio_modem_test_support',
'../extensions/extensions_resources.gyp:extensions_resources',
'../extensions/extensions_strings.gyp:extensions_strings',
],
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 17947a2..cba0f94 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -75,6 +75,10 @@
"contexts": ["blessed_extension", "unblessed_extension", "content_script"],
"matches": []
},
+ "audioModem": {
+ "dependencies": ["permission:audioModem"],
+ "contexts": ["blessed_extension"]
+ },
"automationInternal": {
"internal": true,
"dependencies": ["manifest:automation"],
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 6b11547..e684aeb 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -49,6 +49,23 @@
"5107DE9024C329EEA9C9A72D94C16723790C6422" // Apps Developer Tool Dev.
]
},
+ "audioModem": [
+ {
+ "channel": "dev",
+ "extension_types": ["extension", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "whitelist": [
+ "05EBA3051DFCA6AF17070AEE5FE8C66322FF4738", // http://crbug.com/431978
+ "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2", // http://crbug.com/458218
+ "0EFB879311E9EFBB7C45251F89EC655711B1F6ED", // http://crbug.com/458218
+ "9193D3A51E2FE33B496CDA53EA330423166E7F02", // http://crbug.com/458218
+ "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89" // http://crbug.com/458218
+ ]
+ }
+ ],
"autotestPrivate": {
"channel": "stable",
"extension_types": ["extension", "legacy_packaged_app"],
diff --git a/chrome/common/extensions/api/audio_modem.idl b/chrome/common/extensions/api/audio_modem.idl
new file mode 100644
index 0000000..a306f7c
--- /dev/null
+++ b/chrome/common/extensions/api/audio_modem.idl
@@ -0,0 +1,86 @@
+// 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.
+
+// Use the <code>chrome.audio_modem</code> API
+// to transmit and receive short tokens over audio.
+namespace audioModem {
+ // The audio bands supported.
+ enum Audioband {
+ // Audible (up to 3 kHz)
+ audible,
+ // Inaudible (18-20 kHz)
+ inaudible
+ };
+
+ // Details for how a token is encoded in audio.
+ dictionary TokenEncoding {
+ // The length of the tokens to transmit, in bytes.
+ // For now, apps must always use the same token length.
+ long tokenLength;
+ // Whether to use a 2-byte CRC checksum. Defaults to false.
+ boolean? crc;
+ // Whether to use a parity symbol. Defaults to false.
+ boolean? parity;
+ };
+
+ // Details of a transmit or receive request.
+ dictionary RequestParams {
+ // How long to transmit or receive for.
+ // The timeout has a maximum of 10 minutes for transmit,
+ // or 1 hour for receive.
+ long timeoutMillis;
+ // The audio band to use.
+ Audioband band;
+ // The token encoding details.
+ TokenEncoding encoding;
+ };
+
+ // Results of token decoding.
+ dictionary ReceivedToken {
+ // The token contents in raw bytes.
+ ArrayBuffer token;
+ // The audio band the token was heard on.
+ Audioband band;
+ };
+
+ // The result of a requested operation.
+ enum Status {
+ // The requested operation was processed successfully.
+ success,
+ // The request was invalid. See chrome.runtime.lastError for details.
+ invalidRequest,
+ // The requested audio band is already in use by another client.
+ // Eventually, simultaneous tokens will be time-sliced,
+ // and this error will no longer occur.
+ inUse,
+ // Audio encoding or decoding failed.
+ coderError
+ };
+
+ // A callback to report the status of a request.
+ callback StatusCallback = void(Status status);
+
+ interface Functions {
+ // Transmit a token. Only one can be transmitted at a time.
+ // Transmission of any previous tokens (by this app) will stop.
+ static void transmit(
+ RequestParams params, ArrayBuffer token, StatusCallback callback);
+ // Stop any active transmission on the specified band.
+ static void stopTransmit(Audioband band, StatusCallback callback);
+ // Start listening for audio tokens. For now,
+ // only one app will be able to listen at a time.
+ static void receive(RequestParams params, StatusCallback callback);
+ // Stop any active listening on the specified band.
+ static void stopReceive(Audioband band, StatusCallback callback);
+ };
+
+ interface Events {
+ // Audio tokens have been received.
+ static void onReceived(ReceivedToken[] tokens);
+ // Transmit could not be confirmed.
+ // The speaker volume might be too low.
+ static void onTransmitFail(Audioband band);
+ };
+};
+
diff --git a/chrome/common/extensions/api/copresence_private.idl b/chrome/common/extensions/api/copresence_private.idl
index 691cdab..6db3420 100644
--- a/chrome/common/extensions/api/copresence_private.idl
+++ b/chrome/common/extensions/api/copresence_private.idl
@@ -10,19 +10,26 @@ namespace copresencePrivate {
boolean audible;
};
+ dictionary TokenParameters {
+ long length;
+ boolean crc;
+ boolean parity;
+ };
+
dictionary DecodeSamplesParameters {
ArrayBuffer samples;
boolean decodeAudible;
boolean decodeInaudible;
- long audibleTokenLength;
- long inaudibleTokenLength;
+ TokenParameters audibleTokenParams;
+ TokenParameters inaudibleTokenParams;
};
dictionary EncodeTokenParameters {
Token token;
long repetitions;
+ TokenParameters tokenParams;
};
dictionary AudioParameters {
@@ -35,22 +42,26 @@ namespace copresencePrivate {
interface Functions {
// Send a boolean indicating whether our initialization was successful.
static void sendInitialized(boolean success);
+
// Sends an array of found tokens to Chrome.
- static void sendFound(Token[] tokens);
+ static void sendFound(DOMString clientId, Token[] tokens);
+
// Send an array buffer of samples encoded for the specified token.
- static void sendSamples(Token token, ArrayBuffer samples);
- // Send a boolean indicating whether we detected a broadcast or not.
- static void sendDetect(boolean detected);
+ static void sendSamples(DOMString clientId,
+ Token token,
+ ArrayBuffer samples);
};
interface Events {
// Fired to request audio configuration of the whisper.net library.
- static void onConfigAudio(AudioParameters audioParams);
+ static void onConfigAudio(DOMString clientId, AudioParameters audioParams);
+
// Fired to request encoding of the given token.
- static void onEncodeTokenRequest(EncodeTokenParameters encodeParams);
+ static void onEncodeTokenRequest(DOMString clientId,
+ EncodeTokenParameters encodeParams);
+
// Fired when we have new samples to decode.
- static void onDecodeSamplesRequest(DecodeSamplesParameters decodeParams);
- // Fired to request a DetectBroadcast.
- static void onDetectBroadcastRequest();
+ static void onDecodeSamplesRequest(DOMString clientId,
+ DecodeSamplesParameters decodeParams);
};
};
diff --git a/chrome/common/extensions/api/schemas.gypi b/chrome/common/extensions/api/schemas.gypi
index f8ac601..f29dbc6 100644
--- a/chrome/common/extensions/api/schemas.gypi
+++ b/chrome/common/extensions/api/schemas.gypi
@@ -11,6 +11,7 @@
'accessibility_features.json',
'accessibility_private.json',
'activity_log_private.json',
+ 'audio_modem.idl',
'automation.idl',
'automation_internal.idl',
'autotest_private.idl',
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index bec99e9..0e50e6e 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -86,6 +86,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions()
"accessibilityPrivate",
APIPermissionInfo::kFlagCannotBeOptional},
{APIPermission::kActiveTab, "activeTab"},
+ {APIPermission::kAudioModem, "audioModem"},
{APIPermission::kBookmark,
"bookmarks",
APIPermissionInfo::kFlagNone,
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index dae5a7d..d4bfb1d 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -641,6 +641,7 @@ TEST(PermissionsTest, PermissionMessages) {
skip.insert(APIPermission::kAlwaysOnTopWindows);
skip.insert(APIPermission::kAppView);
skip.insert(APIPermission::kAudio);
+ skip.insert(APIPermission::kAudioModem);
skip.insert(APIPermission::kBrowsingData);
skip.insert(APIPermission::kCastStreaming);
skip.insert(APIPermission::kCommandsAccessibility);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2c5cb50..49909a8 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1220,6 +1220,7 @@ if (!is_android && (!is_win || link_chrome_on_windows)) {
if (!is_ios) {
deps += [
+ "//components/audio_modem:audio_modem_test_support",
"//components/autofill/content/browser:test_support",
"//components/metrics/proto",
"//components/data_reduction_proxy/core/browser:test_support",
diff --git a/components/audio_modem.gypi b/components/audio_modem.gypi
index 79d2dbe..7d2219b 100644
--- a/components/audio_modem.gypi
+++ b/components/audio_modem.gypi
@@ -43,6 +43,8 @@
'sources': [
'audio_modem/test/random_samples.cc',
'audio_modem/test/random_samples.h',
+ 'audio_modem/test/stub_modem.cc',
+ 'audio_modem/test/stub_modem.h',
'audio_modem/test/stub_whispernet_client.cc',
'audio_modem/test/stub_whispernet_client.h',
],
diff --git a/components/audio_modem/BUILD.gn b/components/audio_modem/BUILD.gn
index 6676e64..93c9fd6 100644
--- a/components/audio_modem/BUILD.gn
+++ b/components/audio_modem/BUILD.gn
@@ -27,3 +27,19 @@ static_library("audio_modem") {
"//third_party/webrtc/common_audio",
]
}
+
+static_library("audio_modem_test_support") {
+ sources = [
+ "test/random_samples.cc",
+ "test/random_samples.h",
+ "test/stub_modem.cc",
+ "test/stub_modem.h",
+ "test/stub_whispernet_client.cc",
+ "test/stub_whispernet_client.h",
+ ]
+
+ deps = [
+ "//base",
+ "//media",
+ ]
+}
diff --git a/components/audio_modem/modem_impl.cc b/components/audio_modem/modem_impl.cc
index 293b1f9..23d75d0 100644
--- a/components/audio_modem/modem_impl.cc
+++ b/components/audio_modem/modem_impl.cc
@@ -92,8 +92,6 @@ ModemImpl::ModemImpl() : client_(nullptr), recorder_(nullptr) {
switches::kAudioModemEnableInaudibleBroadcast, true);
player_[AUDIBLE] = nullptr;
player_[INAUDIBLE] = nullptr;
- token_length_[0] = 0;
- token_length_[1] = 0;
samples_caches_.resize(2);
samples_caches_[AUDIBLE] = new SamplesMap(kMaxSamples);
@@ -191,21 +189,21 @@ void ModemImpl::StopRecording(AudioType type) {
}
void ModemImpl::SetToken(AudioType type,
- const std::string& url_safe_token) {
+ const std::string& url_safe_token) {
DCHECK(type == AUDIBLE || type == INAUDIBLE);
std::string token = FromUrlSafe(url_safe_token);
if (samples_caches_[type]->Get(token) == samples_caches_[type]->end()) {
- client_->EncodeToken(token, type);
+ client_->EncodeToken(token, type, token_params_);
} else {
UpdateToken(type, token);
}
}
-const std::string ModemImpl::GetToken(AudioType type) {
+const std::string ModemImpl::GetToken(AudioType type) const {
return playing_token_[type];
}
-bool ModemImpl::IsPlayingTokenHeard(AudioType type) {
+bool ModemImpl::IsPlayingTokenHeard(AudioType type) const {
base::TimeDelta tokenTimeout =
base::TimeDelta::FromMilliseconds(kTokenTimeoutMs);
@@ -217,8 +215,12 @@ bool ModemImpl::IsPlayingTokenHeard(AudioType type) {
return base::Time::Now() - heard_own_token_[type] < tokenTimeout;
}
-void ModemImpl::SetTokenLength(AudioType type, size_t token_length) {
- token_length_[type] = token_length;
+void ModemImpl::SetTokenParams(AudioType type, const TokenParameters& params) {
+ DCHECK_GT(params.length, 0u);
+ token_params_[type] = params;
+
+ // TODO(ckehoe): Make whispernet handle different token lengths
+ // simultaneously without reinitializing the decoder over and over.
}
// static
@@ -294,11 +296,11 @@ void ModemImpl::DecodeSamplesConnector(const std::string& samples) {
should_be_recording_[INAUDIBLE] || should_be_playing_[INAUDIBLE];
if (decode_audible && decode_inaudible) {
- client_->DecodeSamples(BOTH, samples, token_length_);
+ client_->DecodeSamples(BOTH, samples, token_params_);
} else if (decode_audible) {
- client_->DecodeSamples(AUDIBLE, samples, token_length_);
+ client_->DecodeSamples(AUDIBLE, samples, token_params_);
} else if (decode_inaudible) {
- client_->DecodeSamples(INAUDIBLE, samples, token_length_);
+ client_->DecodeSamples(INAUDIBLE, samples, token_params_);
}
}
diff --git a/components/audio_modem/modem_impl.h b/components/audio_modem/modem_impl.h
index 108d4bb..ec6beb5 100644
--- a/components/audio_modem/modem_impl.h
+++ b/components/audio_modem/modem_impl.h
@@ -45,9 +45,9 @@ class ModemImpl final : public Modem {
void StartRecording(AudioType type) override;
void StopRecording(AudioType type) override;
void SetToken(AudioType type, const std::string& url_safe_token) override;
- const std::string GetToken(AudioType type) override;
- bool IsPlayingTokenHeard(AudioType type) override;
- void SetTokenLength(AudioType type, size_t token_length) override;
+ const std::string GetToken(AudioType type) const override;
+ bool IsPlayingTokenHeard(AudioType type) const override;
+ void SetTokenParams(AudioType type, const TokenParameters& params) override;
void set_player_for_testing(AudioType type, AudioPlayer* player) {
player_[type] = player;
@@ -108,7 +108,7 @@ class ModemImpl final : public Modem {
// Indexed using enum AudioType.
std::string playing_token_[2];
- size_t token_length_[2];
+ TokenParameters token_params_[2];
base::Time started_playing_[2];
base::Time heard_own_token_[2];
diff --git a/components/audio_modem/public/audio_modem_types.h b/components/audio_modem/public/audio_modem_types.h
index 32c6757..036a6a1 100644
--- a/components/audio_modem/public/audio_modem_types.h
+++ b/components/audio_modem/public/audio_modem_types.h
@@ -50,6 +50,22 @@ struct AudioToken final {
bool audible;
};
+// Struct to hold the encoding parameters for tokens.
+// Parity is on by default.
+struct TokenParameters {
+ TokenParameters() : TokenParameters(0, false, true) {}
+
+ explicit TokenParameters(size_t length)
+ : TokenParameters(length, false, true) {}
+
+ TokenParameters(size_t length, bool crc, bool parity)
+ : length(length), crc(crc), parity(parity) {}
+
+ size_t length;
+ bool crc;
+ bool parity;
+};
+
// Callback to pass around found tokens.
using TokensCallback = base::Callback<void(const std::vector<AudioToken>&)>;
diff --git a/components/audio_modem/public/modem.h b/components/audio_modem/public/modem.h
index 304614c..09ab399 100644
--- a/components/audio_modem/public/modem.h
+++ b/components/audio_modem/public/modem.h
@@ -31,11 +31,12 @@ class Modem {
virtual void SetToken(AudioType type,
const std::string& url_safe_token) = 0;
- virtual const std::string GetToken(AudioType type) = 0;
+ virtual const std::string GetToken(AudioType type) const = 0;
- virtual bool IsPlayingTokenHeard(AudioType type) = 0;
+ virtual bool IsPlayingTokenHeard(AudioType type) const = 0;
- virtual void SetTokenLength(AudioType type, size_t token_length) = 0;
+ virtual void SetTokenParams(AudioType type,
+ const TokenParameters& params) = 0;
static scoped_ptr<Modem> Create();
};
diff --git a/components/audio_modem/public/whispernet_client.h b/components/audio_modem/public/whispernet_client.h
index c7219c8..48d1efa 100644
--- a/components/audio_modem/public/whispernet_client.h
+++ b/components/audio_modem/public/whispernet_client.h
@@ -34,27 +34,24 @@ class WhispernetClient {
virtual void Initialize(const SuccessCallback& init_callback) = 0;
// Fires an event to request a token encode.
- virtual void EncodeToken(const std::string& token, AudioType type) = 0;
+ virtual void EncodeToken(const std::string& token,
+ AudioType type,
+ const TokenParameters token_params[2]) = 0;
// Fires an event to request a decode for the given samples.
virtual void DecodeSamples(AudioType type,
const std::string& samples,
- const size_t token_length[2]) = 0;
- // Fires an event to request detection of a whispernet broadcast.
- virtual void DetectBroadcast() = 0;
+ const TokenParameters token_params[2]) = 0;
// Callback registration methods. The modem will set these to receive data.
virtual void RegisterTokensCallback(
const TokensCallback& tokens_callback) = 0;
virtual void RegisterSamplesCallback(
const SamplesCallback& samples_callback) = 0;
- virtual void RegisterDetectBroadcastCallback(
- const SuccessCallback& db_callback) = 0;
// Don't cache these callbacks, as they may become invalid at any time.
// Always invoke callbacks directly through these accessors.
virtual TokensCallback GetTokensCallback() = 0;
virtual SamplesCallback GetSamplesCallback() = 0;
- virtual SuccessCallback GetDetectBroadcastCallback() = 0;
virtual SuccessCallback GetInitializedCallback() = 0;
virtual ~WhispernetClient() {}
diff --git a/components/audio_modem/test/stub_modem.cc b/components/audio_modem/test/stub_modem.cc
new file mode 100644
index 0000000..ea0fb08
--- /dev/null
+++ b/components/audio_modem/test/stub_modem.cc
@@ -0,0 +1,69 @@
+// 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 "components/audio_modem/test/stub_modem.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+
+namespace audio_modem {
+
+StubModem::StubModem() {
+ playing_[AUDIBLE] = false;
+ playing_[INAUDIBLE] = false;
+ recording_[AUDIBLE] = false;
+ recording_[INAUDIBLE] = false;
+}
+
+StubModem::~StubModem() {}
+
+void StubModem::Initialize(WhispernetClient* whispernet_client,
+ const TokensCallback& tokens_cb) {
+ tokens_callback_ = tokens_cb;
+}
+
+void StubModem::StartPlaying(AudioType type) {
+ playing_[type] = true;
+}
+
+void StubModem::StopPlaying(AudioType type) {
+ playing_[type] = false;
+}
+
+void StubModem::StartRecording(AudioType type) {
+ recording_[type] = true;
+}
+
+void StubModem::StopRecording(AudioType type) {
+ recording_[type] = false;
+}
+
+const std::string StubModem::GetToken(AudioType type) const {
+ return std::string();
+}
+
+bool StubModem::IsPlayingTokenHeard(AudioType type) const {
+ return false;
+}
+
+bool StubModem::IsRecording(AudioType type) const {
+ return recording_[type];
+}
+
+bool StubModem::IsPlaying(AudioType type) const {
+ return playing_[type];
+}
+
+void StubModem::DeliverTokens(const std::vector<AudioToken>& tokens) {
+ std::vector<AudioToken> encoded_tokens;
+ for (const AudioToken& token : tokens) {
+ std::string encoded_token;
+ base::Base64Encode(token.token, & encoded_token);
+ encoded_tokens.push_back(AudioToken(encoded_token, token.audible));
+ }
+ DCHECK_EQ(tokens.size(), encoded_tokens.size());
+ tokens_callback_.Run(encoded_tokens);
+}
+
+} // namespace audio_modem
diff --git a/components/audio_modem/test/stub_modem.h b/components/audio_modem/test/stub_modem.h
new file mode 100644
index 0000000..e76f5e3
--- /dev/null
+++ b/components/audio_modem/test/stub_modem.h
@@ -0,0 +1,51 @@
+// 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 COMPONENTS_AUDIO_MODEM_TEST_STUB_MODEM_H_
+#define COMPONENTS_AUDIO_MODEM_TEST_STUB_MODEM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "components/audio_modem/public/modem.h"
+
+namespace audio_modem {
+
+class StubModem final : public Modem {
+ public:
+ StubModem();
+ ~StubModem() override;
+
+ // AudioManager overrides:
+ void Initialize(WhispernetClient* whispernet_client,
+ const TokensCallback& tokens_cb) override;
+ void StartPlaying(AudioType type) override;
+ void StopPlaying(AudioType type) override;
+ void StartRecording(AudioType type) override;
+ void StopRecording(AudioType type) override;
+ void SetToken(AudioType type, const std::string& url_unsafe_token) override {}
+ const std::string GetToken(AudioType type) const override;
+ bool IsPlayingTokenHeard(AudioType type) const override;
+ void SetTokenParams(AudioType type, const TokenParameters& params) override {}
+
+ bool IsRecording(AudioType type) const;
+ bool IsPlaying(AudioType type) const;
+
+ // Encodes tokens as base64 and then delivers them.
+ void DeliverTokens(const std::vector<AudioToken>& tokens);
+
+ private:
+ // Indexed using enum AudioType.
+ bool playing_[2];
+ bool recording_[2];
+
+ TokensCallback tokens_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(StubModem);
+};
+
+} // namespace audio_modem
+
+#endif // COMPONENTS_AUDIO_MODEM_TEST_STUB_MODEM_H_
diff --git a/components/audio_modem/test/stub_whispernet_client.cc b/components/audio_modem/test/stub_whispernet_client.cc
index 9b0a50c..e2763ce 100644
--- a/components/audio_modem/test/stub_whispernet_client.cc
+++ b/components/audio_modem/test/stub_whispernet_client.cc
@@ -18,14 +18,16 @@ StubWhispernetClient::~StubWhispernetClient() {}
void StubWhispernetClient::Initialize(const SuccessCallback& init_callback) {}
void StubWhispernetClient::EncodeToken(const std::string& token,
- AudioType type) {
+ AudioType type,
+ const TokenParameters token_params[2]) {
if (!samples_cb_.is_null())
samples_cb_.Run(type, token, samples_);
}
-void StubWhispernetClient::DecodeSamples(AudioType type,
- const std::string& samples,
- const size_t token_length[2]) {
+void StubWhispernetClient::DecodeSamples(
+ AudioType type,
+ const std::string& samples,
+ const TokenParameters token_params[2]) {
if (!tokens_cb_.is_null())
tokens_cb_.Run(tokens_);
}
@@ -48,10 +50,6 @@ SamplesCallback StubWhispernetClient::GetSamplesCallback() {
return samples_cb_;
}
-SuccessCallback StubWhispernetClient::GetDetectBroadcastCallback() {
- return SuccessCallback();
-}
-
SuccessCallback StubWhispernetClient::GetInitializedCallback() {
return SuccessCallback();
}
diff --git a/components/audio_modem/test/stub_whispernet_client.h b/components/audio_modem/test/stub_whispernet_client.h
index 3225863c..9a0e8c6 100644
--- a/components/audio_modem/test/stub_whispernet_client.h
+++ b/components/audio_modem/test/stub_whispernet_client.h
@@ -30,20 +30,18 @@ class StubWhispernetClient final : public WhispernetClient {
void Initialize(const SuccessCallback& init_callback) override;
- void EncodeToken(const std::string& token, AudioType type) override;
+ void EncodeToken(const std::string& token,
+ AudioType type,
+ const TokenParameters token_params[2]) override;
void DecodeSamples(AudioType type,
const std::string& samples,
- const size_t token_length[2]) override;
- void DetectBroadcast() override {}
+ const TokenParameters token_params[2]) override;
void RegisterTokensCallback(const TokensCallback& tokens_cb) override;
void RegisterSamplesCallback(const SamplesCallback& samples_cb) override;
- void RegisterDetectBroadcastCallback(
- const SuccessCallback& /* db_cb */) override {}
TokensCallback GetTokensCallback() override;
SamplesCallback GetSamplesCallback() override;
- SuccessCallback GetDetectBroadcastCallback() override;
SuccessCallback GetInitializedCallback() override;
private:
diff --git a/components/copresence/handlers/audio/audio_directive_handler_impl.cc b/components/copresence/handlers/audio/audio_directive_handler_impl.cc
index 16b0269..d91b4d8 100644
--- a/components/copresence/handlers/audio/audio_directive_handler_impl.cc
+++ b/components/copresence/handlers/audio/audio_directive_handler_impl.cc
@@ -22,6 +22,7 @@
using audio_modem::AUDIBLE;
using audio_modem::INAUDIBLE;
+using audio_modem::TokenParameters;
namespace copresence {
@@ -102,12 +103,13 @@ void AudioDirectiveHandlerImpl::AddInstruction(
DCHECK_GT(token_length, 0u);
switch (instruction.medium()) {
case AUDIO_ULTRASOUND_PASSBAND:
- audio_modem_->SetTokenLength(INAUDIBLE, token_length);
+ audio_modem_->SetTokenParams(INAUDIBLE,
+ TokenParameters(token_length));
transmits_lists_[INAUDIBLE]->AddDirective(op_id, directive);
audio_modem_->SetToken(INAUDIBLE, instruction.token_id());
break;
case AUDIO_AUDIBLE_DTMF:
- audio_modem_->SetTokenLength(AUDIBLE, token_length);
+ audio_modem_->SetTokenParams(AUDIBLE, TokenParameters(token_length));
transmits_lists_[AUDIBLE]->AddDirective(op_id, directive);
audio_modem_->SetToken(AUDIBLE, instruction.token_id());
break;
@@ -123,11 +125,12 @@ void AudioDirectiveHandlerImpl::AddInstruction(
DCHECK_GT(token_length, 0u);
switch (instruction.medium()) {
case AUDIO_ULTRASOUND_PASSBAND:
- audio_modem_->SetTokenLength(INAUDIBLE, token_length);
+ audio_modem_->SetTokenParams(INAUDIBLE,
+ TokenParameters(token_length));
receives_lists_[INAUDIBLE]->AddDirective(op_id, directive);
break;
case AUDIO_AUDIBLE_DTMF:
- audio_modem_->SetTokenLength(AUDIBLE, token_length);
+ audio_modem_->SetTokenParams(AUDIBLE, TokenParameters(token_length));
receives_lists_[AUDIBLE]->AddDirective(op_id, directive);
break;
default:
diff --git a/components/copresence/handlers/audio/audio_directive_handler_unittest.cc b/components/copresence/handlers/audio/audio_directive_handler_unittest.cc
index d414dd2..24da3ca 100644
--- a/components/copresence/handlers/audio/audio_directive_handler_unittest.cc
+++ b/components/copresence/handlers/audio/audio_directive_handler_unittest.cc
@@ -12,6 +12,7 @@
#include "base/timer/mock_timer.h"
#include "components/audio_modem/public/modem.h"
#include "components/audio_modem/test/random_samples.h"
+#include "components/audio_modem/test/stub_modem.h"
#include "components/copresence/handlers/audio/audio_directive_handler_impl.h"
#include "components/copresence/handlers/audio/tick_clock_ref_counted.h"
#include "components/copresence/proto/data.pb.h"
@@ -20,6 +21,7 @@
using audio_modem::AUDIBLE;
using audio_modem::AudioType;
using audio_modem::INAUDIBLE;
+using audio_modem::StubModem;
namespace copresence {
@@ -39,34 +41,6 @@ const Directive CreateDirective(TokenInstructionType type,
} // namespace
-class StubModem final : public audio_modem::Modem {
- public:
- StubModem() {}
- ~StubModem() override {}
-
- // AudioManager overrides:
- void Initialize(audio_modem::WhispernetClient* whispernet_client,
- const audio_modem::TokensCallback& tokens_cb) override {}
- void StartPlaying(AudioType type) override { playing_[type] = true; }
- void StopPlaying(AudioType type) override { playing_[type] = false; }
- void StartRecording(AudioType type) override { recording_[type] = true; }
- void StopRecording(AudioType type) override { recording_[type] = false; }
- void SetToken(AudioType type, const std::string& url_unsafe_token) override {}
- const std::string GetToken(AudioType type) override { return std::string(); }
- bool IsPlayingTokenHeard(AudioType type) override { return false; }
- void SetTokenLength(AudioType type, size_t token_length) override {}
-
- bool IsRecording(AudioType type) { return recording_[type]; }
- bool IsPlaying(AudioType type) { return playing_[type]; }
-
- private:
- // Indexed using enum AudioType.
- bool playing_[2];
- bool recording_[2];
-
- DISALLOW_COPY_AND_ASSIGN(StubModem);
-};
-
class AudioDirectiveHandlerTest : public testing::Test {
public:
AudioDirectiveHandlerTest() {
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index d5ec166..416a678 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1022,6 +1022,10 @@ enum HistogramValue {
WEBVIEWINTERNAL_SETALLOWSCALING,
PLATFORMKEYSINTERNAL_GETPUBLICKEY,
RUNTIME_OPENOPTIONSPAGE,
+ AUDIOMODEM_TRANSMIT,
+ AUDIOMODEM_STOPTRANSMIT,
+ AUDIOMODEM_RECEIVE,
+ AUDIOMODEM_STOPRECEIVE,
// Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms.xml.
ENUM_BOUNDARY
diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h
index c7acd83..9c6712d 100644
--- a/extensions/common/permissions/api_permission.h
+++ b/extensions/common/permissions/api_permission.h
@@ -8,6 +8,7 @@
#include <map>
#include <set>
#include <string>
+#include <vector>
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
@@ -54,6 +55,7 @@ class APIPermission {
kAppView,
kAudio,
kAudioCapture,
+ kAudioModem,
kAutomation,
kAutoTestPrivate,
kBackground,
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 2ef1fb2..62c0d40 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -48211,6 +48211,10 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="961" label="WEBVIEWINTERNAL_SETALLOWSCALING"/>
<int value="962" label="PLATFORMKEYSINTERNAL_GETPUBLICKEY"/>
<int value="963" label="RUNTIME_OPENOPTIONSPAGE"/>
+ <int value="964" label="AUDIOMODEM_TRANSMIT"/>
+ <int value="965" label="AUDIOMODEM_STOPTRANSMIT"/>
+ <int value="966" label="AUDIOMODEM_RECEIVE"/>
+ <int value="967" label="AUDIOMODEM_STOPRECEIVE"/>
</enum>
<enum name="ExtensionInstallCause" type="int">