Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / audio_modem / audio_modem_api.cc
blob572979ce27a7b306ccd6599067b5d78e066ce4e4
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <map>
6 #include <string>
7 #include <vector>
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/guid.h"
12 #include "base/lazy_instance.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/timer/timer.h"
18 #include "chrome/browser/copresence/chrome_whispernet_client.h"
19 #include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h"
20 #include "chrome/common/extensions/api/audio_modem.h"
21 #include "extensions/browser/event_router.h"
23 // TODO(ckehoe): Implement transmit fail checking.
25 namespace extensions {
27 using api::audio_modem::AUDIOBAND_AUDIBLE;
28 using api::audio_modem::AUDIOBAND_INAUDIBLE;
29 using api::audio_modem::Audioband;
30 using api::audio_modem::STATUS_CODERERROR;
31 using api::audio_modem::STATUS_INUSE;
32 using api::audio_modem::STATUS_INVALIDREQUEST;
33 using api::audio_modem::STATUS_SUCCESS;
34 using api::audio_modem::ReceivedToken;
35 using api::audio_modem::RequestParams;
36 using api::audio_modem::Status;
38 namespace Transmit = api::audio_modem::Transmit;
39 namespace StopTransmit = api::audio_modem::StopTransmit;
40 namespace Receive = api::audio_modem::Receive;
41 namespace StopReceive = api::audio_modem::StopReceive;
42 namespace OnReceived = api::audio_modem::OnReceived;
44 using audio_modem::AUDIBLE;
45 using audio_modem::AudioToken;
46 using audio_modem::AudioType;
47 using audio_modem::INAUDIBLE;
48 using audio_modem::TokenParameters;
50 namespace {
52 const char kInitFailedError[] = "The audio modem is not available. "
53 "Failed to initialize the token encoder/decoder.";
54 const char kInvalidTokenLengthError[] =
55 "The token length must be greater than zero.";
56 const char kIncorrectTokenLengthError[] =
57 "The token provided did not match the declared token length.";
58 const char kInvalidTimeoutError[] =
59 "Transmit and receive timeouts must be greater than zero.";
61 const int kMaxTransmitTimeout = 10 * 60 * 1000; // 10 minutes
62 const int kMaxReceiveTimeout = 60 * 60 * 1000; // 1 hour
64 base::LazyInstance<BrowserContextKeyedAPIFactory<AudioModemAPI>>
65 g_factory = LAZY_INSTANCE_INITIALIZER;
67 AudioType AudioTypeForBand(Audioband band) {
68 switch (band) {
69 case AUDIOBAND_AUDIBLE:
70 return AUDIBLE;
71 case AUDIOBAND_INAUDIBLE:
72 return INAUDIBLE;
73 default:
74 NOTREACHED();
75 return audio_modem::AUDIO_TYPE_UNKNOWN;
79 TokenParameters TokenParamsForEncoding(
80 const api::audio_modem::TokenEncoding& encoding) {
81 return TokenParameters(encoding.token_length,
82 encoding.crc ? *encoding.crc : false,
83 encoding.parity ? *encoding.parity : true);
86 const std::string DecodeBase64Token(std::string encoded_token) {
87 // Make sure the token is padded correctly.
88 while (encoded_token.size() % 4 > 0)
89 encoded_token += "=";
91 // Decode and return the token.
92 std::string raw_token;
93 bool decode_success = base::Base64Decode(encoded_token, &raw_token);
94 DCHECK(decode_success);
95 return raw_token;
98 } // namespace
101 // Public functions.
103 AudioModemAPI::AudioModemAPI(content::BrowserContext* context)
104 : AudioModemAPI(context,
105 make_scoped_ptr(new ChromeWhispernetClient(context)),
106 audio_modem::Modem::Create()) {}
108 AudioModemAPI::AudioModemAPI(
109 content::BrowserContext* context,
110 scoped_ptr<audio_modem::WhispernetClient> whispernet_client,
111 scoped_ptr<audio_modem::Modem> modem)
112 : browser_context_(context),
113 whispernet_client_(whispernet_client.Pass()),
114 modem_(modem.Pass()),
115 init_failed_(false) {
116 // We own these objects, so these callbacks will not outlive us.
117 whispernet_client_->Initialize(
118 base::Bind(&AudioModemAPI::WhispernetInitComplete,
119 base::Unretained(this)));
120 modem_->Initialize(whispernet_client_.get(),
121 base::Bind(&AudioModemAPI::TokensReceived,
122 base::Unretained(this)));
125 AudioModemAPI::~AudioModemAPI() {
126 for (const auto& timer_entry : receive_timers_[0])
127 delete timer_entry.second;
128 for (const auto& timer_entry : receive_timers_[1])
129 delete timer_entry.second;
132 Status AudioModemAPI::StartTransmit(const std::string& app_id,
133 const RequestParams& params,
134 const std::string& token) {
135 AudioType audio_type = AudioTypeForBand(params.band);
136 if (transmitters_[audio_type].empty())
137 transmitters_[audio_type] = app_id;
138 else if (transmitters_[audio_type] != app_id)
139 return STATUS_INUSE;
141 DVLOG(3) << "Starting transmit for app " << app_id;
143 std::string encoded_token;
144 base::Base64Encode(token, &encoded_token);
145 base::RemoveChars(encoded_token, "=", &encoded_token);
147 modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding));
148 modem_->SetToken(audio_type, encoded_token);
149 modem_->StartPlaying(audio_type);
151 transmit_timers_[audio_type].Start(
152 FROM_HERE,
153 base::TimeDelta::FromMilliseconds(params.timeout_millis),
154 base::Bind(base::IgnoreResult(&AudioModemAPI::StopTransmit),
155 base::Unretained(this),
156 app_id,
157 audio_type));
158 return STATUS_SUCCESS;
161 Status AudioModemAPI::StopTransmit(const std::string& app_id,
162 AudioType audio_type) {
163 if (transmitters_[audio_type] != app_id)
164 return STATUS_INVALIDREQUEST;
166 DVLOG(3) << "Stopping transmit for app " << app_id;
167 transmitters_[audio_type].clear();
168 modem_->StopPlaying(audio_type);
169 return STATUS_SUCCESS;
172 void AudioModemAPI::StartReceive(const std::string& app_id,
173 const RequestParams& params) {
174 DVLOG(3) << "Starting receive for app " << app_id;
176 AudioType audio_type = AudioTypeForBand(params.band);
177 modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding));
178 modem_->StartRecording(audio_type);
180 if (receive_timers_[audio_type].count(app_id) == 0)
181 receive_timers_[audio_type][app_id] = new base::OneShotTimer<AudioModemAPI>;
182 DCHECK(receive_timers_[audio_type][app_id]);
183 receive_timers_[audio_type][app_id]->Start(
184 FROM_HERE,
185 base::TimeDelta::FromMilliseconds(params.timeout_millis),
186 base::Bind(base::IgnoreResult(&AudioModemAPI::StopReceive),
187 base::Unretained(this),
188 app_id,
189 audio_type));
192 Status AudioModemAPI::StopReceive(const std::string& app_id,
193 AudioType audio_type) {
194 if (receive_timers_[audio_type].count(app_id) == 0)
195 return STATUS_INVALIDREQUEST;
197 DCHECK(receive_timers_[audio_type][app_id]);
198 delete receive_timers_[audio_type][app_id];
199 receive_timers_[audio_type].erase(app_id);
201 DVLOG(3) << "Stopping receive for app " << app_id;
202 if (receive_timers_[audio_type].empty())
203 modem_->StopRecording(audio_type);
204 return STATUS_SUCCESS;
207 // static
208 BrowserContextKeyedAPIFactory<AudioModemAPI>*
209 AudioModemAPI::GetFactoryInstance() {
210 return g_factory.Pointer();
214 // Private functions.
216 void AudioModemAPI::WhispernetInitComplete(bool success) {
217 if (success) {
218 VLOG(2) << "Whispernet initialized successfully.";
219 } else {
220 LOG(ERROR) << "Failed to initialize Whispernet!";
221 init_failed_ = true;
225 void AudioModemAPI::TokensReceived(const std::vector<AudioToken>& tokens) {
226 // Distribute the tokens to the appropriate app(s).
227 std::map<std::string, std::vector<linked_ptr<ReceivedToken>>> tokens_by_app;
228 for (const AudioToken& token : tokens) {
229 linked_ptr<ReceivedToken> api_token(new ReceivedToken);
230 const std::string& raw_token = DecodeBase64Token(token.token);
231 api_token->token.assign(raw_token.c_str(),
232 raw_token.c_str() + raw_token.size());
233 api_token->band = token.audible ? AUDIOBAND_AUDIBLE : AUDIOBAND_INAUDIBLE;
234 for (const auto& receiver :
235 receive_timers_[token.audible ? AUDIBLE : INAUDIBLE]) {
236 tokens_by_app[receiver.first].push_back(api_token);
240 // Send events to the appropriate app(s).
241 for (const auto& app_entry : tokens_by_app) {
242 const std::string& app_id = app_entry.first;
243 const auto& tokens = app_entry.second;
244 if (app_id.empty())
245 continue;
247 EventRouter::Get(browser_context_)
248 ->DispatchEventToExtension(
249 app_id, make_scoped_ptr(new Event(events::AUDIO_MODEM_ON_RECEIVED,
250 OnReceived::kEventName,
251 OnReceived::Create(tokens))));
256 // Functions outside the API scope.
258 template <>
259 void
260 BrowserContextKeyedAPIFactory<AudioModemAPI>::DeclareFactoryDependencies() {
261 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
264 ExtensionFunction::ResponseAction AudioModemTransmitFunction::Run() {
265 scoped_ptr<Transmit::Params> params(Transmit::Params::Create(*args_));
266 EXTENSION_FUNCTION_VALIDATE(params.get());
267 AudioModemAPI* api =
268 AudioModemAPI::GetFactoryInstance()->Get(browser_context());
269 if (api->init_failed()) {
270 return RespondNow(ErrorWithArguments(
271 Transmit::Results::Create(STATUS_CODERERROR),
272 kInitFailedError));
275 // Check the token length.
276 int token_length = params->params.encoding.token_length;
277 if (token_length <= 0) {
278 return RespondNow(ErrorWithArguments(
279 Transmit::Results::Create(STATUS_INVALIDREQUEST),
280 kInvalidTokenLengthError));
282 const char* token = vector_as_array(&params->token);
283 std::string token_str(token, params->token.size());
284 if (static_cast<int>(token_str.size()) != token_length) {
285 return RespondNow(ErrorWithArguments(
286 Transmit::Results::Create(STATUS_INVALIDREQUEST),
287 kIncorrectTokenLengthError));
290 // Check the timeout.
291 int64_t timeout_millis = params->params.timeout_millis;
292 if (timeout_millis <= 0) {
293 return RespondNow(ErrorWithArguments(
294 Transmit::Results::Create(STATUS_INVALIDREQUEST),
295 kInvalidTimeoutError));
297 if (timeout_millis > kMaxTransmitTimeout)
298 timeout_millis = kMaxTransmitTimeout;
300 // Start transmission.
301 Status status = api->StartTransmit(extension_id(), params->params, token_str);
302 return RespondNow(ArgumentList(Transmit::Results::Create(status)));
305 ExtensionFunction::ResponseAction AudioModemStopTransmitFunction::Run() {
306 scoped_ptr<StopTransmit::Params> params(StopTransmit::Params::Create(*args_));
307 EXTENSION_FUNCTION_VALIDATE(params.get());
309 Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context())
310 ->StopTransmit(extension_id(), AudioTypeForBand(params->band));
311 return RespondNow(ArgumentList(StopTransmit::Results::Create(status)));
314 ExtensionFunction::ResponseAction AudioModemReceiveFunction::Run() {
315 scoped_ptr<Receive::Params> params(Receive::Params::Create(*args_));
316 EXTENSION_FUNCTION_VALIDATE(params.get());
317 AudioModemAPI* api =
318 AudioModemAPI::GetFactoryInstance()->Get(browser_context());
319 if (api->init_failed()) {
320 return RespondNow(ErrorWithArguments(
321 Transmit::Results::Create(STATUS_CODERERROR),
322 kInitFailedError));
325 // Check the timeout.
326 int64_t timeout_millis = params->params.timeout_millis;
327 if (timeout_millis <= 0) {
328 return RespondNow(ErrorWithArguments(
329 Receive::Results::Create(STATUS_INVALIDREQUEST),
330 kInvalidTimeoutError));
332 if (timeout_millis > kMaxReceiveTimeout)
333 timeout_millis = kMaxReceiveTimeout;
335 // Start receiving.
336 api->StartReceive(extension_id(), params->params);
337 return RespondNow(ArgumentList(Receive::Results::Create(STATUS_SUCCESS)));
340 ExtensionFunction::ResponseAction AudioModemStopReceiveFunction::Run() {
341 scoped_ptr<StopReceive::Params> params(StopReceive::Params::Create(*args_));
342 EXTENSION_FUNCTION_VALIDATE(params.get());
344 Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context())
345 ->StopReceive(extension_id(), AudioTypeForBand(params->band));
346 return RespondNow(ArgumentList(StopReceive::Results::Create(status)));
349 } // namespace extensions