summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions')
-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
6 files changed, 921 insertions, 50 deletions
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);