// Correspond to steps in the hotword opt-in flow.
/** @const */ var HOTWORD_AUDIO_HISTORY = 'hotword-audio-history-container';
/** @const */ var HOTWORD_ONLY_START = 'hotword-only-container';
- /** @const */ var AUDIO_HISTORY_START = 'audio-history-container';
/** @const */ var SPEECH_TRAINING = 'speech-training-container';
/** @const */ var FINISHED = 'finished-container';
* @const
*/
var FLOWS = [
- // TODO(kcarattini): Remove the first flow, since we will not be
- // managing the Audio History Setting in Chrome anymore.
- [AUDIO_HISTORY_START],
- [HOTWORD_ONLY_START, SPEECH_TRAINING, FINISHED],
+ [HOTWORD_ONLY_START, FINISHED],
[HOTWORD_AUDIO_HISTORY, SPEECH_TRAINING, FINISHED],
[SPEECH_TRAINING, FINISHED]
];
/**
+ * The launch mode. This enum needs to be kept in sync with that of
+ * the same name in hotword_service.h.
+ * @enum {number}
+ */
+ var LaunchMode = {
+ HOTWORD_ONLY: 0,
+ HOTWORD_AND_AUDIO_HISTORY: 1,
+ RETRAIN: 2
+ };
+
+ /**
* Class to control the page flow of the always-on hotword and
* Audio History opt-in process.
* @constructor
function Flow() {
this.currentStepIndex_ = -1;
this.currentFlow_ = [];
+
+ /**
+ * Whether this flow is currently in the process of training a voice model.
+ * @private {LaunchMode}
+ */
+ this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY;
+
+ /**
+ * Whether this flow is currently in the process of training a voice model.
+ * @private {boolean}
+ */
+ this.training_ = false;
+
+ /**
+ * Prefix of the element ids for the page that is currently training.
+ * @private {string}
+ */
+ this.trainingPagePrefix_ = '';
}
/**
chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this));
};
+ /**
+ * Starts the training process.
+ */
+ Flow.prototype.startTraining = function() {
+ // Don't start a training session if one already exists.
+ if (this.training_)
+ return;
+
+ this.training_ = true;
+ if (this.launchMode_ == LaunchMode.HOTWORD_ONLY) {
+ this.trainingPagePrefix_ = 'hotword-only';
+ } else if (this.launchMode_ == LaunchMode.HOTWORD_AND_AUDIO_HISTORY ||
+ this.launchMode_ == LaunchMode.RETRAIN) {
+ this.trainingPagePrefix_ = 'speech-training';
+ }
+
+ if (chrome.hotwordPrivate.onHotwordTriggered) {
+ chrome.hotwordPrivate.onHotwordTriggered.addListener(
+ this.handleHotwordTrigger_.bind(this));
+ }
+ if (chrome.hotwordPrivate.startTraining)
+ chrome.hotwordPrivate.startTraining();
+ };
+
+ /**
+ * Stops the training process.
+ */
+ Flow.prototype.stopTraining = function() {
+ if (!this.training_)
+ return;
+
+ this.training_ = false;
+ if (chrome.hotwordPrivate.onHotwordTriggered) {
+ chrome.hotwordPrivate.onHotwordTriggered.
+ removeListener(this.handleHotwordTrigger_);
+ }
+ if (chrome.hotwordPrivate.stopTraining)
+ chrome.hotwordPrivate.stopTraining();
+ };
+
+ /**
+ * Handles the speaker model finalized event.
+ */
+ Flow.prototype.onSpeakerModelFinalized = function() {
+ this.stopTraining();
+
+ if (chrome.hotwordPrivate.setAudioLoggingEnabled)
+ chrome.hotwordPrivate.setAudioLoggingEnabled(true, function() {});
+
+ if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
+ chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(true,
+ this.advanceStep.bind(this));
+ }
+ };
+
// ---- private methods:
/**
+ * Completes the training process.
+ * @private
+ */
+ Flow.prototype.finalizeSpeakerModel_ = function() {
+ if (!this.training_)
+ return;
+
+ if (chrome.hotwordPrivate.finalizeSpeakerModel)
+ chrome.hotwordPrivate.finalizeSpeakerModel();
+
+ // TODO(kcarattini): Implement a notification that speaker model has been
+ // finalized instead of setting a timeout.
+ setTimeout(this.onSpeakerModelFinalized.bind(this), 2000);
+ };
+
+ /**
+ * Handles a hotword trigger event and updates the training UI.
+ * @private
+ */
+ Flow.prototype.handleHotwordTrigger_ = function() {
+ var curStep =
+ $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
+ // TODO(kcarattini): Localize this string.
+ curStep.querySelector('.text').textContent = 'Recorded';
+ curStep.classList.remove('listening');
+ curStep.classList.add('recorded');
+
+ var steps =
+ $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
+ var index = Array.prototype.indexOf.call(steps, curStep);
+ if (steps[index + 1]) {
+ steps[index + 1].classList.remove('not-started');
+ steps[index + 1].classList.add('listening');
+ return;
+ }
+
+ // Only the last step makes it here.
+ var buttonElem = $(this.trainingPagePrefix_ + '-cancel-button');
+ // TODO(kcarattini): Localize this string.
+ buttonElem.textContent = 'Please wait ...';
+ buttonElem.classList.add('grayed-out');
+ buttonElem.classList.remove('finish-button');
+
+ this.finalizeSpeakerModel_();
+ };
+
+ /**
* Gets and starts the appropriate flow for the launch mode.
+ * @param {chrome.hotwordPrivate.LaunchState} state Launch state of the
+ * Hotword Audio Verification App.
* @private
*/
Flow.prototype.startFlowForMode_ = function(state) {
+ this.launchMode_ = state.launchMode;
assert(state.launchMode >= 0 && state.launchMode < FLOWS.length,
'Invalid Launch Mode.');
this.currentFlow_ = FLOWS[state.launchMode];
this.advanceStep();
+ // If the flow begins with a a training step, then start the training flow.
+ if (state.launchMode == LaunchMode.HOTWORD_ONLY ||
+ state.launchMode == LaunchMode.RETRAIN) {
+ this.startTraining();
+ }
};
/**
*/
Flow.prototype.showStep_ = function() {
var currentStep = this.currentFlow_[this.currentStepIndex_];
+ document.getElementById(currentStep).hidden = false;
+
var previousStep = null;
if (this.currentStepIndex_ > 0)
previousStep = this.currentFlow_[this.currentStepIndex_ - 1];
if (previousStep)
document.getElementById(previousStep).hidden = true;
- document.getElementById(currentStep).hidden = false;
+ chrome.app.window.current().show();
};
window.Flow = Flow;