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