Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(367)

Side by Side Diff: chrome/browser/resources/hotword_audio_verification/flow.js

Issue 2939273002: DO NOT SUBMIT: what chrome/browser/resources/ could eventually look like with clang-format (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 (function() { 5 (function() {
6 6
7 // Correspond to steps in the hotword opt-in flow. 7 // Correspond to steps in the hotword opt-in flow.
8 /** @const */ var START = 'start-container'; 8 /** @const */ var START = 'start-container';
9 /** @const */ var AUDIO_HISTORY = 'audio-history-container'; 9 /** @const */ var AUDIO_HISTORY = 'audio-history-container';
10 /** @const */ var SPEECH_TRAINING = 'speech-training-container'; 10 /** @const */ var SPEECH_TRAINING = 'speech-training-container';
11 /** @const */ var FINISH = 'finish-container'; 11 /** @const */ var FINISH = 'finish-container';
12 12
13 /** 13 /**
14 * These flows correspond to the three LaunchModes as defined in 14 * These flows correspond to the three LaunchModes as defined in
15 * chrome/browser/search/hotword_service.h and should be kept in sync 15 * chrome/browser/search/hotword_service.h and should be kept in sync
16 * with them. 16 * with them.
17 * @const 17 * @const
18 */ 18 */
19 var FLOWS = [ 19 var FLOWS = [
20 [START, SPEECH_TRAINING, FINISH], 20 [START, SPEECH_TRAINING, FINISH],
21 [START, AUDIO_HISTORY, SPEECH_TRAINING, FINISH], 21 [START, AUDIO_HISTORY, SPEECH_TRAINING, FINISH], [SPEECH_TRAINING, FINISH]
22 [SPEECH_TRAINING, FINISH] 22 ];
23 ]; 23
24 24 /**
25 /** 25 * The launch mode. This enum needs to be kept in sync with that of
26 * The launch mode. This enum needs to be kept in sync with that of 26 * the same name in hotword_service.h.
27 * the same name in hotword_service.h. 27 * @enum {number}
28 * @enum {number} 28 */
29 */ 29 var LaunchMode = {HOTWORD_ONLY: 0, HOTWORD_AND_AUDIO_HISTORY: 1, RETRAIN: 2};
30 var LaunchMode = { 30
31 HOTWORD_ONLY: 0, 31 /**
32 HOTWORD_AND_AUDIO_HISTORY: 1, 32 * The training state.
33 RETRAIN: 2 33 * @enum {string}
34 */
35 var TrainingState = {
36 RESET: 'reset',
37 TIMEOUT: 'timeout',
38 ERROR: 'error',
39 };
40
41 /**
42 * Class to control the page flow of the always-on hotword and
43 * Audio History opt-in process.
44 * @constructor
45 */
46 function Flow() {
47 this.currentStepIndex_ = -1;
48 this.currentFlow_ = [];
49
50 /**
51 * The mode that this app was launched in.
52 * @private {LaunchMode}
53 */
54 this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY;
55
56 /**
57 * Whether this flow is currently in the process of training a voice model.
58 * @private {boolean}
59 */
60 this.training_ = false;
61
62 /**
63 * The current training state.
64 * @private {?TrainingState}
65 */
66 this.trainingState_ = null;
67
68 /**
69 * Whether an expected hotword trigger has been received, indexed by
70 * training step.
71 * @private {boolean[]}
72 */
73 this.hotwordTriggerReceived_ = [];
74
75 /**
76 * Prefix of the element ids for the page that is currently training.
77 * @private {string}
78 */
79 this.trainingPagePrefix_ = 'speech-training';
80
81 /**
82 * Whether the speaker model for this flow has been finalized.
83 * @private {boolean}
84 */
85 this.speakerModelFinalized_ = false;
86
87 /**
88 * ID of the currently active timeout.
89 * @private {?number}
90 */
91 this.timeoutId_ = null;
92
93 /**
94 * Listener for the speakerModelSaved event.
95 * @private {Function}
96 */
97 this.speakerModelFinalizedListener_ =
98 this.onSpeakerModelFinalized_.bind(this);
99
100 /**
101 * Listener for the hotword trigger event.
102 * @private {Function}
103 */
104 this.hotwordTriggerListener_ = this.handleHotwordTrigger_.bind(this);
105
106 // Listen for the user locking the screen.
107 chrome.idle.onStateChanged.addListener(
108 this.handleIdleStateChanged_.bind(this));
109
110 // Listen for hotword settings changes. This used to detect when the user
111 // switches to a different profile.
112 if (chrome.hotwordPrivate.onEnabledChanged) {
113 chrome.hotwordPrivate.onEnabledChanged.addListener(
114 this.handleEnabledChanged_.bind(this));
115 }
116 }
117
118 /**
119 * Advances the current step. Begins training if the speech-training
120 * page has been reached.
121 */
122 Flow.prototype.advanceStep = function() {
123 this.currentStepIndex_++;
124 if (this.currentStepIndex_ < this.currentFlow_.length) {
125 if (this.currentFlow_[this.currentStepIndex_] == SPEECH_TRAINING)
126 this.startTraining();
127 this.showStep_.apply(this);
128 }
129 };
130
131 /**
132 * Gets the appropriate flow and displays its first page.
133 */
134 Flow.prototype.startFlow = function() {
135 if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState)
136 chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this));
137 };
138
139 /**
140 * Starts the training process.
141 */
142 Flow.prototype.startTraining = function() {
143 // Don't start a training session if one already exists.
144 if (this.training_)
145 return;
146
147 this.training_ = true;
148
149 if (chrome.hotwordPrivate.onHotwordTriggered &&
150 !chrome.hotwordPrivate.onHotwordTriggered.hasListener(
151 this.hotwordTriggerListener_)) {
152 chrome.hotwordPrivate.onHotwordTriggered.addListener(
153 this.hotwordTriggerListener_);
154 }
155
156 this.waitForHotwordTrigger_(0);
157 if (chrome.hotwordPrivate.startTraining)
158 chrome.hotwordPrivate.startTraining();
159 };
160
161 /**
162 * Stops the training process.
163 */
164 Flow.prototype.stopTraining = function() {
165 if (!this.training_)
166 return;
167
168 this.training_ = false;
169 if (chrome.hotwordPrivate.onHotwordTriggered) {
170 chrome.hotwordPrivate.onHotwordTriggered.removeListener(
171 this.hotwordTriggerListener_);
172 }
173 if (chrome.hotwordPrivate.stopTraining)
174 chrome.hotwordPrivate.stopTraining();
175 };
176
177 /**
178 * Attempts to enable audio history for the signed-in account.
179 */
180 Flow.prototype.enableAudioHistory = function() {
181 // Update UI
182 $('audio-history-agree').disabled = true;
183 $('audio-history-cancel').disabled = true;
184
185 $('audio-history-error').hidden = true;
186 $('audio-history-wait').hidden = false;
187
188 if (chrome.hotwordPrivate.setAudioHistoryEnabled) {
189 chrome.hotwordPrivate.setAudioHistoryEnabled(
190 true, this.onAudioHistoryRequestCompleted_.bind(this));
191 }
192 };
193
194 // ---- private methods:
195
196 /**
197 * Shows an error if the audio history setting was not enabled successfully.
198 * @private
199 */
200 Flow.prototype.handleAudioHistoryError_ = function() {
201 $('audio-history-agree').disabled = false;
202 $('audio-history-cancel').disabled = false;
203
204 $('audio-history-wait').hidden = true;
205 $('audio-history-error').hidden = false;
206
207 // Set a timeout before focusing the Enable button so that screenreaders
208 // have time to announce the error first.
209 this.setTimeout_(function() {
210 $('audio-history-agree').focus();
211 }.bind(this), 50);
212 };
213
214 /**
215 * Callback for when an audio history request completes.
216 * @param {chrome.hotwordPrivate.AudioHistoryState} state The audio history
217 * request state.
218 * @private
219 */
220 Flow.prototype.onAudioHistoryRequestCompleted_ = function(state) {
221 if (!state.success || !state.enabled) {
222 this.handleAudioHistoryError_();
223 return;
224 }
225
226 this.advanceStep();
227 };
228
229 /**
230 * Shows an error if the speaker model has not been finalized.
231 * @private
232 */
233 Flow.prototype.handleSpeakerModelFinalizedError_ = function() {
234 if (!this.training_)
235 return;
236
237 if (this.speakerModelFinalized_)
238 return;
239
240 this.updateTrainingState_(TrainingState.ERROR);
241 this.stopTraining();
242 };
243
244 /**
245 * Handles the speaker model finalized event.
246 * @private
247 */
248 Flow.prototype.onSpeakerModelFinalized_ = function() {
249 this.speakerModelFinalized_ = true;
250 if (chrome.hotwordPrivate.onSpeakerModelSaved) {
251 chrome.hotwordPrivate.onSpeakerModelSaved.removeListener(
252 this.speakerModelFinalizedListener_);
253 }
254 this.stopTraining();
255 this.setTimeout_(this.finishFlow_.bind(this), 2000);
256 };
257
258 /**
259 * Completes the training process.
260 * @private
261 */
262 Flow.prototype.finishFlow_ = function() {
263 if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
264 chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(
265 true, this.advanceStep.bind(this));
266 }
267 };
268
269 /**
270 * Handles a user clicking on the retry button.
271 */
272 Flow.prototype.handleRetry = function() {
273 if (!(this.trainingState_ == TrainingState.TIMEOUT ||
274 this.trainingState_ == TrainingState.ERROR))
275 return;
276
277 this.startTraining();
278 this.updateTrainingState_(TrainingState.RESET);
279 };
280
281 // ---- private methods:
282
283 /**
284 * Completes the training process.
285 * @private
286 */
287 Flow.prototype.finalizeSpeakerModel_ = function() {
288 if (!this.training_)
289 return;
290
291 // Listen for the success event from the NaCl module.
292 if (chrome.hotwordPrivate.onSpeakerModelSaved &&
293 !chrome.hotwordPrivate.onSpeakerModelSaved.hasListener(
294 this.speakerModelFinalizedListener_)) {
295 chrome.hotwordPrivate.onSpeakerModelSaved.addListener(
296 this.speakerModelFinalizedListener_);
297 }
298
299 this.speakerModelFinalized_ = false;
300 this.setTimeout_(this.handleSpeakerModelFinalizedError_.bind(this), 30000);
301 if (chrome.hotwordPrivate.finalizeSpeakerModel)
302 chrome.hotwordPrivate.finalizeSpeakerModel();
303 };
304
305 /**
306 * Returns the current training step.
307 * @param {string} curStepClassName The name of the class of the current
308 * training step.
309 * @return {Object} The current training step, its index, and an array of
310 * all training steps. Any of these can be undefined.
311 * @private
312 */
313 Flow.prototype.getCurrentTrainingStep_ = function(curStepClassName) {
314 var steps =
315 $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
316 var curStep =
317 $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
318
319 return {
320 current: curStep,
321 index: Array.prototype.indexOf.call(steps, curStep),
322 steps: steps
34 }; 323 };
35 324 };
36 /** 325
37 * The training state. 326 /**
38 * @enum {string} 327 * Updates the training state.
39 */ 328 * @param {TrainingState} state The training state.
40 var TrainingState = { 329 * @private
41 RESET: 'reset', 330 */
42 TIMEOUT: 'timeout', 331 Flow.prototype.updateTrainingState_ = function(state) {
43 ERROR: 'error', 332 this.trainingState_ = state;
44 }; 333 this.updateErrorUI_();
45 334 };
46 /** 335
47 * Class to control the page flow of the always-on hotword and 336 /**
48 * Audio History opt-in process. 337 * Waits two minutes and then checks for a training error.
49 * @constructor 338 * @param {number} index The index of the training step.
50 */ 339 * @private
51 function Flow() { 340 */
52 this.currentStepIndex_ = -1; 341 Flow.prototype.waitForHotwordTrigger_ = function(index) {
53 this.currentFlow_ = []; 342 if (!this.training_)
54 343 return;
55 /** 344
56 * The mode that this app was launched in. 345 this.hotwordTriggerReceived_[index] = false;
57 * @private {LaunchMode} 346 this.setTimeout_(this.handleTrainingTimeout_.bind(this, index), 120000);
58 */ 347 };
59 this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY; 348
60 349 /**
61 /** 350 * Checks for and handles a training error.
62 * Whether this flow is currently in the process of training a voice model. 351 * @param {number} index The index of the training step.
63 * @private {boolean} 352 * @private
64 */ 353 */
65 this.training_ = false; 354 Flow.prototype.handleTrainingTimeout_ = function(index) {
66 355 if (this.hotwordTriggerReceived_[index])
67 /** 356 return;
68 * The current training state. 357
69 * @private {?TrainingState} 358 this.timeoutTraining_();
70 */ 359 };
71 this.trainingState_ = null; 360
72 361 /**
73 /** 362 * Times out training and updates the UI to show a "retry" message, if
74 * Whether an expected hotword trigger has been received, indexed by 363 * currently training.
75 * training step. 364 * @private
76 * @private {boolean[]} 365 */
77 */ 366 Flow.prototype.timeoutTraining_ = function() {
78 this.hotwordTriggerReceived_ = []; 367 if (!this.training_)
79 368 return;
80 /** 369
81 * Prefix of the element ids for the page that is currently training. 370 this.clearTimeout_();
82 * @private {string} 371 this.updateTrainingState_(TrainingState.TIMEOUT);
83 */ 372 this.stopTraining();
84 this.trainingPagePrefix_ = 'speech-training'; 373 };
85 374
86 /** 375 /**
87 * Whether the speaker model for this flow has been finalized. 376 * Sets a timeout. If any timeout is active, clear it.
88 * @private {boolean} 377 * @param {Function} func The function to invoke when the timeout occurs.
89 */ 378 * @param {number} delay Timeout delay in milliseconds.
90 this.speakerModelFinalized_ = false; 379 * @private
91 380 */
92 /** 381 Flow.prototype.setTimeout_ = function(func, delay) {
93 * ID of the currently active timeout. 382 this.clearTimeout_();
94 * @private {?number} 383 this.timeoutId_ = setTimeout(function() {
95 */
96 this.timeoutId_ = null; 384 this.timeoutId_ = null;
97 385 func();
98 /** 386 }, delay);
99 * Listener for the speakerModelSaved event. 387 };
100 * @private {Function} 388
101 */ 389 /**
102 this.speakerModelFinalizedListener_ = 390 * Clears any currently active timeout.
103 this.onSpeakerModelFinalized_.bind(this); 391 * @private
104 392 */
105 /** 393 Flow.prototype.clearTimeout_ = function() {
106 * Listener for the hotword trigger event. 394 if (this.timeoutId_ != null) {
107 * @private {Function} 395 clearTimeout(this.timeoutId_);
108 */ 396 this.timeoutId_ = null;
109 this.hotwordTriggerListener_ = 397 }
110 this.handleHotwordTrigger_.bind(this); 398 };
111 399
112 // Listen for the user locking the screen. 400 /**
113 chrome.idle.onStateChanged.addListener( 401 * Updates the training error UI.
114 this.handleIdleStateChanged_.bind(this)); 402 * @private
115 403 */
116 // Listen for hotword settings changes. This used to detect when the user 404 Flow.prototype.updateErrorUI_ = function() {
117 // switches to a different profile. 405 if (!this.training_)
118 if (chrome.hotwordPrivate.onEnabledChanged) { 406 return;
119 chrome.hotwordPrivate.onEnabledChanged.addListener( 407
120 this.handleEnabledChanged_.bind(this)); 408 var trainingSteps = this.getCurrentTrainingStep_('listening');
409 var steps = trainingSteps.steps;
410
411 $(this.trainingPagePrefix_ + '-toast').hidden =
412 this.trainingState_ != TrainingState.TIMEOUT;
413 if (this.trainingState_ == TrainingState.RESET) {
414 // We reset the training to begin at the first step.
415 // The first step is reset to 'listening', while the rest
416 // are reset to 'not-started'.
417 var prompt = loadTimeData.getString('trainingFirstPrompt');
418 for (var i = 0; i < steps.length; ++i) {
419 steps[i].classList.remove('recorded');
420 if (i == 0) {
421 steps[i].classList.remove('not-started');
422 steps[i].classList.add('listening');
423 } else {
424 steps[i].classList.add('not-started');
425 if (i == steps.length - 1)
426 prompt = loadTimeData.getString('trainingLastPrompt');
427 else
428 prompt = loadTimeData.getString('trainingMiddlePrompt');
429 }
430 steps[i].querySelector('.text').textContent = prompt;
121 } 431 }
122 } 432
123 433 // Reset the buttonbar.
124 /** 434 $(this.trainingPagePrefix_ + '-processing').hidden = true;
125 * Advances the current step. Begins training if the speech-training 435 $(this.trainingPagePrefix_ + '-wait').hidden = false;
126 * page has been reached. 436 $(this.trainingPagePrefix_ + '-error').hidden = true;
127 */ 437 $(this.trainingPagePrefix_ + '-retry').hidden = true;
128 Flow.prototype.advanceStep = function() { 438 } else if (this.trainingState_ == TrainingState.TIMEOUT) {
129 this.currentStepIndex_++; 439 var curStep = trainingSteps.current;
130 if (this.currentStepIndex_ < this.currentFlow_.length) { 440 if (curStep) {
131 if (this.currentFlow_[this.currentStepIndex_] == SPEECH_TRAINING) 441 curStep.classList.remove('listening');
132 this.startTraining(); 442 curStep.classList.add('not-started');
133 this.showStep_.apply(this);
134 } 443 }
135 }; 444
136 445 // Set a timeout before focusing the Retry button so that screenreaders
137 /** 446 // have time to announce the timeout first.
138 * Gets the appropriate flow and displays its first page. 447 this.setTimeout_(function() {
139 */ 448 $(this.trainingPagePrefix_ + '-toast').children[1].focus();
140 Flow.prototype.startFlow = function() { 449 }.bind(this), 50);
141 if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState) 450 } else if (this.trainingState_ == TrainingState.ERROR) {
142 chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this)); 451 // Update the buttonbar.
143 }; 452 $(this.trainingPagePrefix_ + '-wait').hidden = true;
144 453 $(this.trainingPagePrefix_ + '-error').hidden = false;
145 /** 454 $(this.trainingPagePrefix_ + '-retry').hidden = false;
146 * Starts the training process. 455 $(this.trainingPagePrefix_ + '-processing').hidden = false;
147 */ 456
148 Flow.prototype.startTraining = function() { 457 // Set a timeout before focusing the Retry button so that screenreaders
149 // Don't start a training session if one already exists.
150 if (this.training_)
151 return;
152
153 this.training_ = true;
154
155 if (chrome.hotwordPrivate.onHotwordTriggered &&
156 !chrome.hotwordPrivate.onHotwordTriggered.hasListener(
157 this.hotwordTriggerListener_)) {
158 chrome.hotwordPrivate.onHotwordTriggered.addListener(
159 this.hotwordTriggerListener_);
160 }
161
162 this.waitForHotwordTrigger_(0);
163 if (chrome.hotwordPrivate.startTraining)
164 chrome.hotwordPrivate.startTraining();
165 };
166
167 /**
168 * Stops the training process.
169 */
170 Flow.prototype.stopTraining = function() {
171 if (!this.training_)
172 return;
173
174 this.training_ = false;
175 if (chrome.hotwordPrivate.onHotwordTriggered) {
176 chrome.hotwordPrivate.onHotwordTriggered.
177 removeListener(this.hotwordTriggerListener_);
178 }
179 if (chrome.hotwordPrivate.stopTraining)
180 chrome.hotwordPrivate.stopTraining();
181 };
182
183 /**
184 * Attempts to enable audio history for the signed-in account.
185 */
186 Flow.prototype.enableAudioHistory = function() {
187 // Update UI
188 $('audio-history-agree').disabled = true;
189 $('audio-history-cancel').disabled = true;
190
191 $('audio-history-error').hidden = true;
192 $('audio-history-wait').hidden = false;
193
194 if (chrome.hotwordPrivate.setAudioHistoryEnabled) {
195 chrome.hotwordPrivate.setAudioHistoryEnabled(
196 true, this.onAudioHistoryRequestCompleted_.bind(this));
197 }
198 };
199
200 // ---- private methods:
201
202 /**
203 * Shows an error if the audio history setting was not enabled successfully.
204 * @private
205 */
206 Flow.prototype.handleAudioHistoryError_ = function() {
207 $('audio-history-agree').disabled = false;
208 $('audio-history-cancel').disabled = false;
209
210 $('audio-history-wait').hidden = true;
211 $('audio-history-error').hidden = false;
212
213 // Set a timeout before focusing the Enable button so that screenreaders
214 // have time to announce the error first. 458 // have time to announce the error first.
215 this.setTimeout_(function() { 459 this.setTimeout_(function() {
216 $('audio-history-agree').focus(); 460 $(this.trainingPagePrefix_ + '-retry').children[0].focus();
217 }.bind(this), 50); 461 }.bind(this), 50);
218 }; 462 }
219 463 };
220 /** 464
221 * Callback for when an audio history request completes. 465 /**
222 * @param {chrome.hotwordPrivate.AudioHistoryState} state The audio history 466 * Handles a hotword trigger event and updates the training UI.
223 * request state. 467 * @private
224 * @private 468 */
225 */ 469 Flow.prototype.handleHotwordTrigger_ = function() {
226 Flow.prototype.onAudioHistoryRequestCompleted_ = function(state) { 470 var trainingSteps = this.getCurrentTrainingStep_('listening');
227 if (!state.success || !state.enabled) { 471
228 this.handleAudioHistoryError_(); 472 if (!trainingSteps.current)
229 return; 473 return;
230 } 474
231 475 var index = trainingSteps.index;
232 this.advanceStep(); 476 this.hotwordTriggerReceived_[index] = true;
233 }; 477
234 478 trainingSteps.current.querySelector('.text').textContent =
235 /** 479 loadTimeData.getString('trainingRecorded');
236 * Shows an error if the speaker model has not been finalized. 480 trainingSteps.current.classList.remove('listening');
237 * @private 481 trainingSteps.current.classList.add('recorded');
238 */ 482
239 Flow.prototype.handleSpeakerModelFinalizedError_ = function() { 483 if (trainingSteps.steps[index + 1]) {
240 if (!this.training_) 484 trainingSteps.steps[index + 1].classList.remove('not-started');
241 return; 485 trainingSteps.steps[index + 1].classList.add('listening');
242 486 this.waitForHotwordTrigger_(index + 1);
243 if (this.speakerModelFinalized_) 487 return;
244 return; 488 }
245 489
246 this.updateTrainingState_(TrainingState.ERROR); 490 // Only the last step makes it here.
247 this.stopTraining(); 491 var buttonElem = $(this.trainingPagePrefix_ + '-processing').hidden = false;
248 }; 492 this.finalizeSpeakerModel_();
249 493 };
250 /** 494
251 * Handles the speaker model finalized event. 495 /**
252 * @private 496 * Handles a chrome.idle.onStateChanged event and times out the training if
253 */ 497 * the state is "locked".
254 Flow.prototype.onSpeakerModelFinalized_ = function() { 498 * @param {!string} state State, one of "active", "idle", or "locked".
255 this.speakerModelFinalized_ = true; 499 * @private
256 if (chrome.hotwordPrivate.onSpeakerModelSaved) { 500 */
257 chrome.hotwordPrivate.onSpeakerModelSaved.removeListener( 501 Flow.prototype.handleIdleStateChanged_ = function(state) {
258 this.speakerModelFinalizedListener_); 502 if (state == 'locked')
259 }
260 this.stopTraining();
261 this.setTimeout_(this.finishFlow_.bind(this), 2000);
262 };
263
264 /**
265 * Completes the training process.
266 * @private
267 */
268 Flow.prototype.finishFlow_ = function() {
269 if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
270 chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(true,
271 this.advanceStep.bind(this));
272 }
273 };
274
275 /**
276 * Handles a user clicking on the retry button.
277 */
278 Flow.prototype.handleRetry = function() {
279 if (!(this.trainingState_ == TrainingState.TIMEOUT ||
280 this.trainingState_ == TrainingState.ERROR))
281 return;
282
283 this.startTraining();
284 this.updateTrainingState_(TrainingState.RESET);
285 };
286
287 // ---- private methods:
288
289 /**
290 * Completes the training process.
291 * @private
292 */
293 Flow.prototype.finalizeSpeakerModel_ = function() {
294 if (!this.training_)
295 return;
296
297 // Listen for the success event from the NaCl module.
298 if (chrome.hotwordPrivate.onSpeakerModelSaved &&
299 !chrome.hotwordPrivate.onSpeakerModelSaved.hasListener(
300 this.speakerModelFinalizedListener_)) {
301 chrome.hotwordPrivate.onSpeakerModelSaved.addListener(
302 this.speakerModelFinalizedListener_);
303 }
304
305 this.speakerModelFinalized_ = false;
306 this.setTimeout_(this.handleSpeakerModelFinalizedError_.bind(this), 30000);
307 if (chrome.hotwordPrivate.finalizeSpeakerModel)
308 chrome.hotwordPrivate.finalizeSpeakerModel();
309 };
310
311 /**
312 * Returns the current training step.
313 * @param {string} curStepClassName The name of the class of the current
314 * training step.
315 * @return {Object} The current training step, its index, and an array of
316 * all training steps. Any of these can be undefined.
317 * @private
318 */
319 Flow.prototype.getCurrentTrainingStep_ = function(curStepClassName) {
320 var steps =
321 $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
322 var curStep =
323 $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
324
325 return {current: curStep,
326 index: Array.prototype.indexOf.call(steps, curStep),
327 steps: steps};
328 };
329
330 /**
331 * Updates the training state.
332 * @param {TrainingState} state The training state.
333 * @private
334 */
335 Flow.prototype.updateTrainingState_ = function(state) {
336 this.trainingState_ = state;
337 this.updateErrorUI_();
338 };
339
340 /**
341 * Waits two minutes and then checks for a training error.
342 * @param {number} index The index of the training step.
343 * @private
344 */
345 Flow.prototype.waitForHotwordTrigger_ = function(index) {
346 if (!this.training_)
347 return;
348
349 this.hotwordTriggerReceived_[index] = false;
350 this.setTimeout_(this.handleTrainingTimeout_.bind(this, index), 120000);
351 };
352
353 /**
354 * Checks for and handles a training error.
355 * @param {number} index The index of the training step.
356 * @private
357 */
358 Flow.prototype.handleTrainingTimeout_ = function(index) {
359 if (this.hotwordTriggerReceived_[index])
360 return;
361
362 this.timeoutTraining_(); 503 this.timeoutTraining_();
363 }; 504 };
364 505
365 /** 506 /**
366 * Times out training and updates the UI to show a "retry" message, if 507 * Handles a chrome.hotwordPrivate.onEnabledChanged event and times out
367 * currently training. 508 * training if the user is no longer the active user (user switches profiles).
368 * @private 509 * @private
369 */ 510 */
370 Flow.prototype.timeoutTraining_ = function() { 511 Flow.prototype.handleEnabledChanged_ = function() {
371 if (!this.training_) 512 if (chrome.hotwordPrivate.getStatus) {
372 return; 513 chrome.hotwordPrivate.getStatus(function(status) {
373 514 if (status.userIsActive)
374 this.clearTimeout_(); 515 return;
375 this.updateTrainingState_(TrainingState.TIMEOUT); 516
376 this.stopTraining();
377 };
378
379 /**
380 * Sets a timeout. If any timeout is active, clear it.
381 * @param {Function} func The function to invoke when the timeout occurs.
382 * @param {number} delay Timeout delay in milliseconds.
383 * @private
384 */
385 Flow.prototype.setTimeout_ = function(func, delay) {
386 this.clearTimeout_();
387 this.timeoutId_ = setTimeout(function() {
388 this.timeoutId_ = null;
389 func();
390 }, delay);
391 };
392
393 /**
394 * Clears any currently active timeout.
395 * @private
396 */
397 Flow.prototype.clearTimeout_ = function() {
398 if (this.timeoutId_ != null) {
399 clearTimeout(this.timeoutId_);
400 this.timeoutId_ = null;
401 }
402 };
403
404 /**
405 * Updates the training error UI.
406 * @private
407 */
408 Flow.prototype.updateErrorUI_ = function() {
409 if (!this.training_)
410 return;
411
412 var trainingSteps = this.getCurrentTrainingStep_('listening');
413 var steps = trainingSteps.steps;
414
415 $(this.trainingPagePrefix_ + '-toast').hidden =
416 this.trainingState_ != TrainingState.TIMEOUT;
417 if (this.trainingState_ == TrainingState.RESET) {
418 // We reset the training to begin at the first step.
419 // The first step is reset to 'listening', while the rest
420 // are reset to 'not-started'.
421 var prompt = loadTimeData.getString('trainingFirstPrompt');
422 for (var i = 0; i < steps.length; ++i) {
423 steps[i].classList.remove('recorded');
424 if (i == 0) {
425 steps[i].classList.remove('not-started');
426 steps[i].classList.add('listening');
427 } else {
428 steps[i].classList.add('not-started');
429 if (i == steps.length - 1)
430 prompt = loadTimeData.getString('trainingLastPrompt');
431 else
432 prompt = loadTimeData.getString('trainingMiddlePrompt');
433 }
434 steps[i].querySelector('.text').textContent = prompt;
435 }
436
437 // Reset the buttonbar.
438 $(this.trainingPagePrefix_ + '-processing').hidden = true;
439 $(this.trainingPagePrefix_ + '-wait').hidden = false;
440 $(this.trainingPagePrefix_ + '-error').hidden = true;
441 $(this.trainingPagePrefix_ + '-retry').hidden = true;
442 } else if (this.trainingState_ == TrainingState.TIMEOUT) {
443 var curStep = trainingSteps.current;
444 if (curStep) {
445 curStep.classList.remove('listening');
446 curStep.classList.add('not-started');
447 }
448
449 // Set a timeout before focusing the Retry button so that screenreaders
450 // have time to announce the timeout first.
451 this.setTimeout_(function() {
452 $(this.trainingPagePrefix_ + '-toast').children[1].focus();
453 }.bind(this), 50);
454 } else if (this.trainingState_ == TrainingState.ERROR) {
455 // Update the buttonbar.
456 $(this.trainingPagePrefix_ + '-wait').hidden = true;
457 $(this.trainingPagePrefix_ + '-error').hidden = false;
458 $(this.trainingPagePrefix_ + '-retry').hidden = false;
459 $(this.trainingPagePrefix_ + '-processing').hidden = false;
460
461 // Set a timeout before focusing the Retry button so that screenreaders
462 // have time to announce the error first.
463 this.setTimeout_(function() {
464 $(this.trainingPagePrefix_ + '-retry').children[0].focus();
465 }.bind(this), 50);
466 }
467 };
468
469 /**
470 * Handles a hotword trigger event and updates the training UI.
471 * @private
472 */
473 Flow.prototype.handleHotwordTrigger_ = function() {
474 var trainingSteps = this.getCurrentTrainingStep_('listening');
475
476 if (!trainingSteps.current)
477 return;
478
479 var index = trainingSteps.index;
480 this.hotwordTriggerReceived_[index] = true;
481
482 trainingSteps.current.querySelector('.text').textContent =
483 loadTimeData.getString('trainingRecorded');
484 trainingSteps.current.classList.remove('listening');
485 trainingSteps.current.classList.add('recorded');
486
487 if (trainingSteps.steps[index + 1]) {
488 trainingSteps.steps[index + 1].classList.remove('not-started');
489 trainingSteps.steps[index + 1].classList.add('listening');
490 this.waitForHotwordTrigger_(index + 1);
491 return;
492 }
493
494 // Only the last step makes it here.
495 var buttonElem = $(this.trainingPagePrefix_ + '-processing').hidden = false;
496 this.finalizeSpeakerModel_();
497 };
498
499 /**
500 * Handles a chrome.idle.onStateChanged event and times out the training if
501 * the state is "locked".
502 * @param {!string} state State, one of "active", "idle", or "locked".
503 * @private
504 */
505 Flow.prototype.handleIdleStateChanged_ = function(state) {
506 if (state == 'locked')
507 this.timeoutTraining_(); 517 this.timeoutTraining_();
508 }; 518 }.bind(this));
509 519 }
510 /** 520 };
511 * Handles a chrome.hotwordPrivate.onEnabledChanged event and times out 521
512 * training if the user is no longer the active user (user switches profiles). 522 /**
513 * @private 523 * Gets and starts the appropriate flow for the launch mode.
514 */ 524 * @param {chrome.hotwordPrivate.LaunchState} state Launch state of the
515 Flow.prototype.handleEnabledChanged_ = function() { 525 * Hotword Audio Verification App.
516 if (chrome.hotwordPrivate.getStatus) { 526 * @private
517 chrome.hotwordPrivate.getStatus(function(status) { 527 */
518 if (status.userIsActive) 528 Flow.prototype.startFlowForMode_ = function(state) {
519 return; 529 this.launchMode_ = state.launchMode;
520 530 assert(
521 this.timeoutTraining_(); 531 state.launchMode >= 0 && state.launchMode < FLOWS.length,
522 }.bind(this)); 532 'Invalid Launch Mode.');
523 } 533 this.currentFlow_ = FLOWS[state.launchMode];
524 }; 534 if (state.launchMode == LaunchMode.HOTWORD_ONLY) {
525 535 $('intro-description-audio-history-enabled').hidden = false;
526 /** 536 } else if (state.launchMode == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) {
527 * Gets and starts the appropriate flow for the launch mode. 537 $('intro-description').hidden = false;
528 * @param {chrome.hotwordPrivate.LaunchState} state Launch state of the 538 }
529 * Hotword Audio Verification App. 539
530 * @private 540 this.advanceStep();
531 */ 541 };
532 Flow.prototype.startFlowForMode_ = function(state) { 542
533 this.launchMode_ = state.launchMode; 543 /**
534 assert(state.launchMode >= 0 && state.launchMode < FLOWS.length, 544 * Displays the current step. If the current step is not the first step,
535 'Invalid Launch Mode.'); 545 * also hides the previous step. Focuses the current step's first button.
536 this.currentFlow_ = FLOWS[state.launchMode]; 546 * @private
537 if (state.launchMode == LaunchMode.HOTWORD_ONLY) { 547 */
538 $('intro-description-audio-history-enabled').hidden = false; 548 Flow.prototype.showStep_ = function() {
539 } else if (state.launchMode == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) { 549 var currentStepId = this.currentFlow_[this.currentStepIndex_];
540 $('intro-description').hidden = false; 550 var currentStep = document.getElementById(currentStepId);
541 } 551 currentStep.hidden = false;
542 552
543 this.advanceStep(); 553 cr.ui.setInitialFocus(currentStep);
544 }; 554
545 555 var previousStep = null;
546 /** 556 if (this.currentStepIndex_ > 0)
547 * Displays the current step. If the current step is not the first step, 557 previousStep = this.currentFlow_[this.currentStepIndex_ - 1];
548 * also hides the previous step. Focuses the current step's first button. 558
549 * @private 559 if (previousStep)
550 */ 560 document.getElementById(previousStep).hidden = true;
551 Flow.prototype.showStep_ = function() { 561
552 var currentStepId = this.currentFlow_[this.currentStepIndex_]; 562 chrome.app.window.current().show();
553 var currentStep = document.getElementById(currentStepId); 563 };
554 currentStep.hidden = false; 564
555 565 window.Flow = Flow;
556 cr.ui.setInitialFocus(currentStep);
557
558 var previousStep = null;
559 if (this.currentStepIndex_ > 0)
560 previousStep = this.currentFlow_[this.currentStepIndex_ - 1];
561
562 if (previousStep)
563 document.getElementById(previousStep).hidden = true;
564
565 chrome.app.window.current().show();
566 };
567
568 window.Flow = Flow;
569 })(); 566 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698