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 import java.util.concurrent.TimeUnit; 25 26 public class Statistics { 27 private static final String TAG = Statistics.class.getSimpleName(); 28 private static final boolean DEBUG = false 29 && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; 30 31 // TODO: Cleanup comments to only including those giving meaningful information. 32 // Number of characters entered during a typing session 33 int mCharCount; 34 // Number of letter characters entered during a typing session 35 int mLetterCount; 36 // Number of number characters entered 37 int mNumberCount; 38 // Number of space characters entered 39 int mSpaceCount; 40 // Number of delete operations entered (taps on the backspace key) 41 int mDeleteKeyCount; 42 // Number of words entered during a session. 43 int mWordCount; 44 // Number of words found in the dictionary. 45 int mDictionaryWordCount; 46 // Number of words split and spaces automatically entered. 47 int mSplitWordsCount; 48 // Number of words entered during a session. 49 int mCorrectedWordsCount; 50 // Number of gestures that were input. 51 int mGesturesInputCount; 52 // Number of gestures that were deleted. 53 int mGesturesDeletedCount; 54 // Total number of characters in words entered by gesture. 55 int mGesturesCharsCount; 56 // Number of manual suggestions chosen. 57 int mManualSuggestionsCount; 58 // Number of times that autocorrection was invoked. 59 int mAutoCorrectionsCount; 60 // Number of times a commit was reverted in this session. 61 int mRevertCommitsCount; 62 // Whether the text field was empty upon editing 63 boolean mIsEmptyUponStarting; 64 boolean mIsEmptinessStateKnown; 65 66 // Counts of how often an n-gram is collected or not, and the reasons for the decision. 67 // Keep consistent with publishability result code list in MainLogBuffer 68 int mPublishableCount; 69 int mUnpublishableStoppingCount; 70 int mUnpublishableIncorrectWordCount; 71 int mUnpublishableSampledTooRecently; 72 int mUnpublishableDictionaryUnavailable; 73 int mUnpublishableMayContainDigit; 74 int mUnpublishableNotInDictionary; 75 76 // Timers to count average time to enter a key, first press a delete key, 77 // between delete keys, and then to return typing after a delete key. 78 final AverageTimeCounter mKeyCounter = new AverageTimeCounter(); 79 final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter(); 80 final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter(); 81 final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter(); 82 83 static class AverageTimeCounter { 84 int mCount; 85 int mTotalTime; 86 87 public void reset() { 88 mCount = 0; 89 mTotalTime = 0; 90 } 91 92 public void add(long deltaTime) { 93 mCount++; 94 mTotalTime += deltaTime; 95 } 96 97 public int getAverageTime() { 98 if (mCount == 0) { 99 return 0; 100 } 101 return mTotalTime / mCount; 102 } 103 } 104 105 // To account for the interruptions when the user's attention is directed elsewhere, times 106 // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic. 107 public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2); 108 public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10); 109 110 // The last time that a tap was performed 111 private long mLastTapTime; 112 // The type of the last keypress (delete key or not) 113 boolean mIsLastKeyDeleteKey; 114 115 private static final Statistics sInstance = new Statistics(); 116 117 public static Statistics getInstance() { 118 return sInstance; 119 } 120 121 private Statistics() { 122 reset(); 123 } 124 125 public void reset() { 126 mCharCount = 0; 127 mLetterCount = 0; 128 mNumberCount = 0; 129 mSpaceCount = 0; 130 mDeleteKeyCount = 0; 131 mWordCount = 0; 132 mDictionaryWordCount = 0; 133 mSplitWordsCount = 0; 134 mCorrectedWordsCount = 0; 135 mGesturesInputCount = 0; 136 mGesturesDeletedCount = 0; 137 mManualSuggestionsCount = 0; 138 mRevertCommitsCount = 0; 139 mAutoCorrectionsCount = 0; 140 mIsEmptyUponStarting = true; 141 mIsEmptinessStateKnown = false; 142 mKeyCounter.reset(); 143 mBeforeDeleteKeyCounter.reset(); 144 mDuringRepeatedDeleteKeysCounter.reset(); 145 mAfterDeleteKeyCounter.reset(); 146 mGesturesCharsCount = 0; 147 mGesturesDeletedCount = 0; 148 mPublishableCount = 0; 149 mUnpublishableStoppingCount = 0; 150 mUnpublishableIncorrectWordCount = 0; 151 mUnpublishableSampledTooRecently = 0; 152 mUnpublishableDictionaryUnavailable = 0; 153 mUnpublishableMayContainDigit = 0; 154 mUnpublishableNotInDictionary = 0; 155 156 mLastTapTime = 0; 157 mIsLastKeyDeleteKey = false; 158 } 159 160 public void recordChar(int codePoint, long time) { 161 if (DEBUG) { 162 Log.d(TAG, "recordChar() called"); 163 } 164 if (codePoint == Constants.CODE_DELETE) { 165 mDeleteKeyCount++; 166 recordUserAction(time, true /* isDeletion */); 167 } else { 168 mCharCount++; 169 if (Character.isDigit(codePoint)) { 170 mNumberCount++; 171 } 172 if (Character.isLetter(codePoint)) { 173 mLetterCount++; 174 } 175 if (Character.isSpaceChar(codePoint)) { 176 mSpaceCount++; 177 } 178 recordUserAction(time, false /* isDeletion */); 179 } 180 } 181 182 public void recordWordEntered(final boolean isDictionaryWord, 183 final boolean containsCorrection) { 184 mWordCount++; 185 if (isDictionaryWord) { 186 mDictionaryWordCount++; 187 } 188 if (containsCorrection) { 189 mCorrectedWordsCount++; 190 } 191 } 192 193 public void recordSplitWords() { 194 mSplitWordsCount++; 195 } 196 197 public void recordGestureInput(final int numCharsEntered, final long time) { 198 mGesturesInputCount++; 199 mGesturesCharsCount += numCharsEntered; 200 recordUserAction(time, false /* isDeletion */); 201 } 202 203 public void setIsEmptyUponStarting(final boolean isEmpty) { 204 mIsEmptyUponStarting = isEmpty; 205 mIsEmptinessStateKnown = true; 206 } 207 208 public void recordGestureDelete(final int length, final long time) { 209 mGesturesDeletedCount++; 210 recordUserAction(time, true /* isDeletion */); 211 } 212 213 public void recordManualSuggestion(final long time) { 214 mManualSuggestionsCount++; 215 recordUserAction(time, false /* isDeletion */); 216 } 217 218 public void recordAutoCorrection(final long time) { 219 mAutoCorrectionsCount++; 220 recordUserAction(time, false /* isDeletion */); 221 } 222 223 public void recordRevertCommit(final long time) { 224 mRevertCommitsCount++; 225 recordUserAction(time, true /* isDeletion */); 226 } 227 228 private void recordUserAction(final long time, final boolean isDeletion) { 229 final long delta = time - mLastTapTime; 230 if (isDeletion) { 231 if (delta < MIN_DELETION_INTERMISSION) { 232 if (mIsLastKeyDeleteKey) { 233 mDuringRepeatedDeleteKeysCounter.add(delta); 234 } else { 235 mBeforeDeleteKeyCounter.add(delta); 236 } 237 } else { 238 ResearchLogger.onUserPause(delta); 239 } 240 } else { 241 if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) { 242 mAfterDeleteKeyCounter.add(delta); 243 } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) { 244 mKeyCounter.add(delta); 245 } else { 246 ResearchLogger.onUserPause(delta); 247 } 248 } 249 mIsLastKeyDeleteKey = isDeletion; 250 mLastTapTime = time; 251 } 252 253 public void recordPublishabilityResultCode(final int publishabilityResultCode) { 254 // Keep consistent with publishability result code list in MainLogBuffer 255 switch (publishabilityResultCode) { 256 case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE: 257 mPublishableCount++; 258 break; 259 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING: 260 mUnpublishableStoppingCount++; 261 break; 262 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT: 263 mUnpublishableIncorrectWordCount++; 264 break; 265 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY: 266 mUnpublishableSampledTooRecently++; 267 break; 268 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE: 269 mUnpublishableDictionaryUnavailable++; 270 break; 271 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT: 272 mUnpublishableMayContainDigit++; 273 break; 274 case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY: 275 mUnpublishableNotInDictionary++; 276 break; 277 } 278 } 279 } 280