blob: abf7429d0379c3bfa474a8d82d3b30cdb436d047 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
// 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 SPEECH_TRAINING = 'speech-training-container';
/** @const */ var FINISHED = 'finished-container';
/**
* These flows correspond to the three LaunchModes as defined in
* chrome/browser/search/hotword_service.h and should be kept in sync
* with them.
* @const
*/
var FLOWS = [
[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_ = '';
}
/**
* Advances the current step.
*/
Flow.prototype.advanceStep = function() {
this.currentStepIndex_++;
if (this.currentStepIndex_ < this.currentFlow_.length)
this.showStep_.apply(this);
};
/**
* Gets the appropriate flow and displays its first page.
*/
Flow.prototype.startFlow = function() {
if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState)
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();
}
};
/**
* Displays the current step. If the current step is not the first step,
* also hides the previous step.
* @private
*/
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;
chrome.app.window.current().show();
};
window.Flow = Flow;
})();