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 length
= target
.length
;
235 // Increment to include space.
236 if (triggerType
== TriggerType
.RESET
) {
238 } else if (triggerType
== TriggerType
.REVERT
) {
239 length
-= this.lastCommitLength_
;
241 this.lastCommitLength_
= length
;
242 this.charactersCommitted_
+= length
;
244 var CommitTypes
= Statistics
.CommitTypes
;
246 if (targetIndex
== 0 && source
== target
) {
247 commitType
= CommitTypes
.X_X0
;
248 } else if (targetIndex
== 0 && source
!= target
) {
249 commitType
= CommitTypes
.X_Y0
;
250 } else if (targetIndex
== 1 && source
== target
) {
251 commitType
= CommitTypes
.X_X1
;
252 } else if (targetIndex
== 1 && source
!= target
) {
253 commitType
= CommitTypes
.X_Y1
;
254 } else if (targetIndex
> 1 && source
== target
) {
255 commitType
= CommitTypes
.X_X2
;
256 } else if (targetIndex
> 1 && source
!= target
) {
257 commitType
= CommitTypes
.X_Y2
;
258 } else if (!source
&& source
!= target
) {
259 commitType
= CommitTypes
.PREDICTION
;
260 } else if (triggerType
== TriggerType
.REVERT
) {
261 commitType
= CommitTypes
.REVERT
;
262 } else if (triggerType
== TriggerType
.VOICE
) {
263 commitType
= CommitTypes
.VOICE
;
265 if (commitType
< 0) {
269 // For latin transliteration, record the logs under the name with 'Pk' which
270 // means Physical Keyboard.
271 var name
= this.isPhysicalKeyboard_
?
272 'InputMethod.PkCommit.' : 'InputMethod.Commit.';
273 var type
= this.isPhysicalKeyboard_
? 'Type' : 'Type2';
276 var record = function(suffix
) {
277 self
.recordEnum(name
+ 'Index' + suffix
, targetIndex
+ 1, 20);
278 self
.recordEnum(name
+ type
+ suffix
, commitType
, CommitTypes
.MAX
);
283 if (/^pinyin/.test(this.inputMethodId_
)) {
285 } else if (/^xkb:us/.test(this.inputMethodId_
)) {
287 record('.US.AC' + this.autoCorrectLevel_
);
288 } else if (/^xkb:fr/.test(this.inputMethodId_
)) {
290 record('.FR.AC' + this.autoCorrectLevel_
);
296 * Records the latency value for stats.
298 * @param {string} name .
299 * @param {number} timeInMs .
301 Statistics
.prototype.recordLatency = function(
303 this.recordValue(name
, timeInMs
, 1000, 50);
308 * Gets the layout type for stats.
310 * @param {string} layoutCode .
311 * @param {boolean} isA11yMode .
312 * @return {Statistics.LayoutTypes}
314 Statistics
.prototype.getLayoutType = function(layoutCode
, isA11yMode
) {
315 var LayoutTypes
= Statistics
.LayoutTypes
;
316 var layoutType
= LayoutTypes
.MAX
;
318 layoutType
= LayoutTypes
.A11Y
;
319 } else if (/compact/.test(layoutCode
)) {
320 if (/symbol/.test(layoutCode
)) {
321 layoutType
= LayoutTypes
.COMPACT_SYMBOL
;
322 } else if (/more/.test(layoutCode
)) {
323 layoutType
= LayoutTypes
.COMPACT_MORE
;
325 layoutType
= LayoutTypes
.COMPACT
;
327 } else if (/^hwt/.test(layoutCode
)) {
328 layoutType
= LayoutTypes
.HANDWRITING
;
329 } else if (/^emoji/.test(layoutCode
)) {
330 layoutType
= LayoutTypes
.EMOJI
;
337 * Records the layout usage.
339 * @param {string} layoutCode The layout code to be switched to.
340 * @param {boolean} isA11yMode .
342 Statistics
.prototype.recordLayout = function(
343 layoutCode
, isA11yMode
) {
344 this.recordEnum('InputMethod.VirtualKeyboard.Layout',
345 this.getLayoutType(layoutCode
, isA11yMode
), Statistics
.LayoutTypes
.MAX
);
350 * Records the layout switching action.
352 Statistics
.prototype.recordLayoutSwitch = function() {
353 this.recordValue('InputMethod.VirtualKeyboard.LayoutSwitch', 1, 1, 1);
358 * Records enum value.
360 * @param {string} name .
361 * @param {number} enumVal .
362 * @param {number} enumCount .
364 Statistics
.prototype.recordEnum = function(
365 name
, enumVal
, enumCount
) {
366 if (chrome
.metricsPrivate
&& chrome
.metricsPrivate
.recordValue
) {
367 chrome
.metricsPrivate
.recordValue({
369 'type': 'histogram-linear',
371 'max': enumCount
- 1,
379 * Records count value.
381 * @param {string} name .
382 * @param {number} count .
383 * @param {number} max .
384 * @param {number} bucketCount .
386 Statistics
.prototype.recordValue = function(
387 name
, count
, max
, bucketCount
) {
388 if (chrome
.metricsPrivate
&& chrome
.metricsPrivate
.recordValue
) {
389 chrome
.metricsPrivate
.recordValue({
391 'type': 'histogram-log',
394 'buckets': bucketCount
401 * Records a key down.
403 Statistics
.prototype.recordCharacterKey = function() {
404 var now
= Date
.now();
405 if (this.lastActivityTimeStamp_
) {
406 if (now
< (this.lastActivityTimeStamp_
+ this.MAX_PAUSE_DURATION_
)) {
407 this.typingDuration_
+= (now
- this.lastActivityTimeStamp_
);
409 // Exceeded pause duration. Ignore this character.
413 // Ignore the first character in the new session.
416 this.lastActivityTimeStamp_
= now
;
417 this.charactersBetweenBackspaces_
++;
422 * Records a backspace.
424 Statistics
.prototype.recordBackspace = function() {
425 // Ignore multiple backspaces typed in succession.
426 if (this.charactersBetweenBackspaces_
> 0) {
428 'InputMethod.VirtualKeyboard.CharactersBetweenBackspaces',
429 this.charactersBetweenBackspaces_
, 4096, 50);
431 this.charactersBetweenBackspaces_
= 0;