1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.content.Context; 20import android.inputmethodservice.Keyboard.Key; 21import android.text.format.DateFormat; 22import android.util.Log; 23 24import java.io.FileOutputStream; 25import java.io.IOException; 26import java.util.Calendar; 27 28public class TextEntryState { 29 30 private static final boolean DBG = false; 31 32 private static final String TAG = "TextEntryState"; 33 34 private static boolean LOGGING = false; 35 36 private static int sBackspaceCount = 0; 37 38 private static int sAutoSuggestCount = 0; 39 40 private static int sAutoSuggestUndoneCount = 0; 41 42 private static int sManualSuggestCount = 0; 43 44 private static int sWordNotInDictionaryCount = 0; 45 46 private static int sSessionCount = 0; 47 48 private static int sTypedChars; 49 50 private static int sActualChars; 51 52 public enum State { 53 UNKNOWN, 54 START, 55 IN_WORD, 56 ACCEPTED_DEFAULT, 57 PICKED_SUGGESTION, 58 PUNCTUATION_AFTER_WORD, 59 PUNCTUATION_AFTER_ACCEPTED, 60 SPACE_AFTER_ACCEPTED, 61 SPACE_AFTER_PICKED, 62 UNDO_COMMIT, 63 CORRECTING, 64 PICKED_CORRECTION; 65 } 66 67 private static State sState = State.UNKNOWN; 68 69 private static FileOutputStream sKeyLocationFile; 70 private static FileOutputStream sUserActionFile; 71 72 public static void newSession(Context context) { 73 sSessionCount++; 74 sAutoSuggestCount = 0; 75 sBackspaceCount = 0; 76 sAutoSuggestUndoneCount = 0; 77 sManualSuggestCount = 0; 78 sWordNotInDictionaryCount = 0; 79 sTypedChars = 0; 80 sActualChars = 0; 81 sState = State.START; 82 83 if (LOGGING) { 84 try { 85 sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND); 86 sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND); 87 } catch (IOException ioe) { 88 Log.e("TextEntryState", "Couldn't open file for output: " + ioe); 89 } 90 } 91 } 92 93 public static void endSession() { 94 if (sKeyLocationFile == null) { 95 return; 96 } 97 try { 98 sKeyLocationFile.close(); 99 // Write to log file 100 // Write timestamp, settings, 101 String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime()) 102 .toString() 103 + " BS: " + sBackspaceCount 104 + " auto: " + sAutoSuggestCount 105 + " manual: " + sManualSuggestCount 106 + " typed: " + sWordNotInDictionaryCount 107 + " undone: " + sAutoSuggestUndoneCount 108 + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars) 109 + "\n"; 110 sUserActionFile.write(out.getBytes()); 111 sUserActionFile.close(); 112 sKeyLocationFile = null; 113 sUserActionFile = null; 114 } catch (IOException ioe) { 115 116 } 117 } 118 119 public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) { 120 if (typedWord == null) return; 121 if (!typedWord.equals(actualWord)) { 122 sAutoSuggestCount++; 123 } 124 sTypedChars += typedWord.length(); 125 sActualChars += actualWord.length(); 126 sState = State.ACCEPTED_DEFAULT; 127 LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString()); 128 displayState(); 129 } 130 131 // State.ACCEPTED_DEFAULT will be changed to other sub-states 132 // (see "case ACCEPTED_DEFAULT" in typedCharacter() below), 133 // and should be restored back to State.ACCEPTED_DEFAULT after processing for each sub-state. 134 public static void backToAcceptedDefault(CharSequence typedWord) { 135 if (typedWord == null) return; 136 switch (sState) { 137 case SPACE_AFTER_ACCEPTED: 138 case PUNCTUATION_AFTER_ACCEPTED: 139 case IN_WORD: 140 sState = State.ACCEPTED_DEFAULT; 141 break; 142 } 143 displayState(); 144 } 145 146 public static void acceptedTyped(CharSequence typedWord) { 147 sWordNotInDictionaryCount++; 148 sState = State.PICKED_SUGGESTION; 149 displayState(); 150 } 151 152 public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) { 153 sManualSuggestCount++; 154 State oldState = sState; 155 if (typedWord.equals(actualWord)) { 156 acceptedTyped(typedWord); 157 } 158 if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) { 159 sState = State.PICKED_CORRECTION; 160 } else { 161 sState = State.PICKED_SUGGESTION; 162 } 163 displayState(); 164 } 165 166 public static void selectedForCorrection() { 167 sState = State.CORRECTING; 168 displayState(); 169 } 170 171 public static void typedCharacter(char c, boolean isSeparator) { 172 boolean isSpace = c == ' '; 173 switch (sState) { 174 case IN_WORD: 175 if (isSpace || isSeparator) { 176 sState = State.START; 177 } else { 178 // State hasn't changed. 179 } 180 break; 181 case ACCEPTED_DEFAULT: 182 case SPACE_AFTER_PICKED: 183 if (isSpace) { 184 sState = State.SPACE_AFTER_ACCEPTED; 185 } else if (isSeparator) { 186 sState = State.PUNCTUATION_AFTER_ACCEPTED; 187 } else { 188 sState = State.IN_WORD; 189 } 190 break; 191 case PICKED_SUGGESTION: 192 case PICKED_CORRECTION: 193 if (isSpace) { 194 sState = State.SPACE_AFTER_PICKED; 195 } else if (isSeparator) { 196 // Swap 197 sState = State.PUNCTUATION_AFTER_ACCEPTED; 198 } else { 199 sState = State.IN_WORD; 200 } 201 break; 202 case START: 203 case UNKNOWN: 204 case SPACE_AFTER_ACCEPTED: 205 case PUNCTUATION_AFTER_ACCEPTED: 206 case PUNCTUATION_AFTER_WORD: 207 if (!isSpace && !isSeparator) { 208 sState = State.IN_WORD; 209 } else { 210 sState = State.START; 211 } 212 break; 213 case UNDO_COMMIT: 214 if (isSpace || isSeparator) { 215 sState = State.ACCEPTED_DEFAULT; 216 } else { 217 sState = State.IN_WORD; 218 } 219 break; 220 case CORRECTING: 221 sState = State.START; 222 break; 223 } 224 displayState(); 225 } 226 227 public static void backspace() { 228 if (sState == State.ACCEPTED_DEFAULT) { 229 sState = State.UNDO_COMMIT; 230 sAutoSuggestUndoneCount++; 231 LatinImeLogger.logOnAutoSuggestionCanceled(); 232 } else if (sState == State.UNDO_COMMIT) { 233 sState = State.IN_WORD; 234 } 235 sBackspaceCount++; 236 displayState(); 237 } 238 239 public static void reset() { 240 sState = State.START; 241 displayState(); 242 } 243 244 public static State getState() { 245 if (DBG) { 246 Log.d(TAG, "Returning state = " + sState); 247 } 248 return sState; 249 } 250 251 public static boolean isCorrecting() { 252 return sState == State.CORRECTING || sState == State.PICKED_CORRECTION; 253 } 254 255 public static void keyPressedAt(Key key, int x, int y) { 256 if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) { 257 String out = 258 "KEY: " + (char) key.codes[0] 259 + " X: " + x 260 + " Y: " + y 261 + " MX: " + (key.x + key.width / 2) 262 + " MY: " + (key.y + key.height / 2) 263 + "\n"; 264 try { 265 sKeyLocationFile.write(out.getBytes()); 266 } catch (IOException ioe) { 267 // TODO: May run out of space 268 } 269 } 270 } 271 272 private static void displayState() { 273 if (DBG) { 274 Log.d(TAG, "State = " + sState); 275 } 276 } 277} 278 279