1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/speech/google_streaming_remote_engine.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/rand_util.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/time/time.h" 15 #include "content/browser/speech/audio_buffer.h" 16 #include "content/browser/speech/proto/google_streaming_api.pb.h" 17 #include "content/public/common/speech_recognition_error.h" 18 #include "content/public/common/speech_recognition_result.h" 19 #include "google_apis/google_api_keys.h" 20 #include "net/base/escape.h" 21 #include "net/base/load_flags.h" 22 #include "net/url_request/http_user_agent_settings.h" 23 #include "net/url_request/url_fetcher.h" 24 #include "net/url_request/url_request_context.h" 25 #include "net/url_request/url_request_context_getter.h" 26 #include "net/url_request/url_request_status.h" 27 28 using net::URLFetcher; 29 30 namespace content { 31 namespace { 32 33 const char kWebServiceBaseUrl[] = 34 "https://www.google.com/speech-api/full-duplex/v1"; 35 const char kDownstreamUrl[] = "/down?"; 36 const char kUpstreamUrl[] = "/up?"; 37 const AudioEncoder::Codec kDefaultAudioCodec = AudioEncoder::CODEC_FLAC; 38 39 // This matches the maximum maxAlternatives value supported by the server. 40 const uint32 kMaxMaxAlternatives = 30; 41 42 // TODO(hans): Remove this and other logging when we don't need it anymore. 43 void DumpResponse(const std::string& response) { 44 DVLOG(1) << "------------"; 45 proto::SpeechRecognitionEvent event; 46 if (!event.ParseFromString(response)) { 47 DVLOG(1) << "Parse failed!"; 48 return; 49 } 50 if (event.has_status()) 51 DVLOG(1) << "STATUS\t" << event.status(); 52 for (int i = 0; i < event.result_size(); ++i) { 53 DVLOG(1) << "RESULT #" << i << ":"; 54 const proto::SpeechRecognitionResult& res = event.result(i); 55 if (res.has_final()) 56 DVLOG(1) << " FINAL:\t" << res.final(); 57 if (res.has_stability()) 58 DVLOG(1) << " STABILITY:\t" << res.stability(); 59 for (int j = 0; j < res.alternative_size(); ++j) { 60 const proto::SpeechRecognitionAlternative& alt = 61 res.alternative(j); 62 if (alt.has_confidence()) 63 DVLOG(1) << " CONFIDENCE:\t" << alt.confidence(); 64 if (alt.has_transcript()) 65 DVLOG(1) << " TRANSCRIPT:\t" << alt.transcript(); 66 } 67 } 68 } 69 70 } // namespace 71 72 const int GoogleStreamingRemoteEngine::kAudioPacketIntervalMs = 100; 73 const int GoogleStreamingRemoteEngine::kUpstreamUrlFetcherIdForTesting = 0; 74 const int GoogleStreamingRemoteEngine::kDownstreamUrlFetcherIdForTesting = 1; 75 const int GoogleStreamingRemoteEngine::kWebserviceStatusNoError = 0; 76 const int GoogleStreamingRemoteEngine::kWebserviceStatusErrorNoMatch = 5; 77 78 GoogleStreamingRemoteEngine::GoogleStreamingRemoteEngine( 79 net::URLRequestContextGetter* context) 80 : url_context_(context), 81 previous_response_length_(0), 82 got_last_definitive_result_(false), 83 is_dispatching_event_(false), 84 state_(STATE_IDLE) {} 85 86 GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {} 87 88 void GoogleStreamingRemoteEngine::SetConfig( 89 const SpeechRecognitionEngineConfig& config) { 90 config_ = config; 91 } 92 93 void GoogleStreamingRemoteEngine::StartRecognition() { 94 FSMEventArgs event_args(EVENT_START_RECOGNITION); 95 DispatchEvent(event_args); 96 } 97 98 void GoogleStreamingRemoteEngine::EndRecognition() { 99 FSMEventArgs event_args(EVENT_END_RECOGNITION); 100 DispatchEvent(event_args); 101 } 102 103 void GoogleStreamingRemoteEngine::TakeAudioChunk(const AudioChunk& data) { 104 FSMEventArgs event_args(EVENT_AUDIO_CHUNK); 105 event_args.audio_data = &data; 106 DispatchEvent(event_args); 107 } 108 109 void GoogleStreamingRemoteEngine::AudioChunksEnded() { 110 FSMEventArgs event_args(EVENT_AUDIO_CHUNKS_ENDED); 111 DispatchEvent(event_args); 112 } 113 114 void GoogleStreamingRemoteEngine::OnURLFetchComplete(const URLFetcher* source) { 115 const bool kResponseComplete = true; 116 DispatchHTTPResponse(source, kResponseComplete); 117 } 118 119 void GoogleStreamingRemoteEngine::OnURLFetchDownloadProgress( 120 const URLFetcher* source, int64 current, int64 total) { 121 const bool kPartialResponse = false; 122 DispatchHTTPResponse(source, kPartialResponse); 123 } 124 125 void GoogleStreamingRemoteEngine::DispatchHTTPResponse(const URLFetcher* source, 126 bool end_of_response) { 127 DCHECK(CalledOnValidThread()); 128 DCHECK(source); 129 const bool response_is_good = source->GetStatus().is_success() && 130 source->GetResponseCode() == 200; 131 std::string response; 132 if (response_is_good) 133 source->GetResponseAsString(&response); 134 const size_t current_response_length = response.size(); 135 136 DVLOG(1) << (source == downstream_fetcher_.get() ? "Downstream" : "Upstream") 137 << "HTTP, code: " << source->GetResponseCode() 138 << " length: " << current_response_length 139 << " eor: " << end_of_response; 140 141 // URLFetcher provides always the entire response buffer, but we are only 142 // interested in the fresh data introduced by the last chunk. Therefore, we 143 // drop the previous content we have already processed. 144 if (current_response_length != 0) { 145 DCHECK_GE(current_response_length, previous_response_length_); 146 response.erase(0, previous_response_length_); 147 previous_response_length_ = current_response_length; 148 } 149 150 if (!response_is_good && source == downstream_fetcher_.get()) { 151 DVLOG(1) << "Downstream error " << source->GetResponseCode(); 152 FSMEventArgs event_args(EVENT_DOWNSTREAM_ERROR); 153 DispatchEvent(event_args); 154 return; 155 } 156 if (!response_is_good && source == upstream_fetcher_.get()) { 157 DVLOG(1) << "Upstream error " << source->GetResponseCode() 158 << " EOR " << end_of_response; 159 FSMEventArgs event_args(EVENT_UPSTREAM_ERROR); 160 DispatchEvent(event_args); 161 return; 162 } 163 164 // Ignore incoming data on the upstream connection. 165 if (source == upstream_fetcher_.get()) 166 return; 167 168 DCHECK(response_is_good && source == downstream_fetcher_.get()); 169 170 // The downstream response is organized in chunks, whose size is determined 171 // by a 4 bytes prefix, transparently handled by the ChunkedByteBuffer class. 172 // Such chunks are sent by the speech recognition webservice over the HTTP 173 // downstream channel using HTTP chunked transfer (unrelated to our chunks). 174 // This function is called every time an HTTP chunk is received by the 175 // url fetcher. However there isn't any particular matching beween our 176 // protocol chunks and HTTP chunks, in the sense that a single HTTP chunk can 177 // contain a portion of one chunk or even more chunks together. 178 chunked_byte_buffer_.Append(response); 179 180 // A single HTTP chunk can contain more than one data chunk, thus the while. 181 while (chunked_byte_buffer_.HasChunks()) { 182 FSMEventArgs event_args(EVENT_DOWNSTREAM_RESPONSE); 183 event_args.response = chunked_byte_buffer_.PopChunk(); 184 DCHECK(event_args.response.get()); 185 DumpResponse(std::string(event_args.response->begin(), 186 event_args.response->end())); 187 DispatchEvent(event_args); 188 } 189 if (end_of_response) { 190 FSMEventArgs event_args(EVENT_DOWNSTREAM_CLOSED); 191 DispatchEvent(event_args); 192 } 193 } 194 195 bool GoogleStreamingRemoteEngine::IsRecognitionPending() const { 196 DCHECK(CalledOnValidThread()); 197 return state_ != STATE_IDLE; 198 } 199 200 int GoogleStreamingRemoteEngine::GetDesiredAudioChunkDurationMs() const { 201 return kAudioPacketIntervalMs; 202 } 203 204 // ----------------------- Core FSM implementation --------------------------- 205 206 void GoogleStreamingRemoteEngine::DispatchEvent( 207 const FSMEventArgs& event_args) { 208 DCHECK(CalledOnValidThread()); 209 DCHECK_LE(event_args.event, EVENT_MAX_VALUE); 210 DCHECK_LE(state_, STATE_MAX_VALUE); 211 212 // Event dispatching must be sequential, otherwise it will break all the rules 213 // and the assumptions of the finite state automata model. 214 DCHECK(!is_dispatching_event_); 215 is_dispatching_event_ = true; 216 217 state_ = ExecuteTransitionAndGetNextState(event_args); 218 219 is_dispatching_event_ = false; 220 } 221 222 GoogleStreamingRemoteEngine::FSMState 223 GoogleStreamingRemoteEngine::ExecuteTransitionAndGetNextState( 224 const FSMEventArgs& event_args) { 225 const FSMEvent event = event_args.event; 226 switch (state_) { 227 case STATE_IDLE: 228 switch (event) { 229 case EVENT_START_RECOGNITION: 230 return ConnectBothStreams(event_args); 231 case EVENT_END_RECOGNITION: 232 // Note AUDIO_CHUNK and AUDIO_END events can remain enqueued in case of 233 // abort, so we just silently drop them here. 234 case EVENT_AUDIO_CHUNK: 235 case EVENT_AUDIO_CHUNKS_ENDED: 236 // DOWNSTREAM_CLOSED can be received if we end up here due to an error. 237 case EVENT_DOWNSTREAM_CLOSED: 238 return DoNothing(event_args); 239 case EVENT_UPSTREAM_ERROR: 240 case EVENT_DOWNSTREAM_ERROR: 241 case EVENT_DOWNSTREAM_RESPONSE: 242 return NotFeasible(event_args); 243 } 244 break; 245 case STATE_BOTH_STREAMS_CONNECTED: 246 switch (event) { 247 case EVENT_AUDIO_CHUNK: 248 return TransmitAudioUpstream(event_args); 249 case EVENT_DOWNSTREAM_RESPONSE: 250 return ProcessDownstreamResponse(event_args); 251 case EVENT_AUDIO_CHUNKS_ENDED: 252 return CloseUpstreamAndWaitForResults(event_args); 253 case EVENT_END_RECOGNITION: 254 return AbortSilently(event_args); 255 case EVENT_UPSTREAM_ERROR: 256 case EVENT_DOWNSTREAM_ERROR: 257 case EVENT_DOWNSTREAM_CLOSED: 258 return AbortWithError(event_args); 259 case EVENT_START_RECOGNITION: 260 return NotFeasible(event_args); 261 } 262 break; 263 case STATE_WAITING_DOWNSTREAM_RESULTS: 264 switch (event) { 265 case EVENT_DOWNSTREAM_RESPONSE: 266 return ProcessDownstreamResponse(event_args); 267 case EVENT_DOWNSTREAM_CLOSED: 268 return RaiseNoMatchErrorIfGotNoResults(event_args); 269 case EVENT_END_RECOGNITION: 270 return AbortSilently(event_args); 271 case EVENT_UPSTREAM_ERROR: 272 case EVENT_DOWNSTREAM_ERROR: 273 return AbortWithError(event_args); 274 case EVENT_START_RECOGNITION: 275 case EVENT_AUDIO_CHUNK: 276 case EVENT_AUDIO_CHUNKS_ENDED: 277 return NotFeasible(event_args); 278 } 279 break; 280 } 281 return NotFeasible(event_args); 282 } 283 284 // ----------- Contract for all the FSM evolution functions below ------------- 285 // - Are guaranteed to be executed in the same thread (IO, except for tests); 286 // - Are guaranteed to be not reentrant (themselves and each other); 287 // - event_args members are guaranteed to be stable during the call; 288 289 GoogleStreamingRemoteEngine::FSMState 290 GoogleStreamingRemoteEngine::ConnectBothStreams(const FSMEventArgs&) { 291 DCHECK(!upstream_fetcher_.get()); 292 DCHECK(!downstream_fetcher_.get()); 293 294 encoder_.reset(AudioEncoder::Create(kDefaultAudioCodec, 295 config_.audio_sample_rate, 296 config_.audio_num_bits_per_sample)); 297 DCHECK(encoder_.get()); 298 const std::string request_key = GenerateRequestKey(); 299 300 // Setup downstream fetcher. 301 std::vector<std::string> downstream_args; 302 downstream_args.push_back( 303 "key=" + net::EscapeQueryParamValue(google_apis::GetAPIKey(), true)); 304 downstream_args.push_back("pair=" + request_key); 305 downstream_args.push_back("output=pb"); 306 GURL downstream_url(std::string(kWebServiceBaseUrl) + 307 std::string(kDownstreamUrl) + 308 JoinString(downstream_args, '&')); 309 310 downstream_fetcher_.reset(URLFetcher::Create( 311 kDownstreamUrlFetcherIdForTesting, downstream_url, URLFetcher::GET, 312 this)); 313 downstream_fetcher_->SetRequestContext(url_context_.get()); 314 downstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | 315 net::LOAD_DO_NOT_SEND_COOKIES | 316 net::LOAD_DO_NOT_SEND_AUTH_DATA); 317 downstream_fetcher_->Start(); 318 319 // Setup upstream fetcher. 320 // TODO(hans): Support for user-selected grammars. 321 std::vector<std::string> upstream_args; 322 upstream_args.push_back("key=" + 323 net::EscapeQueryParamValue(google_apis::GetAPIKey(), true)); 324 upstream_args.push_back("pair=" + request_key); 325 upstream_args.push_back("output=pb"); 326 upstream_args.push_back( 327 "lang=" + net::EscapeQueryParamValue(GetAcceptedLanguages(), true)); 328 upstream_args.push_back( 329 config_.filter_profanities ? "pFilter=2" : "pFilter=0"); 330 if (config_.max_hypotheses > 0U) { 331 int max_alternatives = std::min(kMaxMaxAlternatives, 332 config_.max_hypotheses); 333 upstream_args.push_back("maxAlternatives=" + 334 base::UintToString(max_alternatives)); 335 } 336 upstream_args.push_back("client=chromium"); 337 if (!config_.hardware_info.empty()) { 338 upstream_args.push_back( 339 "xhw=" + net::EscapeQueryParamValue(config_.hardware_info, true)); 340 } 341 if (config_.continuous) 342 upstream_args.push_back("continuous"); 343 if (config_.interim_results) 344 upstream_args.push_back("interim"); 345 346 GURL upstream_url(std::string(kWebServiceBaseUrl) + 347 std::string(kUpstreamUrl) + 348 JoinString(upstream_args, '&')); 349 350 upstream_fetcher_.reset(URLFetcher::Create( 351 kUpstreamUrlFetcherIdForTesting, upstream_url, URLFetcher::POST, this)); 352 upstream_fetcher_->SetChunkedUpload(encoder_->mime_type()); 353 upstream_fetcher_->SetRequestContext(url_context_.get()); 354 upstream_fetcher_->SetReferrer(config_.origin_url); 355 upstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | 356 net::LOAD_DO_NOT_SEND_COOKIES | 357 net::LOAD_DO_NOT_SEND_AUTH_DATA); 358 upstream_fetcher_->Start(); 359 previous_response_length_ = 0; 360 return STATE_BOTH_STREAMS_CONNECTED; 361 } 362 363 GoogleStreamingRemoteEngine::FSMState 364 GoogleStreamingRemoteEngine::TransmitAudioUpstream( 365 const FSMEventArgs& event_args) { 366 DCHECK(upstream_fetcher_.get()); 367 DCHECK(event_args.audio_data.get()); 368 const AudioChunk& audio = *(event_args.audio_data.get()); 369 370 DCHECK_EQ(audio.bytes_per_sample(), config_.audio_num_bits_per_sample / 8); 371 encoder_->Encode(audio); 372 scoped_refptr<AudioChunk> encoded_data(encoder_->GetEncodedDataAndClear()); 373 upstream_fetcher_->AppendChunkToUpload(encoded_data->AsString(), false); 374 return state_; 375 } 376 377 GoogleStreamingRemoteEngine::FSMState 378 GoogleStreamingRemoteEngine::ProcessDownstreamResponse( 379 const FSMEventArgs& event_args) { 380 DCHECK(event_args.response.get()); 381 382 proto::SpeechRecognitionEvent ws_event; 383 if (!ws_event.ParseFromString(std::string(event_args.response->begin(), 384 event_args.response->end()))) 385 return AbortWithError(event_args); 386 387 // An empty (default) event is used to notify us that the upstream has 388 // been connected. Ignore. 389 if (!ws_event.result_size() && (!ws_event.has_status() || 390 ws_event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS)) { 391 DVLOG(1) << "Received empty response"; 392 return state_; 393 } 394 395 if (ws_event.has_status()) { 396 switch (ws_event.status()) { 397 case proto::SpeechRecognitionEvent::STATUS_SUCCESS: 398 break; 399 case proto::SpeechRecognitionEvent::STATUS_NO_SPEECH: 400 return Abort(SPEECH_RECOGNITION_ERROR_NO_SPEECH); 401 case proto::SpeechRecognitionEvent::STATUS_ABORTED: 402 return Abort(SPEECH_RECOGNITION_ERROR_ABORTED); 403 case proto::SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE: 404 return Abort(SPEECH_RECOGNITION_ERROR_AUDIO); 405 case proto::SpeechRecognitionEvent::STATUS_NETWORK: 406 return Abort(SPEECH_RECOGNITION_ERROR_NETWORK); 407 case proto::SpeechRecognitionEvent::STATUS_NOT_ALLOWED: 408 // TODO(hans): We need a better error code for this. 409 return Abort(SPEECH_RECOGNITION_ERROR_ABORTED); 410 case proto::SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED: 411 // TODO(hans): We need a better error code for this. 412 return Abort(SPEECH_RECOGNITION_ERROR_ABORTED); 413 case proto::SpeechRecognitionEvent::STATUS_BAD_GRAMMAR: 414 return Abort(SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR); 415 case proto::SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED: 416 // TODO(hans): We need a better error code for this. 417 return Abort(SPEECH_RECOGNITION_ERROR_ABORTED); 418 } 419 } 420 421 SpeechRecognitionResults results; 422 for (int i = 0; i < ws_event.result_size(); ++i) { 423 const proto::SpeechRecognitionResult& ws_result = ws_event.result(i); 424 results.push_back(SpeechRecognitionResult()); 425 SpeechRecognitionResult& result = results.back(); 426 result.is_provisional = !(ws_result.has_final() && ws_result.final()); 427 428 if (!result.is_provisional) 429 got_last_definitive_result_ = true; 430 431 for (int j = 0; j < ws_result.alternative_size(); ++j) { 432 const proto::SpeechRecognitionAlternative& ws_alternative = 433 ws_result.alternative(j); 434 SpeechRecognitionHypothesis hypothesis; 435 if (ws_alternative.has_confidence()) 436 hypothesis.confidence = ws_alternative.confidence(); 437 else if (ws_result.has_stability()) 438 hypothesis.confidence = ws_result.stability(); 439 DCHECK(ws_alternative.has_transcript()); 440 // TODO(hans): Perhaps the transcript should be required in the proto? 441 if (ws_alternative.has_transcript()) 442 hypothesis.utterance = base::UTF8ToUTF16(ws_alternative.transcript()); 443 444 result.hypotheses.push_back(hypothesis); 445 } 446 } 447 448 delegate()->OnSpeechRecognitionEngineResults(results); 449 450 return state_; 451 } 452 453 GoogleStreamingRemoteEngine::FSMState 454 GoogleStreamingRemoteEngine::RaiseNoMatchErrorIfGotNoResults( 455 const FSMEventArgs& event_args) { 456 if (!got_last_definitive_result_) { 457 // Provide an empty result to notify that recognition is ended with no 458 // errors, yet neither any further results. 459 delegate()->OnSpeechRecognitionEngineResults(SpeechRecognitionResults()); 460 } 461 return AbortSilently(event_args); 462 } 463 464 GoogleStreamingRemoteEngine::FSMState 465 GoogleStreamingRemoteEngine::CloseUpstreamAndWaitForResults( 466 const FSMEventArgs&) { 467 DCHECK(upstream_fetcher_.get()); 468 DCHECK(encoder_.get()); 469 470 DVLOG(1) << "Closing upstream."; 471 472 // The encoder requires a non-empty final buffer. So we encode a packet 473 // of silence in case encoder had no data already. 474 std::vector<short> samples( 475 config_.audio_sample_rate * kAudioPacketIntervalMs / 1000); 476 scoped_refptr<AudioChunk> dummy_chunk = 477 new AudioChunk(reinterpret_cast<uint8*>(&samples[0]), 478 samples.size() * sizeof(short), 479 encoder_->bits_per_sample() / 8); 480 encoder_->Encode(*dummy_chunk.get()); 481 encoder_->Flush(); 482 scoped_refptr<AudioChunk> encoded_dummy_data = 483 encoder_->GetEncodedDataAndClear(); 484 DCHECK(!encoded_dummy_data->IsEmpty()); 485 encoder_.reset(); 486 487 upstream_fetcher_->AppendChunkToUpload(encoded_dummy_data->AsString(), true); 488 got_last_definitive_result_ = false; 489 return STATE_WAITING_DOWNSTREAM_RESULTS; 490 } 491 492 GoogleStreamingRemoteEngine::FSMState 493 GoogleStreamingRemoteEngine::CloseDownstream(const FSMEventArgs&) { 494 DCHECK(!upstream_fetcher_.get()); 495 DCHECK(downstream_fetcher_.get()); 496 497 DVLOG(1) << "Closing downstream."; 498 downstream_fetcher_.reset(); 499 return STATE_IDLE; 500 } 501 502 GoogleStreamingRemoteEngine::FSMState 503 GoogleStreamingRemoteEngine::AbortSilently(const FSMEventArgs&) { 504 return Abort(SPEECH_RECOGNITION_ERROR_NONE); 505 } 506 507 GoogleStreamingRemoteEngine::FSMState 508 GoogleStreamingRemoteEngine::AbortWithError(const FSMEventArgs&) { 509 return Abort(SPEECH_RECOGNITION_ERROR_NETWORK); 510 } 511 512 GoogleStreamingRemoteEngine::FSMState GoogleStreamingRemoteEngine::Abort( 513 SpeechRecognitionErrorCode error_code) { 514 DVLOG(1) << "Aborting with error " << error_code; 515 516 if (error_code != SPEECH_RECOGNITION_ERROR_NONE) { 517 delegate()->OnSpeechRecognitionEngineError( 518 SpeechRecognitionError(error_code)); 519 } 520 downstream_fetcher_.reset(); 521 upstream_fetcher_.reset(); 522 encoder_.reset(); 523 return STATE_IDLE; 524 } 525 526 GoogleStreamingRemoteEngine::FSMState 527 GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs&) { 528 return state_; 529 } 530 531 GoogleStreamingRemoteEngine::FSMState 532 GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs& event_args) { 533 NOTREACHED() << "Unfeasible event " << event_args.event 534 << " in state " << state_; 535 return state_; 536 } 537 538 std::string GoogleStreamingRemoteEngine::GetAcceptedLanguages() const { 539 std::string langs = config_.language; 540 if (langs.empty() && url_context_.get()) { 541 // If no language is provided then we use the first from the accepted 542 // language list. If this list is empty then it defaults to "en-US". 543 // Example of the contents of this list: "es,en-GB;q=0.8", "" 544 net::URLRequestContext* request_context = 545 url_context_->GetURLRequestContext(); 546 DCHECK(request_context); 547 // TODO(pauljensen): GoogleStreamingRemoteEngine should be constructed with 548 // a reference to the HttpUserAgentSettings rather than accessing the 549 // accept language through the URLRequestContext. 550 if (request_context->http_user_agent_settings()) { 551 std::string accepted_language_list = 552 request_context->http_user_agent_settings()->GetAcceptLanguage(); 553 size_t separator = accepted_language_list.find_first_of(",;"); 554 if (separator != std::string::npos) 555 langs = accepted_language_list.substr(0, separator); 556 } 557 } 558 if (langs.empty()) 559 langs = "en-US"; 560 return langs; 561 } 562 563 // TODO(primiano): Is there any utility in the codebase that already does this? 564 std::string GoogleStreamingRemoteEngine::GenerateRequestKey() const { 565 const int64 kKeepLowBytes = 0x00000000FFFFFFFFLL; 566 const int64 kKeepHighBytes = 0xFFFFFFFF00000000LL; 567 568 // Just keep the least significant bits of timestamp, in order to reduce 569 // probability of collisions. 570 int64 key = (base::Time::Now().ToInternalValue() & kKeepLowBytes) | 571 (base::RandUint64() & kKeepHighBytes); 572 return base::HexEncode(reinterpret_cast<void*>(&key), sizeof(key)); 573 } 574 575 GoogleStreamingRemoteEngine::FSMEventArgs::FSMEventArgs(FSMEvent event_value) 576 : event(event_value) { 577 } 578 579 GoogleStreamingRemoteEngine::FSMEventArgs::~FSMEventArgs() { 580 } 581 582 } // namespace content 583