Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / speech / google_streaming_remote_engine.cc
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