1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License.
3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License");
14 goog.provide('i18n.input.chrome.Statistics');
16 goog.require('i18n.input.chrome.TriggerType');
18 goog.scope(function() {
19 var TriggerType = i18n.input.chrome.TriggerType;
24 * The statistics util class for IME of ChromeOS.
28 i18n.input.chrome.Statistics = function() {
30 goog.addSingletonGetter(i18n.input.chrome.Statistics);
31 var Statistics = i18n.input.chrome.Statistics;
35 * The layout types for stats.
39 Statistics.LayoutTypes = {
52 * The commit type for stats.
53 * Keep this in sync with the enum IMECommitType2 in histograms.xml file in
55 * For adding new items, please append it at the end.
59 Statistics.CommitTypes = {
60 X_X0: 0, // User types X, and chooses X as top suggestion.
61 X_Y0: 1, // User types X, and chooses Y as top suggestion.
62 X_X1: 2, // User types X, and chooses X as 2nd suggestion.
63 X_Y1: 3, // User types X, and chooses Y as 2nd suggestion.
64 X_X2: 4, // User types X, and chooses X as 3rd/other suggestion.
65 X_Y2: 5, // User types X, and chooses Y as 3rd/other suggestion.
74 * The current input method id.
78 Statistics.prototype.inputMethodId_ = '';
82 * The current auto correct level.
86 Statistics.prototype.autoCorrectLevel_ = 0;
90 * Number of characters entered between each backspace.
94 Statistics.prototype.charactersBetweenBackspaces_ = 0;
98 * Maximum pause duration in milliseconds.
103 Statistics.prototype.MAX_PAUSE_DURATION_ = 3000;
107 * Minimum words typed before committing the WPM statistic.
112 Statistics.prototype.MIN_WORDS_FOR_WPM_ = 10;
116 * Timestamp of last activity.
120 Statistics.prototype.lastActivityTimeStamp_ = 0;
128 Statistics.prototype.typingDuration_ = 0;
132 * Whether recording for physical keyboard specially.
136 Statistics.prototype.isPhysicalKeyboard_ = false;
140 * The length of the last text commit.
144 Statistics.prototype.lastCommitLength_ = 0;
148 * The number of characters typed in this session.
152 Statistics.prototype.charactersCommitted_ = 0;
156 * The number of characters to ignore when calculating WPM.
160 Statistics.prototype.droppedKeys_ = 0;
164 * Sets whether recording for physical keyboard.
166 * @param {boolean} isPhysicalKeyboard .
168 Statistics.prototype.setPhysicalKeyboard = function(isPhysicalKeyboard) {
169 this.isPhysicalKeyboard_ = isPhysicalKeyboard;
174 * Sets the current input method id.
176 * @param {string} inputMethodId .
178 Statistics.prototype.setInputMethodId = function(
180 this.inputMethodId_ = inputMethodId;
185 * Sets the current auto-correct level.
187 * @param {number} level .
189 Statistics.prototype.setAutoCorrectLevel = function(
191 this.autoCorrectLevel_ = level;
192 this.recordEnum('InputMethod.AutoCorrectLevel', level, 3);
197 * Records that the controller session ended.
199 Statistics.prototype.recordSessionEnd = function() {
200 // Do not record cases where we gain and immediately lose focus. This also
201 // excudes the focus loss-gain on the new tab page from being counted.
202 if (this.charactersCommitted_ > 0) {
203 this.recordValue('InputMethod.VirtualKeyboard.CharactersCommitted',
204 this.charactersCommitted_, 16384, 50);
205 var words = (this.charactersCommitted_ - this.droppedKeys_) / 5;
206 if (this.typingDuration_ > 0 && words > this.MIN_WORDS_FOR_WPM_) {
207 // Milliseconds to minutes.
208 var minutes = this.typingDuration_ / 60000;
209 this.recordValue('InputMethod.VirtualKeyboard.WordsPerMinute',
210 Math.round(words / minutes), 100, 100);
213 this.droppedKeys_ = 0;
214 this.charactersCommitted_ = 0;
215 this.lastCommitLength_ = 0;
216 this.typingDuration_ = 0;
217 this.lastActivityTimeStamp_ = 0;
222 * Records the metrics for each commit.
224 * @param {string} source .
225 * @param {string} target .
226 * @param {number} targetIndex The target index.
227 * @param {!TriggerType} triggerType The trigger type.
229 Statistics.prototype.recordCommit = function(
230 source, target, targetIndex, triggerType) {
231 if (!this.inputMethodId_) {
234 var CommitTypes = Statistics.CommitTypes;
236 var length = target.length;
238 if (triggerType == TriggerType.REVERT) {
239 length -= this.lastCommitLength_;
240 commitType = CommitTypes.REVERT;
241 } else if (triggerType == TriggerType.VOICE) {
242 commitType = CommitTypes.VOICE;
243 } else if (triggerType == TriggerType.RESET) {
244 // Increment to include space.
246 } else if (triggerType == TriggerType.CANDIDATE ||
247 triggerType == TriggerType.SPACE) {
248 if (!source && target) {
249 commitType = CommitTypes.PREDICTION;
250 } else if (targetIndex == 0 && source == target) {
251 commitType = CommitTypes.X_X0;
252 } else if (targetIndex == 0 && source != target) {
253 commitType = CommitTypes.X_Y0;
254 } else if (targetIndex == 1 && source == target) {
255 commitType = CommitTypes.X_X1;
256 } else if (targetIndex == 1 && source != target) {
257 commitType = CommitTypes.X_Y1;
258 } else if (targetIndex > 1 && source == target) {
259 commitType = CommitTypes.X_X2;
260 } else if (targetIndex > 1 && source != target) {
261 commitType = CommitTypes.X_Y2;
264 this.lastCommitLength_ = length;
265 this.charactersCommitted_ += length;
267 if (commitType < 0) {
271 // For latin transliteration, record the logs under the name with 'Pk' which
272 // means Physical Keyboard.
273 var name = this.isPhysicalKeyboard_ ?
274 'InputMethod.PkCommit.' : 'InputMethod.Commit.';
275 var type = this.isPhysicalKeyboard_ ? 'Type' : 'Type2';
278 var record = function(suffix) {
279 self.recordEnum(name + 'Index' + suffix, targetIndex + 1, 20);
280 self.recordEnum(name + type + suffix, commitType, CommitTypes.MAX);
285 if (/^pinyin/.test(this.inputMethodId_)) {
287 } else if (/^xkb:us/.test(this.inputMethodId_)) {
289 record('.US.AC' + this.autoCorrectLevel_);
290 } else if (/^xkb:fr/.test(this.inputMethodId_)) {
292 record('.FR.AC' + this.autoCorrectLevel_);
298 * Records the latency value for stats.
300 * @param {string} name .
301 * @param {number} timeInMs .
303 Statistics.prototype.recordLatency = function(
305 this.recordValue(name, timeInMs, 1000, 50);
310 * Gets the layout type for stats.
312 * @param {string} layoutCode .
313 * @param {boolean} isA11yMode .
314 * @return {Statistics.LayoutTypes}
316 Statistics.prototype.getLayoutType = function(layoutCode, isA11yMode) {
317 var LayoutTypes = Statistics.LayoutTypes;
318 var layoutType = LayoutTypes.MAX;
320 layoutType = LayoutTypes.A11Y;
321 } else if (/compact/.test(layoutCode)) {
322 if (/symbol/.test(layoutCode)) {
323 layoutType = LayoutTypes.COMPACT_SYMBOL;
324 } else if (/more/.test(layoutCode)) {
325 layoutType = LayoutTypes.COMPACT_MORE;
327 layoutType = LayoutTypes.COMPACT;
329 } else if (/^hwt/.test(layoutCode)) {
330 layoutType = LayoutTypes.HANDWRITING;
331 } else if (/^emoji/.test(layoutCode)) {
332 layoutType = LayoutTypes.EMOJI;
339 * Records the layout usage.
341 * @param {string} layoutCode The layout code to be switched to.
342 * @param {boolean} isA11yMode .
344 Statistics.prototype.recordLayout = function(
345 layoutCode, isA11yMode) {
346 this.recordEnum('InputMethod.VirtualKeyboard.Layout',
347 this.getLayoutType(layoutCode, isA11yMode), Statistics.LayoutTypes.MAX);
352 * Records the layout switching action.
354 Statistics.prototype.recordLayoutSwitch = function() {
355 this.recordValue('InputMethod.VirtualKeyboard.LayoutSwitch', 1, 1, 1);
360 * Records enum value.
362 * @param {string} name .
363 * @param {number} enumVal .
364 * @param {number} enumCount .
366 Statistics.prototype.recordEnum = function(
367 name, enumVal, enumCount) {
368 if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
369 chrome.metricsPrivate.recordValue({
371 'type': 'histogram-linear',
373 'max': enumCount - 1,
381 * Records count value.
383 * @param {string} name .
384 * @param {number} count .
385 * @param {number} max .
386 * @param {number} bucketCount .
388 Statistics.prototype.recordValue = function(
389 name, count, max, bucketCount) {
390 if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
391 chrome.metricsPrivate.recordValue({
393 'type': 'histogram-log',
396 'buckets': bucketCount
403 * Records a key down.
405 Statistics.prototype.recordCharacterKey = function() {
406 var now = Date.now();
407 if (this.lastActivityTimeStamp_) {
408 if (now < (this.lastActivityTimeStamp_ + this.MAX_PAUSE_DURATION_)) {
409 this.typingDuration_ += (now - this.lastActivityTimeStamp_);
411 // Exceeded pause duration. Ignore this character.
415 // Ignore the first character in the new session.
418 this.lastActivityTimeStamp_ = now;
419 this.charactersBetweenBackspaces_++;
424 * Records a backspace.
426 Statistics.prototype.recordBackspace = function() {
427 // Ignore multiple backspaces typed in succession.
428 if (this.charactersBetweenBackspaces_ > 0) {
430 'InputMethod.VirtualKeyboard.CharactersBetweenBackspaces',
431 this.charactersBetweenBackspaces_, 4096, 50);
433 this.charactersBetweenBackspaces_ = 0;