blob: 7164876856c606c10812883e47f2fcd1412f8019 [file] [log] [blame]
// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
// limitations under the License.
// See the License for the specific language governing permissions and
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS-IS" BASIS,
// Unless required by applicable law or agreed to in writing, software
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// You may obtain a copy of the License at
// you may not use this file except in compliance with the License.
// Licensed under the Apache License, Version 2.0 (the "License");
//
goog.provide('i18n.input.chrome.Statistics');
goog.require('i18n.input.chrome.TriggerType');
goog.scope(function() {
var TriggerType = i18n.input.chrome.TriggerType;
/**
* The statistics util class for IME of ChromeOS.
*
* @constructor
*/
i18n.input.chrome.Statistics = function() {
};
goog.addSingletonGetter(i18n.input.chrome.Statistics);
var Statistics = i18n.input.chrome.Statistics;
/**
* The layout types for stats.
*
* @enum {number}
*/
Statistics.LayoutTypes = {
COMPACT: 0,
COMPACT_SYMBOL: 1,
COMPACT_MORE: 2,
FULL: 3,
A11Y: 4,
HANDWRITING: 5,
EMOJI: 6,
MAX: 7
};
/**
* The commit type for stats.
* Keep this in sync with the enum IMECommitType2 in histograms.xml file in
* chromium.
* Please append new items at the end.
*
* @enum {number}
*/
Statistics.CommitTypes = {
X_X0: 0, // User types X, and chooses X as top suggestion.
X_Y0: 1, // User types X, and chooses Y as top suggestion.
X_X1: 2, // User types X, and chooses X as 2nd suggestion.
X_Y1: 3, // User types X, and chooses Y as 2nd suggestion.
X_X2: 4, // User types X, and chooses X as 3rd/other suggestion.
X_Y2: 5, // User types X, and chooses Y as 3rd/other suggestion.
PREDICTION: 6,
REVERT: 7,
VOICE: 8,
MAX: 9
};
/**
* The event type for gestures typing actions.
* Keep this in sync with the enum IMEGestureEventType in histograms.xml file in
* chromium.
* Please append new items at the end.
*
* @enum {number}
*/
Statistics.GestureTypingEvent = {
TYPED: 0,
DELETED: 1,
REPLACED_0: 2, // User chooses 1st suggestion.
REPLACED_1: 3, // User chooses 2nd suggestion.
REPLACED_2: 4, // User chooses 3rd suggestion.
MAX: 5
};
/**
* Name to use when logging gesture typing metrics.
*
* @const {string}
*/
Statistics.GESTURE_TYPING_METRIC_NAME =
'InputMethod.VirtualKeyboard.GestureTypingEvent';
/**
* The current input method id.
*
* @private {string}
*/
Statistics.prototype.inputMethodId_ = '';
/**
* The current auto correct level.
*
* @private {number}
*/
Statistics.prototype.autoCorrectLevel_ = 0;
/**
* Number of characters entered between each backspace.
*
* @private {number}
*/
Statistics.prototype.charactersBetweenBackspaces_ = 0;
/**
* Maximum pause duration in milliseconds.
*
* @private {number}
* @const
*/
Statistics.prototype.MAX_PAUSE_DURATION_ = 3000;
/**
* Minimum words typed before committing the WPM statistic.
*
* @private {number}
* @const
*/
Statistics.prototype.MIN_WORDS_FOR_WPM_ = 10;
/**
* Timestamp of last activity.
*
* @private {number}
*/
Statistics.prototype.lastActivityTimeStamp_ = 0;
/**
* Time spent typing.
*
* @private {number}
*/
Statistics.prototype.typingDuration_ = 0;
/**
* Whether recording for physical keyboard specially.
*
* @private {boolean}
*/
Statistics.prototype.isPhysicalKeyboard_ = false;
/**
* The length of the last text commit.
*
* @private {number}
*/
Statistics.prototype.lastCommitLength_ = 0;
/**
* The number of characters typed in this session.
*
* @private {number}
*/
Statistics.prototype.charactersCommitted_ = 0;
/**
* The number of characters to ignore when calculating WPM.
*
* @private {number}
*/
Statistics.prototype.droppedKeys_ = 0;
/**
* Sets whether recording for physical keyboard.
*
* @param {boolean} isPhysicalKeyboard .
*/
Statistics.prototype.setPhysicalKeyboard = function(isPhysicalKeyboard) {
this.isPhysicalKeyboard_ = isPhysicalKeyboard;
};
/**
* Sets the current input method id.
*
* @param {string} inputMethodId .
*/
Statistics.prototype.setInputMethodId = function(
inputMethodId) {
this.inputMethodId_ = inputMethodId;
};
/**
* Sets the current auto-correct level.
*
* @param {number} level .
*/
Statistics.prototype.setAutoCorrectLevel = function(
level) {
this.autoCorrectLevel_ = level;
this.recordEnum('InputMethod.AutoCorrectLevel', level, 3);
};
/**
* Records that the controller session ended.
*/
Statistics.prototype.recordSessionEnd = function() {
// Do not record cases where we gain and immediately lose focus. This also
// excudes the focus loss-gain on the new tab page from being counted.
if (this.charactersCommitted_ > 0) {
this.recordValue('InputMethod.VirtualKeyboard.CharactersCommitted',
this.charactersCommitted_, 16384, 50);
var words = (this.charactersCommitted_ - this.droppedKeys_) / 5;
if (this.typingDuration_ > 0 && words > this.MIN_WORDS_FOR_WPM_) {
// Milliseconds to minutes.
var minutes = this.typingDuration_ / 60000;
this.recordValue('InputMethod.VirtualKeyboard.WordsPerMinute',
Math.round(words / minutes), 100, 100);
}
}
this.droppedKeys_ = 0;
this.charactersCommitted_ = 0;
this.lastCommitLength_ = 0;
this.typingDuration_ = 0;
this.lastActivityTimeStamp_ = 0;
};
/**
* Records the metrics for each commit.
*
* @param {string} source .
* @param {string} target .
* @param {number} targetIndex The target index.
* @param {!TriggerType} triggerType The trigger type.
*/
Statistics.prototype.recordCommit = function(
source, target, targetIndex, triggerType) {
if (!this.inputMethodId_) {
return;
}
var CommitTypes = Statistics.CommitTypes;
var commitType = -1;
var length = target.length;
if (triggerType == TriggerType.REVERT) {
length -= this.lastCommitLength_;
commitType = CommitTypes.REVERT;
} else if (triggerType == TriggerType.VOICE) {
commitType = CommitTypes.VOICE;
} else if (triggerType == TriggerType.RESET) {
// Increment to include space.
length++;
} else if (triggerType == TriggerType.CANDIDATE ||
triggerType == TriggerType.SPACE) {
if (!source && target) {
commitType = CommitTypes.PREDICTION;
} else if (targetIndex == 0 && source == target) {
commitType = CommitTypes.X_X0;
} else if (targetIndex == 0 && source != target) {
commitType = CommitTypes.X_Y0;
} else if (targetIndex == 1 && source == target) {
commitType = CommitTypes.X_X1;
} else if (targetIndex == 1 && source != target) {
commitType = CommitTypes.X_Y1;
} else if (targetIndex > 1 && source == target) {
commitType = CommitTypes.X_X2;
} else if (targetIndex > 1 && source != target) {
commitType = CommitTypes.X_Y2;
}
}
this.lastCommitLength_ = length;
this.charactersCommitted_ += length;
if (commitType < 0) {
return;
}
// For latin transliteration, record the logs under the name with 'Pk' which
// means Physical Keyboard.
var name = this.isPhysicalKeyboard_ ?
'InputMethod.PkCommit.' : 'InputMethod.Commit.';
var type = this.isPhysicalKeyboard_ ? 'Type' : 'Type2';
var self = this;
var record = function(suffix) {
self.recordEnum(name + 'Index' + suffix, targetIndex + 1, 20);
self.recordEnum(name + type + suffix, commitType, CommitTypes.MAX);
};
record('');
if (/^pinyin/.test(this.inputMethodId_)) {
record('.Pinyin');
} else if (/^xkb:us/.test(this.inputMethodId_)) {
record('.US');
record('.US.AC' + this.autoCorrectLevel_);
} else if (/^xkb:fr/.test(this.inputMethodId_)) {
record('.FR');
record('.FR.AC' + this.autoCorrectLevel_);
}
};
/**
* Records the latency value for stats.
*
* @param {string} name .
* @param {number} timeInMs .
*/
Statistics.prototype.recordLatency = function(
name, timeInMs) {
this.recordValue(name, timeInMs, 1000, 50);
};
/**
* Gets the layout type for stats.
*
* @param {string} layoutCode .
* @param {boolean} isA11yMode .
* @return {Statistics.LayoutTypes}
*/
Statistics.prototype.getLayoutType = function(layoutCode, isA11yMode) {
var LayoutTypes = Statistics.LayoutTypes;
var layoutType = LayoutTypes.MAX;
if (isA11yMode) {
layoutType = LayoutTypes.A11Y;
} else if (/compact/.test(layoutCode)) {
if (/symbol/.test(layoutCode)) {
layoutType = LayoutTypes.COMPACT_SYMBOL;
} else if (/more/.test(layoutCode)) {
layoutType = LayoutTypes.COMPACT_MORE;
} else {
layoutType = LayoutTypes.COMPACT;
}
} else if (/^hwt/.test(layoutCode)) {
layoutType = LayoutTypes.HANDWRITING;
} else if (/^emoji/.test(layoutCode)) {
layoutType = LayoutTypes.EMOJI;
}
return layoutType;
};
/**
* Records the layout usage.
*
* @param {string} layoutCode The layout code to be switched to.
* @param {boolean} isA11yMode .
*/
Statistics.prototype.recordLayout = function(
layoutCode, isA11yMode) {
this.recordEnum('InputMethod.VirtualKeyboard.Layout',
this.getLayoutType(layoutCode, isA11yMode), Statistics.LayoutTypes.MAX);
};
/**
* Records the layout switching action.
*/
Statistics.prototype.recordLayoutSwitch = function() {
this.recordValue('InputMethod.VirtualKeyboard.LayoutSwitch', 1, 1, 1);
};
/**
* Records enum value.
*
* @param {string} name .
* @param {number} enumVal .
* @param {number} enumCount .
*/
Statistics.prototype.recordEnum = function(
name, enumVal, enumCount) {
if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
chrome.metricsPrivate.recordValue({
'metricName': name,
'type': 'histogram-linear',
'min': 0,
'max': enumCount - 1,
'buckets': enumCount
}, enumVal);
}
};
/**
* Records count value.
*
* @param {string} name .
* @param {number} count .
* @param {number} max .
* @param {number} bucketCount .
*/
Statistics.prototype.recordValue = function(
name, count, max, bucketCount) {
if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
chrome.metricsPrivate.recordValue({
'metricName': name,
'type': 'histogram-log',
'min': 0,
'max': max,
'buckets': bucketCount
}, count);
}
};
/**
* Records a key down.
*/
Statistics.prototype.recordCharacterKey = function() {
var now = Date.now();
if (this.lastActivityTimeStamp_) {
if (now < (this.lastActivityTimeStamp_ + this.MAX_PAUSE_DURATION_)) {
this.typingDuration_ += (now - this.lastActivityTimeStamp_);
} else {
// Exceeded pause duration. Ignore this character.
this.droppedKeys_++;
}
} else {
// Ignore the first character in the new session.
this.droppedKeys_++;
}
this.lastActivityTimeStamp_ = now;
this.charactersBetweenBackspaces_++;
};
/**
* Records a backspace.
*/
Statistics.prototype.recordBackspace = function() {
// Ignore multiple backspaces typed in succession.
if (this.charactersBetweenBackspaces_ > 0) {
this.recordValue(
'InputMethod.VirtualKeyboard.CharactersBetweenBackspaces',
this.charactersBetweenBackspaces_, 4096, 50);
}
this.charactersBetweenBackspaces_ = 0;
};
}); // goog.scope