Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / google_input_tools / src / chrome / os / statistics.js
blob2ec93aa8e2794d2f3585c564d702934bfef0e409
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
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
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;
23 /**
24  * The statistics util class for IME of ChromeOS.
25  *
26  * @constructor
27  */
28 i18n.input.chrome.Statistics = function() {
30 goog.addSingletonGetter(i18n.input.chrome.Statistics);
31 var Statistics = i18n.input.chrome.Statistics;
34 /**
35  * The layout types for stats.
36  *
37  * @enum {number}
38  */
39 Statistics.LayoutTypes = {
40   COMPACT: 0,
41   COMPACT_SYMBOL: 1,
42   COMPACT_MORE: 2,
43   FULL: 3,
44   A11Y: 4,
45   HANDWRITING: 5,
46   EMOJI: 6,
47   MAX: 7
51 /**
52  * The commit type for stats.
53  * Keep this in sync with the enum IMECommitType2 in histograms.xml file in
54  * chromium.
55  * For adding new items, please append it at the end.
56  *
57  * @enum {number}
58  */
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.
66   PREDICTION: 6,
67   REVERT: 7,
68   VOICE: 8,
69   MAX: 9
73 /**
74  * The current input method id.
75  *
76  * @private {string}
77  */
78 Statistics.prototype.inputMethodId_ = '';
81 /**
82  * The current auto correct level.
83  *
84  * @private {number}
85  */
86 Statistics.prototype.autoCorrectLevel_ = 0;
89 /**
90  * Number of characters entered between each backspace.
91  *
92  * @private {number}
93  */
94 Statistics.prototype.charactersBetweenBackspaces_ = 0;
97 /**
98  * Maximum pause duration in milliseconds.
99  *
100  * @private {number}
101  * @const
102  */
103 Statistics.prototype.MAX_PAUSE_DURATION_ = 3000;
107  * Minimum words typed before committing the WPM statistic.
109  * @private {number}
110  * @const
111  */
112 Statistics.prototype.MIN_WORDS_FOR_WPM_ = 10;
116  * Timestamp of last activity.
118  * @private {number}
119  */
120 Statistics.prototype.lastActivityTimeStamp_ = 0;
124  * Time spent typing.
126  * @private {number}
127  */
128 Statistics.prototype.typingDuration_ = 0;
132  * Whether recording for physical keyboard specially.
134  * @private {boolean}
135  */
136 Statistics.prototype.isPhysicalKeyboard_ = false;
140  * The length of the last text commit.
142  * @private {number}
143  */
144 Statistics.prototype.lastCommitLength_ = 0;
148  * The number of characters typed in this session.
150  * @private {number}
151  */
152 Statistics.prototype.charactersCommitted_ = 0;
156  * The number of characters to ignore when calculating WPM.
158  * @private {number}
159  */
160 Statistics.prototype.droppedKeys_ = 0;
164  * Sets whether recording for physical keyboard.
166  * @param {boolean} isPhysicalKeyboard .
167  */
168 Statistics.prototype.setPhysicalKeyboard = function(isPhysicalKeyboard) {
169   this.isPhysicalKeyboard_ = isPhysicalKeyboard;
174  * Sets the current input method id.
176  * @param {string} inputMethodId .
177  */
178 Statistics.prototype.setInputMethodId = function(
179     inputMethodId) {
180   this.inputMethodId_ = inputMethodId;
185  * Sets the current auto-correct level.
187  * @param {number} level .
188  */
189 Statistics.prototype.setAutoCorrectLevel = function(
190     level) {
191   this.autoCorrectLevel_ = level;
192   this.recordEnum('InputMethod.AutoCorrectLevel', level, 3);
197  * Records that the controller session ended.
198  */
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);
211     }
212   }
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.
228  */
229 Statistics.prototype.recordCommit = function(
230     source, target, targetIndex, triggerType) {
231   if (!this.inputMethodId_) {
232     return;
233   }
234   var CommitTypes = Statistics.CommitTypes;
235   var commitType = -1;
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.
245     length++;
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;
262     }
263   }
264   this.lastCommitLength_ = length;
265   this.charactersCommitted_ += length;
267   if (commitType < 0) {
268     return;
269   }
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';
277   var self = this;
278   var record = function(suffix) {
279     self.recordEnum(name + 'Index' + suffix, targetIndex + 1, 20);
280     self.recordEnum(name + type + suffix, commitType, CommitTypes.MAX);
281   };
283   record('');
285   if (/^pinyin/.test(this.inputMethodId_)) {
286     record('.Pinyin');
287   } else if (/^xkb:us/.test(this.inputMethodId_)) {
288     record('.US');
289     record('.US.AC' + this.autoCorrectLevel_);
290   } else if (/^xkb:fr/.test(this.inputMethodId_)) {
291     record('.FR');
292     record('.FR.AC' + this.autoCorrectLevel_);
293   }
298  * Records the latency value for stats.
300  * @param {string} name .
301  * @param {number} timeInMs .
302  */
303 Statistics.prototype.recordLatency = function(
304     name, timeInMs) {
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}
315  */
316 Statistics.prototype.getLayoutType = function(layoutCode, isA11yMode) {
317   var LayoutTypes = Statistics.LayoutTypes;
318   var layoutType = LayoutTypes.MAX;
319   if (isA11yMode) {
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;
326     } else {
327       layoutType = LayoutTypes.COMPACT;
328     }
329   } else if (/^hwt/.test(layoutCode)) {
330     layoutType = LayoutTypes.HANDWRITING;
331   } else if (/^emoji/.test(layoutCode)) {
332     layoutType = LayoutTypes.EMOJI;
333   }
334   return layoutType;
339  * Records the layout usage.
341  * @param {string} layoutCode The layout code to be switched to.
342  * @param {boolean} isA11yMode .
343  */
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.
353  */
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 .
365  */
366 Statistics.prototype.recordEnum = function(
367     name, enumVal, enumCount) {
368   if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
369     chrome.metricsPrivate.recordValue({
370       'metricName': name,
371       'type': 'histogram-linear',
372       'min': 0,
373       'max': enumCount - 1,
374       'buckets': enumCount
375     }, enumVal);
376   }
381  * Records count value.
383  * @param {string} name .
384  * @param {number} count .
385  * @param {number} max .
386  * @param {number} bucketCount .
387  */
388 Statistics.prototype.recordValue = function(
389     name, count, max, bucketCount) {
390   if (chrome.metricsPrivate && chrome.metricsPrivate.recordValue) {
391     chrome.metricsPrivate.recordValue({
392       'metricName': name,
393       'type': 'histogram-log',
394       'min': 0,
395       'max': max,
396       'buckets': bucketCount
397     }, count);
398   }
403  * Records a key down.
404  */
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_);
410     } else {
411       // Exceeded pause duration. Ignore this character.
412       this.droppedKeys_++;
413     }
414   } else {
415     // Ignore the first character in the new session.
416     this.droppedKeys_++;
417   }
418   this.lastActivityTimeStamp_ = now;
419   this.charactersBetweenBackspaces_++;
424  * Records a backspace.
425  */
426 Statistics.prototype.recordBackspace = function() {
427   // Ignore multiple backspaces typed in succession.
428   if (this.charactersBetweenBackspaces_ > 0) {
429     this.recordValue(
430         'InputMethod.VirtualKeyboard.CharactersBetweenBackspaces',
431         this.charactersBetweenBackspaces_, 4096, 50);
432   }
433   this.charactersBetweenBackspaces_ = 0;
435 });  // goog.scope