1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.research; 18 19 import android.util.Log; 20 21 import com.android.inputmethod.latin.Constants; 22 import com.android.inputmethod.latin.define.ProductionFlag; 23 24 public class Statistics { 25 private static final String TAG = Statistics.class.getSimpleName(); 26 private static final boolean DEBUG = false 27 && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; 28 29 // TODO: Cleanup comments to only including those giving meaningful information. 30 // Number of characters entered during a typing session 31 int mCharCount; 32 // Number of letter characters entered during a typing session 33 int mLetterCount; 34 // Number of number characters entered 35 int mNumberCount; 36 // Number of space characters entered 37 int mSpaceCount; 38 // Number of delete operations entered (taps on the backspace key) 39 int mDeleteKeyCount; 40 // Number of words entered during a session. 41 int mWordCount; 42 // Number of words found in the dictionary. 43 int mDictionaryWordCount; 44 // Number of words split and spaces automatically entered. 45 int mSplitWordsCount; 46 // Number of words entered during a session. 47 int mCorrectedWordsCount; 48 // Number of gestures that were input. 49 int mGesturesInputCount; 50 // Number of gestures that were deleted. 51 int mGesturesDeletedCount; 52 // Total number of characters in words entered by gesture. 53 int mGesturesCharsCount; 54 // Number of manual suggestions chosen. 55 int mManualSuggestionsCount; 56 // Number of times that autocorrection was invoked. 57 int mAutoCorrectionsCount; 58 // Number of times a commit was reverted in this session. 59 int mRevertCommitsCount; 60 // Whether the text field was empty upon editing 61 boolean mIsEmptyUponStarting; 62 boolean mIsEmptinessStateKnown; 63 64 // Timers to count average time to enter a key, first press a delete key, 65 // between delete keys, and then to return typing after a delete key. 66 final AverageTimeCounter mKeyCounter = new AverageTimeCounter(); 67 final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter(); 68 final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter(); 69 final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter(); 70 71 static class AverageTimeCounter { 72 int mCount; 73 int mTotalTime; 74 75 public void reset() { 76 mCount = 0; 77 mTotalTime = 0; 78 } 79 80 public void add(long deltaTime) { 81 mCount++; 82 mTotalTime += deltaTime; 83 } 84 85 public int getAverageTime() { 86 if (mCount == 0) { 87 return 0; 88 } 89 return mTotalTime / mCount; 90 } 91 } 92 93 // To account for the interruptions when the user's attention is directed elsewhere, times 94 // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic. 95 public static final int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds 96 public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds 97 98 // The last time that a tap was performed 99 private long mLastTapTime; 100 // The type of the last keypress (delete key or not) 101 boolean mIsLastKeyDeleteKey; 102 103 private static final Statistics sInstance = new Statistics(); 104 105 public static Statistics getInstance() { 106 return sInstance; 107 } 108 109 private Statistics() { 110 reset(); 111 } 112 113 public void reset() { 114 mCharCount = 0; 115 mLetterCount = 0; 116 mNumberCount = 0; 117 mSpaceCount = 0; 118 mDeleteKeyCount = 0; 119 mWordCount = 0; 120 mDictionaryWordCount = 0; 121 mSplitWordsCount = 0; 122 mCorrectedWordsCount = 0; 123 mGesturesInputCount = 0; 124 mGesturesDeletedCount = 0; 125 mManualSuggestionsCount = 0; 126 mRevertCommitsCount = 0; 127 mAutoCorrectionsCount = 0; 128 mIsEmptyUponStarting = true; 129 mIsEmptinessStateKnown = false; 130 mKeyCounter.reset(); 131 mBeforeDeleteKeyCounter.reset(); 132 mDuringRepeatedDeleteKeysCounter.reset(); 133 mAfterDeleteKeyCounter.reset(); 134 mGesturesCharsCount = 0; 135 mGesturesDeletedCount = 0; 136 137 mLastTapTime = 0; 138 mIsLastKeyDeleteKey = false; 139 } 140 141 public void recordChar(int codePoint, long time) { 142 if (DEBUG) { 143 Log.d(TAG, "recordChar() called"); 144 } 145 if (codePoint == Constants.CODE_DELETE) { 146 mDeleteKeyCount++; 147 recordUserAction(time, true /* isDeletion */); 148 } else { 149 mCharCount++; 150 if (Character.isDigit(codePoint)) { 151 mNumberCount++; 152 } 153 if (Character.isLetter(codePoint)) { 154 mLetterCount++; 155 } 156 if (Character.isSpaceChar(codePoint)) { 157 mSpaceCount++; 158 } 159 recordUserAction(time, false /* isDeletion */); 160 } 161 } 162 163 public void recordWordEntered(final boolean isDictionaryWord, 164 final boolean containsCorrection) { 165 mWordCount++; 166 if (isDictionaryWord) { 167 mDictionaryWordCount++; 168 } 169 if (containsCorrection) { 170 mCorrectedWordsCount++; 171 } 172 } 173 174 public void recordSplitWords() { 175 mSplitWordsCount++; 176 } 177 178 public void recordGestureInput(final int numCharsEntered, final long time) { 179 mGesturesInputCount++; 180 mGesturesCharsCount += numCharsEntered; 181 recordUserAction(time, false /* isDeletion */); 182 } 183 184 public void setIsEmptyUponStarting(final boolean isEmpty) { 185 mIsEmptyUponStarting = isEmpty; 186 mIsEmptinessStateKnown = true; 187 } 188 189 public void recordGestureDelete(final int length, final long time) { 190 mGesturesDeletedCount++; 191 recordUserAction(time, true /* isDeletion */); 192 } 193 194 public void recordManualSuggestion(final long time) { 195 mManualSuggestionsCount++; 196 recordUserAction(time, false /* isDeletion */); 197 } 198 199 public void recordAutoCorrection(final long time) { 200 mAutoCorrectionsCount++; 201 recordUserAction(time, false /* isDeletion */); 202 } 203 204 public void recordRevertCommit(final long time) { 205 mRevertCommitsCount++; 206 recordUserAction(time, true /* isDeletion */); 207 } 208 209 private void recordUserAction(final long time, final boolean isDeletion) { 210 final long delta = time - mLastTapTime; 211 if (isDeletion) { 212 if (delta < MIN_DELETION_INTERMISSION) { 213 if (mIsLastKeyDeleteKey) { 214 mDuringRepeatedDeleteKeysCounter.add(delta); 215 } else { 216 mBeforeDeleteKeyCounter.add(delta); 217 } 218 } else { 219 ResearchLogger.onUserPause(delta); 220 } 221 } else { 222 if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) { 223 mAfterDeleteKeyCounter.add(delta); 224 } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) { 225 mKeyCounter.add(delta); 226 } else { 227 ResearchLogger.onUserPause(delta); 228 } 229 } 230 mIsLastKeyDeleteKey = isDeletion; 231 mLastTapTime = time; 232 } 233 } 234