| Index: chrome/browser/resources/hotword_audio_verification/flow.js
|
| diff --git a/chrome/browser/resources/hotword_audio_verification/flow.js b/chrome/browser/resources/hotword_audio_verification/flow.js
|
| index 9c0c4b016000bd674cc6e4203b8a2cd741e25817..c22211492c4e80efcac9922be8393c0a94d0a446 100644
|
| --- a/chrome/browser/resources/hotword_audio_verification/flow.js
|
| +++ b/chrome/browser/resources/hotword_audio_verification/flow.js
|
| @@ -4,566 +4,563 @@
|
|
|
| (function() {
|
|
|
| - // Correspond to steps in the hotword opt-in flow.
|
| - /** @const */ var START = 'start-container';
|
| - /** @const */ var AUDIO_HISTORY = 'audio-history-container';
|
| - /** @const */ var SPEECH_TRAINING = 'speech-training-container';
|
| - /** @const */ var FINISH = 'finish-container';
|
| +// Correspond to steps in the hotword opt-in flow.
|
| +/** @const */ var START = 'start-container';
|
| +/** @const */ var AUDIO_HISTORY = 'audio-history-container';
|
| +/** @const */ var SPEECH_TRAINING = 'speech-training-container';
|
| +/** @const */ var FINISH = 'finish-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 = [
|
| + [START, SPEECH_TRAINING, FINISH],
|
| + [START, AUDIO_HISTORY, SPEECH_TRAINING, FINISH], [SPEECH_TRAINING, FINISH]
|
| +];
|
| +
|
| +/**
|
| + * 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};
|
| +
|
| +/**
|
| + * The training state.
|
| + * @enum {string}
|
| + */
|
| +var TrainingState = {
|
| + RESET: 'reset',
|
| + TIMEOUT: 'timeout',
|
| + ERROR: 'error',
|
| +};
|
| +
|
| +/**
|
| + * 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_ = [];
|
|
|
| /**
|
| - * 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
|
| + * The mode that this app was launched in.
|
| + * @private {LaunchMode}
|
| */
|
| - var FLOWS = [
|
| - [START, SPEECH_TRAINING, FINISH],
|
| - [START, AUDIO_HISTORY, SPEECH_TRAINING, FINISH],
|
| - [SPEECH_TRAINING, FINISH]
|
| - ];
|
| + this.launchMode_ = LaunchMode.HOTWORD_AND_AUDIO_HISTORY;
|
|
|
| /**
|
| - * The launch mode. This enum needs to be kept in sync with that of
|
| - * the same name in hotword_service.h.
|
| - * @enum {number}
|
| + * Whether this flow is currently in the process of training a voice model.
|
| + * @private {boolean}
|
| */
|
| - var LaunchMode = {
|
| - HOTWORD_ONLY: 0,
|
| - HOTWORD_AND_AUDIO_HISTORY: 1,
|
| - RETRAIN: 2
|
| - };
|
| -
|
| - /**
|
| - * The training state.
|
| - * @enum {string}
|
| - */
|
| - var TrainingState = {
|
| - RESET: 'reset',
|
| - TIMEOUT: 'timeout',
|
| - ERROR: 'error',
|
| - };
|
| -
|
| - /**
|
| - * 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_ = [];
|
| -
|
| - /**
|
| - * The mode that this app was launched in.
|
| - * @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;
|
| -
|
| - /**
|
| - * The current training state.
|
| - * @private {?TrainingState}
|
| - */
|
| - this.trainingState_ = null;
|
| -
|
| - /**
|
| - * Whether an expected hotword trigger has been received, indexed by
|
| - * training step.
|
| - * @private {boolean[]}
|
| - */
|
| - this.hotwordTriggerReceived_ = [];
|
| -
|
| - /**
|
| - * Prefix of the element ids for the page that is currently training.
|
| - * @private {string}
|
| - */
|
| - this.trainingPagePrefix_ = 'speech-training';
|
| -
|
| - /**
|
| - * Whether the speaker model for this flow has been finalized.
|
| - * @private {boolean}
|
| - */
|
| - this.speakerModelFinalized_ = false;
|
| -
|
| - /**
|
| - * ID of the currently active timeout.
|
| - * @private {?number}
|
| - */
|
| - this.timeoutId_ = null;
|
| -
|
| - /**
|
| - * Listener for the speakerModelSaved event.
|
| - * @private {Function}
|
| - */
|
| - this.speakerModelFinalizedListener_ =
|
| - this.onSpeakerModelFinalized_.bind(this);
|
| -
|
| - /**
|
| - * Listener for the hotword trigger event.
|
| - * @private {Function}
|
| - */
|
| - this.hotwordTriggerListener_ =
|
| - this.handleHotwordTrigger_.bind(this);
|
| -
|
| - // Listen for the user locking the screen.
|
| - chrome.idle.onStateChanged.addListener(
|
| - this.handleIdleStateChanged_.bind(this));
|
| -
|
| - // Listen for hotword settings changes. This used to detect when the user
|
| - // switches to a different profile.
|
| - if (chrome.hotwordPrivate.onEnabledChanged) {
|
| - chrome.hotwordPrivate.onEnabledChanged.addListener(
|
| - this.handleEnabledChanged_.bind(this));
|
| - }
|
| - }
|
| + this.training_ = false;
|
|
|
| /**
|
| - * Advances the current step. Begins training if the speech-training
|
| - * page has been reached.
|
| + * The current training state.
|
| + * @private {?TrainingState}
|
| */
|
| - Flow.prototype.advanceStep = function() {
|
| - this.currentStepIndex_++;
|
| - if (this.currentStepIndex_ < this.currentFlow_.length) {
|
| - if (this.currentFlow_[this.currentStepIndex_] == SPEECH_TRAINING)
|
| - this.startTraining();
|
| - this.showStep_.apply(this);
|
| - }
|
| - };
|
| + this.trainingState_ = null;
|
|
|
| /**
|
| - * Gets the appropriate flow and displays its first page.
|
| + * Whether an expected hotword trigger has been received, indexed by
|
| + * training step.
|
| + * @private {boolean[]}
|
| */
|
| - Flow.prototype.startFlow = function() {
|
| - if (chrome.hotwordPrivate && chrome.hotwordPrivate.getLaunchState)
|
| - chrome.hotwordPrivate.getLaunchState(this.startFlowForMode_.bind(this));
|
| - };
|
| + this.hotwordTriggerReceived_ = [];
|
|
|
| /**
|
| - * Starts the training process.
|
| + * Prefix of the element ids for the page that is currently training.
|
| + * @private {string}
|
| */
|
| - Flow.prototype.startTraining = function() {
|
| - // Don't start a training session if one already exists.
|
| - if (this.training_)
|
| - return;
|
| -
|
| - this.training_ = true;
|
| -
|
| - if (chrome.hotwordPrivate.onHotwordTriggered &&
|
| - !chrome.hotwordPrivate.onHotwordTriggered.hasListener(
|
| - this.hotwordTriggerListener_)) {
|
| - chrome.hotwordPrivate.onHotwordTriggered.addListener(
|
| - this.hotwordTriggerListener_);
|
| - }
|
| -
|
| - this.waitForHotwordTrigger_(0);
|
| - if (chrome.hotwordPrivate.startTraining)
|
| - chrome.hotwordPrivate.startTraining();
|
| - };
|
| + this.trainingPagePrefix_ = 'speech-training';
|
|
|
| /**
|
| - * Stops the training process.
|
| + * Whether the speaker model for this flow has been finalized.
|
| + * @private {boolean}
|
| */
|
| - Flow.prototype.stopTraining = function() {
|
| - if (!this.training_)
|
| - return;
|
| -
|
| - this.training_ = false;
|
| - if (chrome.hotwordPrivate.onHotwordTriggered) {
|
| - chrome.hotwordPrivate.onHotwordTriggered.
|
| - removeListener(this.hotwordTriggerListener_);
|
| - }
|
| - if (chrome.hotwordPrivate.stopTraining)
|
| - chrome.hotwordPrivate.stopTraining();
|
| - };
|
| + this.speakerModelFinalized_ = false;
|
|
|
| /**
|
| - * Attempts to enable audio history for the signed-in account.
|
| + * ID of the currently active timeout.
|
| + * @private {?number}
|
| */
|
| - Flow.prototype.enableAudioHistory = function() {
|
| - // Update UI
|
| - $('audio-history-agree').disabled = true;
|
| - $('audio-history-cancel').disabled = true;
|
| -
|
| - $('audio-history-error').hidden = true;
|
| - $('audio-history-wait').hidden = false;
|
| -
|
| - if (chrome.hotwordPrivate.setAudioHistoryEnabled) {
|
| - chrome.hotwordPrivate.setAudioHistoryEnabled(
|
| - true, this.onAudioHistoryRequestCompleted_.bind(this));
|
| - }
|
| - };
|
| -
|
| - // ---- private methods:
|
| + this.timeoutId_ = null;
|
|
|
| /**
|
| - * Shows an error if the audio history setting was not enabled successfully.
|
| - * @private
|
| + * Listener for the speakerModelSaved event.
|
| + * @private {Function}
|
| */
|
| - Flow.prototype.handleAudioHistoryError_ = function() {
|
| - $('audio-history-agree').disabled = false;
|
| - $('audio-history-cancel').disabled = false;
|
| -
|
| - $('audio-history-wait').hidden = true;
|
| - $('audio-history-error').hidden = false;
|
| -
|
| - // Set a timeout before focusing the Enable button so that screenreaders
|
| - // have time to announce the error first.
|
| - this.setTimeout_(function() {
|
| - $('audio-history-agree').focus();
|
| - }.bind(this), 50);
|
| - };
|
| + this.speakerModelFinalizedListener_ =
|
| + this.onSpeakerModelFinalized_.bind(this);
|
|
|
| /**
|
| - * Callback for when an audio history request completes.
|
| - * @param {chrome.hotwordPrivate.AudioHistoryState} state The audio history
|
| - * request state.
|
| - * @private
|
| + * Listener for the hotword trigger event.
|
| + * @private {Function}
|
| */
|
| - Flow.prototype.onAudioHistoryRequestCompleted_ = function(state) {
|
| - if (!state.success || !state.enabled) {
|
| - this.handleAudioHistoryError_();
|
| - return;
|
| - }
|
| + this.hotwordTriggerListener_ = this.handleHotwordTrigger_.bind(this);
|
|
|
| - this.advanceStep();
|
| - };
|
| -
|
| - /**
|
| - * Shows an error if the speaker model has not been finalized.
|
| - * @private
|
| - */
|
| - Flow.prototype.handleSpeakerModelFinalizedError_ = function() {
|
| - if (!this.training_)
|
| - return;
|
| + // Listen for the user locking the screen.
|
| + chrome.idle.onStateChanged.addListener(
|
| + this.handleIdleStateChanged_.bind(this));
|
|
|
| - if (this.speakerModelFinalized_)
|
| - return;
|
| -
|
| - this.updateTrainingState_(TrainingState.ERROR);
|
| - this.stopTraining();
|
| - };
|
| + // Listen for hotword settings changes. This used to detect when the user
|
| + // switches to a different profile.
|
| + if (chrome.hotwordPrivate.onEnabledChanged) {
|
| + chrome.hotwordPrivate.onEnabledChanged.addListener(
|
| + this.handleEnabledChanged_.bind(this));
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Advances the current step. Begins training if the speech-training
|
| + * page has been reached.
|
| + */
|
| +Flow.prototype.advanceStep = function() {
|
| + this.currentStepIndex_++;
|
| + if (this.currentStepIndex_ < this.currentFlow_.length) {
|
| + if (this.currentFlow_[this.currentStepIndex_] == SPEECH_TRAINING)
|
| + this.startTraining();
|
| + 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 (chrome.hotwordPrivate.onHotwordTriggered &&
|
| + !chrome.hotwordPrivate.onHotwordTriggered.hasListener(
|
| + this.hotwordTriggerListener_)) {
|
| + chrome.hotwordPrivate.onHotwordTriggered.addListener(
|
| + this.hotwordTriggerListener_);
|
| + }
|
|
|
| - /**
|
| - * Handles the speaker model finalized event.
|
| - * @private
|
| - */
|
| - Flow.prototype.onSpeakerModelFinalized_ = function() {
|
| - this.speakerModelFinalized_ = true;
|
| - if (chrome.hotwordPrivate.onSpeakerModelSaved) {
|
| - chrome.hotwordPrivate.onSpeakerModelSaved.removeListener(
|
| - this.speakerModelFinalizedListener_);
|
| - }
|
| - this.stopTraining();
|
| - this.setTimeout_(this.finishFlow_.bind(this), 2000);
|
| - };
|
| + this.waitForHotwordTrigger_(0);
|
| + 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.hotwordTriggerListener_);
|
| + }
|
| + if (chrome.hotwordPrivate.stopTraining)
|
| + chrome.hotwordPrivate.stopTraining();
|
| +};
|
| +
|
| +/**
|
| + * Attempts to enable audio history for the signed-in account.
|
| + */
|
| +Flow.prototype.enableAudioHistory = function() {
|
| + // Update UI
|
| + $('audio-history-agree').disabled = true;
|
| + $('audio-history-cancel').disabled = true;
|
| +
|
| + $('audio-history-error').hidden = true;
|
| + $('audio-history-wait').hidden = false;
|
| +
|
| + if (chrome.hotwordPrivate.setAudioHistoryEnabled) {
|
| + chrome.hotwordPrivate.setAudioHistoryEnabled(
|
| + true, this.onAudioHistoryRequestCompleted_.bind(this));
|
| + }
|
| +};
|
| +
|
| +// ---- private methods:
|
| +
|
| +/**
|
| + * Shows an error if the audio history setting was not enabled successfully.
|
| + * @private
|
| + */
|
| +Flow.prototype.handleAudioHistoryError_ = function() {
|
| + $('audio-history-agree').disabled = false;
|
| + $('audio-history-cancel').disabled = false;
|
| +
|
| + $('audio-history-wait').hidden = true;
|
| + $('audio-history-error').hidden = false;
|
| +
|
| + // Set a timeout before focusing the Enable button so that screenreaders
|
| + // have time to announce the error first.
|
| + this.setTimeout_(function() {
|
| + $('audio-history-agree').focus();
|
| + }.bind(this), 50);
|
| +};
|
| +
|
| +/**
|
| + * Callback for when an audio history request completes.
|
| + * @param {chrome.hotwordPrivate.AudioHistoryState} state The audio history
|
| + * request state.
|
| + * @private
|
| + */
|
| +Flow.prototype.onAudioHistoryRequestCompleted_ = function(state) {
|
| + if (!state.success || !state.enabled) {
|
| + this.handleAudioHistoryError_();
|
| + return;
|
| + }
|
|
|
| - /**
|
| - * Completes the training process.
|
| - * @private
|
| - */
|
| - Flow.prototype.finishFlow_ = function() {
|
| - if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
|
| - chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(true,
|
| - this.advanceStep.bind(this));
|
| - }
|
| - };
|
| + this.advanceStep();
|
| +};
|
| +
|
| +/**
|
| + * Shows an error if the speaker model has not been finalized.
|
| + * @private
|
| + */
|
| +Flow.prototype.handleSpeakerModelFinalizedError_ = function() {
|
| + if (!this.training_)
|
| + return;
|
| +
|
| + if (this.speakerModelFinalized_)
|
| + return;
|
| +
|
| + this.updateTrainingState_(TrainingState.ERROR);
|
| + this.stopTraining();
|
| +};
|
| +
|
| +/**
|
| + * Handles the speaker model finalized event.
|
| + * @private
|
| + */
|
| +Flow.prototype.onSpeakerModelFinalized_ = function() {
|
| + this.speakerModelFinalized_ = true;
|
| + if (chrome.hotwordPrivate.onSpeakerModelSaved) {
|
| + chrome.hotwordPrivate.onSpeakerModelSaved.removeListener(
|
| + this.speakerModelFinalizedListener_);
|
| + }
|
| + this.stopTraining();
|
| + this.setTimeout_(this.finishFlow_.bind(this), 2000);
|
| +};
|
| +
|
| +/**
|
| + * Completes the training process.
|
| + * @private
|
| + */
|
| +Flow.prototype.finishFlow_ = function() {
|
| + if (chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled) {
|
| + chrome.hotwordPrivate.setHotwordAlwaysOnSearchEnabled(
|
| + true, this.advanceStep.bind(this));
|
| + }
|
| +};
|
|
|
| - /**
|
| - * Handles a user clicking on the retry button.
|
| - */
|
| - Flow.prototype.handleRetry = function() {
|
| - if (!(this.trainingState_ == TrainingState.TIMEOUT ||
|
| +/**
|
| + * Handles a user clicking on the retry button.
|
| + */
|
| +Flow.prototype.handleRetry = function() {
|
| + if (!(this.trainingState_ == TrainingState.TIMEOUT ||
|
| this.trainingState_ == TrainingState.ERROR))
|
| - return;
|
| -
|
| - this.startTraining();
|
| - this.updateTrainingState_(TrainingState.RESET);
|
| - };
|
| -
|
| - // ---- private methods:
|
| -
|
| - /**
|
| - * Completes the training process.
|
| - * @private
|
| - */
|
| - Flow.prototype.finalizeSpeakerModel_ = function() {
|
| - if (!this.training_)
|
| - return;
|
| -
|
| - // Listen for the success event from the NaCl module.
|
| - if (chrome.hotwordPrivate.onSpeakerModelSaved &&
|
| - !chrome.hotwordPrivate.onSpeakerModelSaved.hasListener(
|
| - this.speakerModelFinalizedListener_)) {
|
| - chrome.hotwordPrivate.onSpeakerModelSaved.addListener(
|
| - this.speakerModelFinalizedListener_);
|
| - }
|
| -
|
| - this.speakerModelFinalized_ = false;
|
| - this.setTimeout_(this.handleSpeakerModelFinalizedError_.bind(this), 30000);
|
| - if (chrome.hotwordPrivate.finalizeSpeakerModel)
|
| - chrome.hotwordPrivate.finalizeSpeakerModel();
|
| - };
|
| -
|
| - /**
|
| - * Returns the current training step.
|
| - * @param {string} curStepClassName The name of the class of the current
|
| - * training step.
|
| - * @return {Object} The current training step, its index, and an array of
|
| - * all training steps. Any of these can be undefined.
|
| - * @private
|
| - */
|
| - Flow.prototype.getCurrentTrainingStep_ = function(curStepClassName) {
|
| - var steps =
|
| - $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
|
| - var curStep =
|
| - $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
|
| -
|
| - return {current: curStep,
|
| - index: Array.prototype.indexOf.call(steps, curStep),
|
| - steps: steps};
|
| - };
|
| -
|
| - /**
|
| - * Updates the training state.
|
| - * @param {TrainingState} state The training state.
|
| - * @private
|
| - */
|
| - Flow.prototype.updateTrainingState_ = function(state) {
|
| - this.trainingState_ = state;
|
| - this.updateErrorUI_();
|
| - };
|
| -
|
| - /**
|
| - * Waits two minutes and then checks for a training error.
|
| - * @param {number} index The index of the training step.
|
| - * @private
|
| - */
|
| - Flow.prototype.waitForHotwordTrigger_ = function(index) {
|
| - if (!this.training_)
|
| - return;
|
| -
|
| - this.hotwordTriggerReceived_[index] = false;
|
| - this.setTimeout_(this.handleTrainingTimeout_.bind(this, index), 120000);
|
| - };
|
| -
|
| - /**
|
| - * Checks for and handles a training error.
|
| - * @param {number} index The index of the training step.
|
| - * @private
|
| - */
|
| - Flow.prototype.handleTrainingTimeout_ = function(index) {
|
| - if (this.hotwordTriggerReceived_[index])
|
| - return;
|
| -
|
| - this.timeoutTraining_();
|
| - };
|
| -
|
| - /**
|
| - * Times out training and updates the UI to show a "retry" message, if
|
| - * currently training.
|
| - * @private
|
| - */
|
| - Flow.prototype.timeoutTraining_ = function() {
|
| - if (!this.training_)
|
| - return;
|
| -
|
| - this.clearTimeout_();
|
| - this.updateTrainingState_(TrainingState.TIMEOUT);
|
| - this.stopTraining();
|
| - };
|
| + return;
|
| +
|
| + this.startTraining();
|
| + this.updateTrainingState_(TrainingState.RESET);
|
| +};
|
| +
|
| +// ---- private methods:
|
| +
|
| +/**
|
| + * Completes the training process.
|
| + * @private
|
| + */
|
| +Flow.prototype.finalizeSpeakerModel_ = function() {
|
| + if (!this.training_)
|
| + return;
|
| +
|
| + // Listen for the success event from the NaCl module.
|
| + if (chrome.hotwordPrivate.onSpeakerModelSaved &&
|
| + !chrome.hotwordPrivate.onSpeakerModelSaved.hasListener(
|
| + this.speakerModelFinalizedListener_)) {
|
| + chrome.hotwordPrivate.onSpeakerModelSaved.addListener(
|
| + this.speakerModelFinalizedListener_);
|
| + }
|
|
|
| - /**
|
| - * Sets a timeout. If any timeout is active, clear it.
|
| - * @param {Function} func The function to invoke when the timeout occurs.
|
| - * @param {number} delay Timeout delay in milliseconds.
|
| - * @private
|
| - */
|
| - Flow.prototype.setTimeout_ = function(func, delay) {
|
| - this.clearTimeout_();
|
| - this.timeoutId_ = setTimeout(function() {
|
| - this.timeoutId_ = null;
|
| - func();
|
| - }, delay);
|
| + this.speakerModelFinalized_ = false;
|
| + this.setTimeout_(this.handleSpeakerModelFinalizedError_.bind(this), 30000);
|
| + if (chrome.hotwordPrivate.finalizeSpeakerModel)
|
| + chrome.hotwordPrivate.finalizeSpeakerModel();
|
| +};
|
| +
|
| +/**
|
| + * Returns the current training step.
|
| + * @param {string} curStepClassName The name of the class of the current
|
| + * training step.
|
| + * @return {Object} The current training step, its index, and an array of
|
| + * all training steps. Any of these can be undefined.
|
| + * @private
|
| + */
|
| +Flow.prototype.getCurrentTrainingStep_ = function(curStepClassName) {
|
| + var steps =
|
| + $(this.trainingPagePrefix_ + '-training').querySelectorAll('.train');
|
| + var curStep =
|
| + $(this.trainingPagePrefix_ + '-training').querySelector('.listening');
|
| +
|
| + return {
|
| + current: curStep,
|
| + index: Array.prototype.indexOf.call(steps, curStep),
|
| + steps: steps
|
| };
|
| -
|
| - /**
|
| - * Clears any currently active timeout.
|
| - * @private
|
| - */
|
| - Flow.prototype.clearTimeout_ = function() {
|
| - if (this.timeoutId_ != null) {
|
| - clearTimeout(this.timeoutId_);
|
| - this.timeoutId_ = null;
|
| - }
|
| - };
|
| -
|
| - /**
|
| - * Updates the training error UI.
|
| - * @private
|
| - */
|
| - Flow.prototype.updateErrorUI_ = function() {
|
| - if (!this.training_)
|
| - return;
|
| -
|
| - var trainingSteps = this.getCurrentTrainingStep_('listening');
|
| - var steps = trainingSteps.steps;
|
| -
|
| - $(this.trainingPagePrefix_ + '-toast').hidden =
|
| - this.trainingState_ != TrainingState.TIMEOUT;
|
| - if (this.trainingState_ == TrainingState.RESET) {
|
| - // We reset the training to begin at the first step.
|
| - // The first step is reset to 'listening', while the rest
|
| - // are reset to 'not-started'.
|
| - var prompt = loadTimeData.getString('trainingFirstPrompt');
|
| - for (var i = 0; i < steps.length; ++i) {
|
| - steps[i].classList.remove('recorded');
|
| - if (i == 0) {
|
| - steps[i].classList.remove('not-started');
|
| - steps[i].classList.add('listening');
|
| - } else {
|
| - steps[i].classList.add('not-started');
|
| - if (i == steps.length - 1)
|
| - prompt = loadTimeData.getString('trainingLastPrompt');
|
| - else
|
| - prompt = loadTimeData.getString('trainingMiddlePrompt');
|
| - }
|
| - steps[i].querySelector('.text').textContent = prompt;
|
| - }
|
| -
|
| - // Reset the buttonbar.
|
| - $(this.trainingPagePrefix_ + '-processing').hidden = true;
|
| - $(this.trainingPagePrefix_ + '-wait').hidden = false;
|
| - $(this.trainingPagePrefix_ + '-error').hidden = true;
|
| - $(this.trainingPagePrefix_ + '-retry').hidden = true;
|
| - } else if (this.trainingState_ == TrainingState.TIMEOUT) {
|
| - var curStep = trainingSteps.current;
|
| - if (curStep) {
|
| - curStep.classList.remove('listening');
|
| - curStep.classList.add('not-started');
|
| +};
|
| +
|
| +/**
|
| + * Updates the training state.
|
| + * @param {TrainingState} state The training state.
|
| + * @private
|
| + */
|
| +Flow.prototype.updateTrainingState_ = function(state) {
|
| + this.trainingState_ = state;
|
| + this.updateErrorUI_();
|
| +};
|
| +
|
| +/**
|
| + * Waits two minutes and then checks for a training error.
|
| + * @param {number} index The index of the training step.
|
| + * @private
|
| + */
|
| +Flow.prototype.waitForHotwordTrigger_ = function(index) {
|
| + if (!this.training_)
|
| + return;
|
| +
|
| + this.hotwordTriggerReceived_[index] = false;
|
| + this.setTimeout_(this.handleTrainingTimeout_.bind(this, index), 120000);
|
| +};
|
| +
|
| +/**
|
| + * Checks for and handles a training error.
|
| + * @param {number} index The index of the training step.
|
| + * @private
|
| + */
|
| +Flow.prototype.handleTrainingTimeout_ = function(index) {
|
| + if (this.hotwordTriggerReceived_[index])
|
| + return;
|
| +
|
| + this.timeoutTraining_();
|
| +};
|
| +
|
| +/**
|
| + * Times out training and updates the UI to show a "retry" message, if
|
| + * currently training.
|
| + * @private
|
| + */
|
| +Flow.prototype.timeoutTraining_ = function() {
|
| + if (!this.training_)
|
| + return;
|
| +
|
| + this.clearTimeout_();
|
| + this.updateTrainingState_(TrainingState.TIMEOUT);
|
| + this.stopTraining();
|
| +};
|
| +
|
| +/**
|
| + * Sets a timeout. If any timeout is active, clear it.
|
| + * @param {Function} func The function to invoke when the timeout occurs.
|
| + * @param {number} delay Timeout delay in milliseconds.
|
| + * @private
|
| + */
|
| +Flow.prototype.setTimeout_ = function(func, delay) {
|
| + this.clearTimeout_();
|
| + this.timeoutId_ = setTimeout(function() {
|
| + this.timeoutId_ = null;
|
| + func();
|
| + }, delay);
|
| +};
|
| +
|
| +/**
|
| + * Clears any currently active timeout.
|
| + * @private
|
| + */
|
| +Flow.prototype.clearTimeout_ = function() {
|
| + if (this.timeoutId_ != null) {
|
| + clearTimeout(this.timeoutId_);
|
| + this.timeoutId_ = null;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Updates the training error UI.
|
| + * @private
|
| + */
|
| +Flow.prototype.updateErrorUI_ = function() {
|
| + if (!this.training_)
|
| + return;
|
| +
|
| + var trainingSteps = this.getCurrentTrainingStep_('listening');
|
| + var steps = trainingSteps.steps;
|
| +
|
| + $(this.trainingPagePrefix_ + '-toast').hidden =
|
| + this.trainingState_ != TrainingState.TIMEOUT;
|
| + if (this.trainingState_ == TrainingState.RESET) {
|
| + // We reset the training to begin at the first step.
|
| + // The first step is reset to 'listening', while the rest
|
| + // are reset to 'not-started'.
|
| + var prompt = loadTimeData.getString('trainingFirstPrompt');
|
| + for (var i = 0; i < steps.length; ++i) {
|
| + steps[i].classList.remove('recorded');
|
| + if (i == 0) {
|
| + steps[i].classList.remove('not-started');
|
| + steps[i].classList.add('listening');
|
| + } else {
|
| + steps[i].classList.add('not-started');
|
| + if (i == steps.length - 1)
|
| + prompt = loadTimeData.getString('trainingLastPrompt');
|
| + else
|
| + prompt = loadTimeData.getString('trainingMiddlePrompt');
|
| }
|
| -
|
| - // Set a timeout before focusing the Retry button so that screenreaders
|
| - // have time to announce the timeout first.
|
| - this.setTimeout_(function() {
|
| - $(this.trainingPagePrefix_ + '-toast').children[1].focus();
|
| - }.bind(this), 50);
|
| - } else if (this.trainingState_ == TrainingState.ERROR) {
|
| - // Update the buttonbar.
|
| - $(this.trainingPagePrefix_ + '-wait').hidden = true;
|
| - $(this.trainingPagePrefix_ + '-error').hidden = false;
|
| - $(this.trainingPagePrefix_ + '-retry').hidden = false;
|
| - $(this.trainingPagePrefix_ + '-processing').hidden = false;
|
| -
|
| - // Set a timeout before focusing the Retry button so that screenreaders
|
| - // have time to announce the error first.
|
| - this.setTimeout_(function() {
|
| - $(this.trainingPagePrefix_ + '-retry').children[0].focus();
|
| - }.bind(this), 50);
|
| + steps[i].querySelector('.text').textContent = prompt;
|
| }
|
| - };
|
| -
|
| - /**
|
| - * Handles a hotword trigger event and updates the training UI.
|
| - * @private
|
| - */
|
| - Flow.prototype.handleHotwordTrigger_ = function() {
|
| - var trainingSteps = this.getCurrentTrainingStep_('listening');
|
| -
|
| - if (!trainingSteps.current)
|
| - return;
|
|
|
| - var index = trainingSteps.index;
|
| - this.hotwordTriggerReceived_[index] = true;
|
| -
|
| - trainingSteps.current.querySelector('.text').textContent =
|
| - loadTimeData.getString('trainingRecorded');
|
| - trainingSteps.current.classList.remove('listening');
|
| - trainingSteps.current.classList.add('recorded');
|
| -
|
| - if (trainingSteps.steps[index + 1]) {
|
| - trainingSteps.steps[index + 1].classList.remove('not-started');
|
| - trainingSteps.steps[index + 1].classList.add('listening');
|
| - this.waitForHotwordTrigger_(index + 1);
|
| - return;
|
| + // Reset the buttonbar.
|
| + $(this.trainingPagePrefix_ + '-processing').hidden = true;
|
| + $(this.trainingPagePrefix_ + '-wait').hidden = false;
|
| + $(this.trainingPagePrefix_ + '-error').hidden = true;
|
| + $(this.trainingPagePrefix_ + '-retry').hidden = true;
|
| + } else if (this.trainingState_ == TrainingState.TIMEOUT) {
|
| + var curStep = trainingSteps.current;
|
| + if (curStep) {
|
| + curStep.classList.remove('listening');
|
| + curStep.classList.add('not-started');
|
| }
|
|
|
| - // Only the last step makes it here.
|
| - var buttonElem = $(this.trainingPagePrefix_ + '-processing').hidden = false;
|
| - this.finalizeSpeakerModel_();
|
| - };
|
| -
|
| - /**
|
| - * Handles a chrome.idle.onStateChanged event and times out the training if
|
| - * the state is "locked".
|
| - * @param {!string} state State, one of "active", "idle", or "locked".
|
| - * @private
|
| - */
|
| - Flow.prototype.handleIdleStateChanged_ = function(state) {
|
| - if (state == 'locked')
|
| - this.timeoutTraining_();
|
| - };
|
| + // Set a timeout before focusing the Retry button so that screenreaders
|
| + // have time to announce the timeout first.
|
| + this.setTimeout_(function() {
|
| + $(this.trainingPagePrefix_ + '-toast').children[1].focus();
|
| + }.bind(this), 50);
|
| + } else if (this.trainingState_ == TrainingState.ERROR) {
|
| + // Update the buttonbar.
|
| + $(this.trainingPagePrefix_ + '-wait').hidden = true;
|
| + $(this.trainingPagePrefix_ + '-error').hidden = false;
|
| + $(this.trainingPagePrefix_ + '-retry').hidden = false;
|
| + $(this.trainingPagePrefix_ + '-processing').hidden = false;
|
| +
|
| + // Set a timeout before focusing the Retry button so that screenreaders
|
| + // have time to announce the error first.
|
| + this.setTimeout_(function() {
|
| + $(this.trainingPagePrefix_ + '-retry').children[0].focus();
|
| + }.bind(this), 50);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Handles a hotword trigger event and updates the training UI.
|
| + * @private
|
| + */
|
| +Flow.prototype.handleHotwordTrigger_ = function() {
|
| + var trainingSteps = this.getCurrentTrainingStep_('listening');
|
| +
|
| + if (!trainingSteps.current)
|
| + return;
|
| +
|
| + var index = trainingSteps.index;
|
| + this.hotwordTriggerReceived_[index] = true;
|
| +
|
| + trainingSteps.current.querySelector('.text').textContent =
|
| + loadTimeData.getString('trainingRecorded');
|
| + trainingSteps.current.classList.remove('listening');
|
| + trainingSteps.current.classList.add('recorded');
|
| +
|
| + if (trainingSteps.steps[index + 1]) {
|
| + trainingSteps.steps[index + 1].classList.remove('not-started');
|
| + trainingSteps.steps[index + 1].classList.add('listening');
|
| + this.waitForHotwordTrigger_(index + 1);
|
| + return;
|
| + }
|
|
|
| - /**
|
| - * Handles a chrome.hotwordPrivate.onEnabledChanged event and times out
|
| - * training if the user is no longer the active user (user switches profiles).
|
| - * @private
|
| - */
|
| - Flow.prototype.handleEnabledChanged_ = function() {
|
| - if (chrome.hotwordPrivate.getStatus) {
|
| - chrome.hotwordPrivate.getStatus(function(status) {
|
| - if (status.userIsActive)
|
| - return;
|
| -
|
| - this.timeoutTraining_();
|
| - }.bind(this));
|
| - }
|
| - };
|
| + // Only the last step makes it here.
|
| + var buttonElem = $(this.trainingPagePrefix_ + '-processing').hidden = false;
|
| + this.finalizeSpeakerModel_();
|
| +};
|
| +
|
| +/**
|
| + * Handles a chrome.idle.onStateChanged event and times out the training if
|
| + * the state is "locked".
|
| + * @param {!string} state State, one of "active", "idle", or "locked".
|
| + * @private
|
| + */
|
| +Flow.prototype.handleIdleStateChanged_ = function(state) {
|
| + if (state == 'locked')
|
| + this.timeoutTraining_();
|
| +};
|
| +
|
| +/**
|
| + * Handles a chrome.hotwordPrivate.onEnabledChanged event and times out
|
| + * training if the user is no longer the active user (user switches profiles).
|
| + * @private
|
| + */
|
| +Flow.prototype.handleEnabledChanged_ = function() {
|
| + if (chrome.hotwordPrivate.getStatus) {
|
| + chrome.hotwordPrivate.getStatus(function(status) {
|
| + if (status.userIsActive)
|
| + return;
|
|
|
| - /**
|
| - * 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];
|
| - if (state.launchMode == LaunchMode.HOTWORD_ONLY) {
|
| - $('intro-description-audio-history-enabled').hidden = false;
|
| - } else if (state.launchMode == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) {
|
| - $('intro-description').hidden = false;
|
| - }
|
| + this.timeoutTraining_();
|
| + }.bind(this));
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * 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];
|
| + if (state.launchMode == LaunchMode.HOTWORD_ONLY) {
|
| + $('intro-description-audio-history-enabled').hidden = false;
|
| + } else if (state.launchMode == LaunchMode.HOTWORD_AND_AUDIO_HISTORY) {
|
| + $('intro-description').hidden = false;
|
| + }
|
|
|
| - this.advanceStep();
|
| - };
|
| + this.advanceStep();
|
| +};
|
|
|
| - /**
|
| - * Displays the current step. If the current step is not the first step,
|
| - * also hides the previous step. Focuses the current step's first button.
|
| - * @private
|
| - */
|
| - Flow.prototype.showStep_ = function() {
|
| - var currentStepId = this.currentFlow_[this.currentStepIndex_];
|
| - var currentStep = document.getElementById(currentStepId);
|
| - currentStep.hidden = false;
|
| +/**
|
| + * Displays the current step. If the current step is not the first step,
|
| + * also hides the previous step. Focuses the current step's first button.
|
| + * @private
|
| + */
|
| +Flow.prototype.showStep_ = function() {
|
| + var currentStepId = this.currentFlow_[this.currentStepIndex_];
|
| + var currentStep = document.getElementById(currentStepId);
|
| + currentStep.hidden = false;
|
|
|
| - cr.ui.setInitialFocus(currentStep);
|
| + cr.ui.setInitialFocus(currentStep);
|
|
|
| - var previousStep = null;
|
| - if (this.currentStepIndex_ > 0)
|
| - previousStep = this.currentFlow_[this.currentStepIndex_ - 1];
|
| + var previousStep = null;
|
| + if (this.currentStepIndex_ > 0)
|
| + previousStep = this.currentFlow_[this.currentStepIndex_ - 1];
|
|
|
| - if (previousStep)
|
| - document.getElementById(previousStep).hidden = true;
|
| + if (previousStep)
|
| + document.getElementById(previousStep).hidden = true;
|
|
|
| - chrome.app.window.current().show();
|
| - };
|
| + chrome.app.window.current().show();
|
| +};
|
|
|
| - window.Flow = Flow;
|
| +window.Flow = Flow;
|
| })();
|
|
|