OLD | NEW |
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 HOTWORD_AUDIO_HISTORY = 'hotword-audio-history-container'; | 8 /** @const */ var HOTWORD_AUDIO_HISTORY = 'hotword-audio-history-container'; |
9 /** @const */ var HOTWORD_ONLY_START = 'hotword-only-container'; | 9 /** @const */ var HOTWORD_ONLY_START = 'hotword-only-container'; |
10 /** @const */ var SPEECH_TRAINING = 'speech-training-container'; | 10 /** @const */ var SPEECH_TRAINING = 'speech-training-container'; |
11 /** @const */ var FINISHED = 'finished-container'; | 11 /** @const */ var FINISHED = 'finished-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 [HOTWORD_ONLY_START, FINISHED], | 20 [HOTWORD_ONLY_START, FINISHED], |
21 [HOTWORD_AUDIO_HISTORY, SPEECH_TRAINING, FINISHED], | 21 [HOTWORD_AUDIO_HISTORY, SPEECH_TRAINING, FINISHED], |
22 [SPEECH_TRAINING, FINISHED] | 22 [SPEECH_TRAINING, FINISHED] |
23 ]; | 23 ]; |
24 | 24 |
25 /** | 25 /** |
| 26 * The launch mode. This enum needs to be kept in sync with that of |
| 27 * the same name in hotword_service.h. |
| 28 * @enum {number} |
| 29 */ |
| 30 var LaunchMode = { |
| 31 HOTWORD_ONLY: 0, |
| 32 HOTWORD_AND_AUDIO_HISTORY: 1, |
| 33 RETRAIN: 2 |
| 34 }; |
| 35 |
| 36 /** |
26 * Class to control the page flow of the always-on hotword and | 37 * Class to control the page flow of the always-on hotword and |
27 * Audio History opt-in process. | 38 * Audio History opt-in process. |
28 * @constructor | 39 * @constructor |
29 */ | 40 */ |
30 function Flow() { | 41 function Flow() { |
31 this.currentStepIndex_ = -1; | 42 this.currentStepIndex_ = -1; |
32 this.currentFlow_ = []; | 43 this.currentFlow_ = []; |
| 44 |
| 45 /** |
| 46 * Whether this flow is currently in the process of training a voice model. |
| 47 * @private {LaunchMode} |
| 48 */ |
| 49 this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY; |
| 50 |
| 51 /** |
| 52 * Whether this flow is currently in the process of training a voice model. |
| 53 * @private {boolean} |
| 54 */ |
| 55 this.training_ = false; |
| 56 |
| 57 /** |
| 58 * Prefix of the element ids for the page that is currently training. |
| 59 * @private {string} |
| 60 */ |
| 61 this.trainingPagePrefix_ = ''; |
33 } | 62 } |
34 | 63 |
35 /** | 64 /** |
36 * Advances the current step. | 65 * Advances the current step. |
37 */ | 66 */ |
38 Flow.prototype.advanceStep = function() { | 67 Flow.prototype.advanceStep = function() { |
39 this.currentStepIndex_++; | 68 this.currentStepIndex_++; |
40 if (this.currentStepIndex_ < this.currentFlow_.length) | 69 if (this.currentStepIndex_ < this.currentFlow_.length) |
41 this.showStep_.apply(this); | 70 this.showStep_.apply(this); |
42 }; | 71 }; |
43 | 72 |
44 /** | 73 /** |
45 * Gets the appropriate flow and displays its first page. | 74 * Gets the appropriate flow and displays its first page. |
46 */ | 75 */ |
47 Flow.prototype.startFlow = function() { | 76 Flow.prototype.startFlow = function() { |
48 if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState) | 77 if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState) |
49 chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this)); | 78 chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this)); |
50 }; | 79 }; |
51 | 80 |
| 81 /** |
| 82 * Starts the training process. |
| 83 */ |
| 84 Flow.prototype.startTraining = function() { |
| 85 // Don't start a training session if one already exists. |
| 86 if (this.training_) |
| 87 return; |
| 88 |
| 89 this.training_ = true; |
| 90 if (this.launchMode_ == LaunchMode.HOTWORD_ONLY || |
| 91 this.launchMode_ == LaunchMode.RETRAIN) { |
| 92 this.trainingPagePrefix_ = 'hotword-only'; |
| 93 } else if (this.launchMode_ == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) { |
| 94 this.trainingPagePrefix_ = 'speech-training'; |
| 95 } |
| 96 |
| 97 if (chrome.hotwordPrivate.onHotwordTriggered) { |
| 98 chrome.hotwordPrivate.onHotwordTriggered.addListener( |
| 99 this.handleHotwordTrigger_.bind(this)); |
| 100 } |
| 101 if (chrome.hotwordPrivate.startTraining) |
| 102 chrome.hotwordPrivate.startTraining(); |
| 103 }; |
| 104 |
| 105 /** |
| 106 * Stops the training process. |
| 107 */ |
| 108 Flow.prototype.stopTraining = function() { |
| 109 if (!this.training_) |
| 110 return; |
| 111 |
| 112 this.training_ = false; |
| 113 if (chrome.hotwordPrivate.onHotwordTriggered) { |
| 114 chrome.hotwordPrivate.onHotwordTriggered. |
| 115 removeListener(this.handleHotwordTrigger_); |
| 116 } |
| 117 if (chrome.hotwordPrivate.stopTraining) |
| 118 chrome.hotwordPrivate.stopTraining(); |
| 119 }; |
| 120 |
| 121 /** |
| 122 * Handles the speaker model finalized event. |
| 123 */ |
| 124 Flow.prototype.onSpeakerModelFinalized = function() { |
| 125 this.stopTraining(); |
| 126 |
| 127 if (chrome.hotwordPrivate.setAudioLoggingEnabled) |
| 128 chrome.hotwordPrivate.setAudioLoggingEnabled(true, function() {}); |
| 129 |
| 130 if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) { |
| 131 chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(true, |
| 132 this.advanceStep.bind(this)); |
| 133 } |
| 134 }; |
| 135 |
52 // ---- private methods: | 136 // ---- private methods: |
53 | 137 |
54 /** | 138 /** |
| 139 * Completes the training process. |
| 140 * @private |
| 141 */ |
| 142 Flow.prototype.finalizeSpeakerModel_ = function() { |
| 143 if (!this.training_) |
| 144 return; |
| 145 |
| 146 if (chrome.hotwordPrivate.finalizeSpeakerModel) |
| 147 chrome.hotwordPrivate.finalizeSpeakerModel(); |
| 148 |
| 149 // TODO(kcarattini): Implement a notification that speaker model has been |
| 150 // finalized instead of setting a timeout. |
| 151 setTimeout(this.onSpeakerModelFinalized.bind(this), 2000); |
| 152 }; |
| 153 |
| 154 /** |
| 155 * Handles a hotword trigger event and updates the training UI. |
| 156 * @private |
| 157 */ |
| 158 Flow.prototype.handleHotwordTrigger_ = function() { |
| 159 var curStep = |
| 160 $(this.trainingPagePrefix_ + '-training').querySelector('.listening'); |
| 161 // TODO(kcarattini): Localize this string. |
| 162 curStep.querySelector('.text').textContent = 'Recorded'; |
| 163 curStep.classList.remove('listening'); |
| 164 curStep.classList.add('recorded'); |
| 165 |
| 166 var steps = |
| 167 $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train'); |
| 168 var index = Array.prototype.indexOf.call(steps, curStep); |
| 169 if (steps[index + 1]) { |
| 170 steps[index + 1].classList.remove('not-started'); |
| 171 steps[index + 1].classList.add('listening'); |
| 172 return; |
| 173 } |
| 174 |
| 175 // Only the last step makes it here. |
| 176 var buttonElem = $(this.trainingPagePrefix_ + '-cancel-button'); |
| 177 // TODO(kcarattini): Localize this string. |
| 178 buttonElem.textContent = 'Please wait ...'; |
| 179 buttonElem.classList.add('grayed-out'); |
| 180 buttonElem.classList.remove('finish-button'); |
| 181 |
| 182 this.finalizeSpeakerModel_(); |
| 183 }; |
| 184 |
| 185 /** |
55 * Gets and starts the appropriate flow for the launch mode. | 186 * Gets and starts the appropriate flow for the launch mode. |
| 187 * @param {chrome.hotwordPrivate.LaunchState} state Launch state of the |
| 188 * Hotword Audio Verification App. |
56 * @private | 189 * @private |
57 */ | 190 */ |
58 Flow.prototype.startFlowForMode_ = function(state) { | 191 Flow.prototype.startFlowForMode_ = function(state) { |
| 192 this.launchMode_ = state.launchMode; |
59 assert(state.launchMode >= 0 && state.launchMode < FLOWS.length, | 193 assert(state.launchMode >= 0 && state.launchMode < FLOWS.length, |
60 'Invalid Launch Mode.'); | 194 'Invalid Launch Mode.'); |
61 this.currentFlow_ = FLOWS[state.launchMode]; | 195 this.currentFlow_ = FLOWS[state.launchMode]; |
62 this.advanceStep(); | 196 this.advanceStep(); |
| 197 // If the flow begins with a a training step, then start the training flow. |
| 198 if (state.launchMode == LaunchMode.HOTWORD_ONLY || |
| 199 state.launchMode == LaunchMode.SPEECH_TRAINING) { |
| 200 this.startTraining(); |
| 201 } |
63 }; | 202 }; |
64 | 203 |
65 /** | 204 /** |
66 * Displays the current step. If the current step is not the first step, | 205 * Displays the current step. If the current step is not the first step, |
67 * also hides the previous step. | 206 * also hides the previous step. |
68 * @private | 207 * @private |
69 */ | 208 */ |
70 Flow.prototype.showStep_ = function() { | 209 Flow.prototype.showStep_ = function() { |
71 var currentStep = this.currentFlow_[this.currentStepIndex_]; | 210 var currentStep = this.currentFlow_[this.currentStepIndex_]; |
72 document.getElementById(currentStep).hidden = false; | 211 document.getElementById(currentStep).hidden = false; |
73 | 212 |
74 var previousStep = null; | 213 var previousStep = null; |
75 if (this.currentStepIndex_ > 0) | 214 if (this.currentStepIndex_ > 0) |
76 previousStep = this.currentFlow_[this.currentStepIndex_ - 1]; | 215 previousStep = this.currentFlow_[this.currentStepIndex_ - 1]; |
77 | 216 |
78 if (previousStep) | 217 if (previousStep) |
79 document.getElementById(previousStep).hidden = true; | 218 document.getElementById(previousStep).hidden = true; |
80 | 219 |
81 chrome.app.window.current().show(); | 220 chrome.app.window.current().show(); |
82 }; | 221 }; |
83 | 222 |
84 window.Flow = Flow; | 223 window.Flow = Flow; |
85 })(); | 224 })(); |
OLD | NEW |