1 /*
2  * Copyright (C) 2008-2012  OMRON SOFTWARE Co., Ltd.
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 jp.co.omronsoft.openwnn;
18 
19 import jp.co.omronsoft.openwnn.EN.*;
20 import android.content.SharedPreferences;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.os.Message;
24 import android.os.Handler;
25 import android.preference.PreferenceManager;
26 import android.text.SpannableStringBuilder;
27 import android.text.Spanned;
28 import android.text.method.MetaKeyKeyListener;
29 import android.text.style.BackgroundColorSpan;
30 import android.text.style.CharacterStyle;
31 import android.text.style.ForegroundColorSpan;
32 import android.text.style.UnderlineSpan;
33 import android.util.Log;
34 import android.view.KeyCharacterMap;
35 import android.view.KeyEvent;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.inputmethod.EditorInfo;
39 
40 /**
41  * The OpenWnn English IME class.
42  *
43  * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
44  */
45 public class OpenWnnEN extends OpenWnn {
46     /** A space character */
47     private static final char[] SPACE = {' '};
48 
49     /** Character style of underline */
50     private static final CharacterStyle SPAN_UNDERLINE   = new UnderlineSpan();
51     /** Highlight color style for the selected string  */
52     private static final CharacterStyle SPAN_EXACT_BGCOLOR_HL     = new BackgroundColorSpan(0xFF66CDAA);
53     /** Highlight color style for the composing text */
54     private static final CharacterStyle SPAN_REMAIN_BGCOLOR_HL    = new BackgroundColorSpan(0xFFF0FFFF);
55     /** Highlight text color */
56     private static final CharacterStyle SPAN_TEXTCOLOR  = new ForegroundColorSpan(0xFF000000);
57 
58     /** A private area code(ALT+SHIFT+X) to be ignore (G1 specific). */
59     private static final int PRIVATE_AREA_CODE = 61184;
60     /** Never move cursor in to the composing text (adapting to IMF's specification change) */
61     private static final boolean FIX_CURSOR_TEXT_END = true;
62 
63     /** Spannable string for the composing text */
64     protected SpannableStringBuilder mDisplayText;
65 
66     /** Characters treated as a separator */
67     private String mWordSeparators;
68     /** Previous event's code */
69     private int mPreviousEventCode;
70 
71     /** Array of words from the user dictionary */
72     private WnnWord[] mUserDictionaryWords = null;
73 
74     /** The converter for English prediction/spell correction */
75     private OpenWnnEngineEN mConverterEN;
76     /** The symbol list generator */
77     private SymbolList mSymbolList;
78     /** Whether it is displaying symbol list */
79     private boolean mSymbolMode;
80     /** Whether prediction is enabled */
81     private boolean mOptPrediction;
82     /** Whether spell correction is enabled */
83     private boolean mOptSpellCorrection;
84     /** Whether learning is enabled */
85     private boolean mOptLearning;
86 
87     /** SHIFT key state */
88     private int mHardShift;
89     /** SHIFT key state (pressing) */
90     private boolean mShiftPressing;
91     /** ALT key state */
92     private int mHardAlt;
93     /** ALT key state (pressing) */
94     private boolean mAltPressing;
95 
96     /** Instance of this service */
97     private static OpenWnnEN mSelf = null;
98 
99     /** Shift lock toggle definition */
100     private static final int[] mShiftKeyToggle = {0, MetaKeyKeyListener.META_SHIFT_ON, MetaKeyKeyListener.META_CAP_LOCKED};
101     /** ALT lock toggle definition */
102     private static final int[] mAltKeyToggle = {0, MetaKeyKeyListener.META_ALT_ON, MetaKeyKeyListener.META_ALT_LOCKED};
103     /** Auto caps mode */
104     private boolean mAutoCaps = false;
105 
106     /** Whether dismissing the keyboard when the enter key is pressed */
107     private boolean mEnableAutoHideKeyboard = true;
108 
109     /** Tutorial */
110     private TutorialEN mTutorial;
111 
112     /** Whether tutorial mode or not */
113     private boolean mEnableTutorial;
114 
115     /** Message for {@code mHandler} (execute prediction) */
116     private static final int MSG_PREDICTION = 0;
117 
118     /** Message for {@code mHandler} (execute tutorial) */
119     private static final int MSG_START_TUTORIAL = 1;
120 
121     /** Message for {@code mHandler} (close) */
122     private static final int MSG_CLOSE = 2;
123 
124     /** Delay time(msec.) to start prediction after key input when the candidates view is not shown. */
125     private static final int PREDICTION_DELAY_MS_1ST = 200;
126 
127     /** Delay time(msec.) to start prediction after key input when the candidates view is shown. */
128     private static final int PREDICTION_DELAY_MS_SHOWING_CANDIDATE = 200;
129 
130     /** {@code Handler} for drawing candidates/displaying tutorial */
131     Handler mHandler = new Handler() {
132             @Override public void handleMessage(Message msg) {
133                 switch (msg.what) {
134                 case MSG_PREDICTION:
135                     updatePrediction();
136                     break;
137                 case MSG_START_TUTORIAL:
138                     if (mTutorial == null) {
139                         if (isInputViewShown()) {
140                             DefaultSoftKeyboardEN inputManager = ((DefaultSoftKeyboardEN) mInputViewManager);
141                             View v = inputManager.getKeyboardView();
142                             mTutorial = new TutorialEN(OpenWnnEN.this, v, inputManager);
143 
144                             mTutorial.start();
145                         } else {
146                             /* Try again soon if the view is not yet showing */
147                             sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
148                         }
149                     }
150                     break;
151                 case MSG_CLOSE:
152                     if (mConverterEN != null) mConverterEN.close();
153                     if (mSymbolList != null) mSymbolList.close();
154                     break;
155                 }
156             }
157         };
158 
159     /**
160      * Constructor
161      */
OpenWnnEN()162     public OpenWnnEN() {
163         super();
164         mSelf = this;
165 
166         /* used by OpenWnn */
167         mComposingText = new ComposingText();
168         mCandidatesViewManager = new TextCandidatesViewManager(-1);
169         mInputViewManager = new DefaultSoftKeyboardEN();
170 
171         if (OpenWnn.getCurrentIme() != null) {
172             if (mConverterEN == null) {
173                 mConverterEN = new OpenWnnEngineEN("/data/data/jp.co.omronsoft.openwnn/writableEN.dic");
174             }
175         }
176 
177         mConverter = mConverterEN;
178         mSymbolList = null;
179 
180         /* etc */
181         mDisplayText = new SpannableStringBuilder();
182         mAutoHideMode = false;
183         mSymbolMode = false;
184         mOptPrediction = true;
185         mOptSpellCorrection = true;
186         mOptLearning = true;
187     }
188 
189     /**
190      * Constructor
191      *
192      * @param context       The context
193      */
OpenWnnEN(Context context)194     public OpenWnnEN(Context context) {
195         this();
196         attachBaseContext(context);
197     }
198     /**
199      * Get the instance of this service.
200      * <br>
201      * Before using this method, the constructor of this service must be invoked.
202      *
203      * @return      The instance of this object
204      */
getInstance()205     public static OpenWnnEN getInstance() {
206         return mSelf;
207     }
208 
209     /**
210      * Insert a character into the composing text.
211      *
212      * @param chars     A array of character
213      */
insertCharToComposingText(char[] chars)214     private void insertCharToComposingText(char[] chars) {
215         StrSegment seg = new StrSegment(chars);
216 
217         if (chars[0] == SPACE[0] || chars[0] == '\u0009') {
218             /* if the character is a space, commit the composing text */
219             commitText(1);
220             commitText(seg.string);
221             mComposingText.clear();
222         } else if (mWordSeparators.contains(seg.string)) {
223             /* if the character is a separator, remove an auto-inserted space and commit the composing text. */
224             if (mPreviousEventCode == OpenWnnEvent.SELECT_CANDIDATE) {
225                 mInputConnection.deleteSurroundingText(1, 0);
226             }
227             commitText(1);
228             commitText(seg.string);
229             mComposingText.clear();
230         } else {
231             mComposingText.insertStrSegment(0, 1, seg);
232             updateComposingText(1);
233         }
234     }
235 
236     /**
237      * Insert a character into the composing text.
238      *
239      * @param charCode      A character code
240      * @return              {@code true} if success; {@code false} if an error occurs.
241      */
insertCharToComposingText(int charCode)242     private boolean insertCharToComposingText(int charCode) {
243         if (charCode == 0) {
244             return false;
245         }
246         insertCharToComposingText(Character.toChars(charCode));
247         return true;
248     }
249 
250     /**
251      * Get the shift key state from the editor.
252      *
253      * @param editor    Editor
254      *
255      * @return          State ID of the shift key (0:off, 1:on)
256      */
getShiftKeyState(EditorInfo editor)257     protected int getShiftKeyState(EditorInfo editor) {
258         return (getCurrentInputConnection().getCursorCapsMode(editor.inputType) == 0) ? 0 : 1;
259     }
260 
261     /**
262      * Set the mode of the symbol list.
263      *
264      * @param mode      {@code SymbolList.SYMBOL_ENGLISH} or {@code null}.
265      */
setSymbolMode(String mode)266     private void setSymbolMode(String mode) {
267         if (mode != null) {
268             mHandler.removeMessages(MSG_PREDICTION);
269             mSymbolMode = true;
270             mSymbolList.setDictionary(mode);
271             mConverter = mSymbolList;
272         } else {
273             if (!mSymbolMode) {
274                 return;
275             }
276             mHandler.removeMessages(MSG_PREDICTION);
277             mSymbolMode = false;
278             mConverter = mConverterEN;
279         }
280     }
281 
282     /***********************************************************************
283      * InputMethodServer
284      ***********************************************************************/
285     /** @see jp.co.omronsoft.openwnn.OpenWnn#onCreate */
onCreate()286     @Override public void onCreate() {
287         super.onCreate();
288         mWordSeparators = getResources().getString(R.string.en_word_separators);
289 
290         if (mConverterEN == null) {
291             mConverterEN = new OpenWnnEngineEN("/data/data/jp.co.omronsoft.openwnn/writableEN.dic");
292         }
293 
294         if (mSymbolList == null) {
295             mSymbolList = new SymbolList(this, SymbolList.LANG_EN);
296         }
297     }
298 
299     /** @see jp.co.omronsoft.openwnn.OpenWnn#onCreateInputView */
onCreateInputView()300     @Override public View onCreateInputView() {
301         int hiddenState = getResources().getConfiguration().hardKeyboardHidden;
302         boolean hidden = (hiddenState == Configuration.HARDKEYBOARDHIDDEN_YES);
303         ((DefaultSoftKeyboardEN) mInputViewManager).setHardKeyboardHidden(hidden);
304         mEnableTutorial = hidden;
305 
306         return super.onCreateInputView();
307     }
308 
309     /** @see jp.co.omronsoft.openwnn.OpenWnn#onStartInputView */
onStartInputView(EditorInfo attribute, boolean restarting)310     @Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
311         super.onStartInputView(attribute, restarting);
312 
313         /* initialize views */
314         mCandidatesViewManager.clearCandidates();
315         mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
316 
317         mHardShift = 0;
318         mHardAlt   = 0;
319         updateMetaKeyStateDisplay();
320 
321         /* load preferences */
322         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
323 
324         /* auto caps mode */
325         mAutoCaps = pref.getBoolean("auto_caps", true);
326 
327         /* set TextCandidatesViewManager's option */
328         ((TextCandidatesViewManager)mCandidatesViewManager).setAutoHide(true);
329 
330         /* display status icon */
331         showStatusIcon(R.drawable.immodeic_half_alphabet);
332 
333         if (mComposingText != null) {
334             mComposingText.clear();
335         }
336         /* initialize the engine's state */
337         fitInputType(pref, attribute);
338 
339         ((DefaultSoftKeyboard) mInputViewManager).resetCurrentKeyboard();
340 
341         if (OpenWnn.isXLarge()) {
342             mTextCandidatesViewManager.setPreferences(pref);
343         }
344     }
345 
346     /** @see jp.co.omronsoft.openwnn.OpenWnn#hideWindow */
hideWindow()347     @Override public void hideWindow() {
348         ((BaseInputView)((DefaultSoftKeyboard) mInputViewManager).getCurrentView()).closeDialog();
349         mComposingText.clear();
350         mInputViewManager.onUpdateState(this);
351         mHandler.removeMessages(MSG_START_TUTORIAL);
352         mInputViewManager.closing();
353         if (mTutorial != null) {
354             mTutorial.close();
355             mTutorial = null;
356         }
357 
358         super.hideWindow();
359     }
360 
361     /** @see jp.co.omronsoft.openwnn.OpenWnn#onUpdateSelection */
onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)362     @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd,
363             int newSelStart, int newSelEnd, int candidatesStart,
364             int candidatesEnd) {
365 
366         boolean isNotComposing = ((candidatesStart < 0) && (candidatesEnd < 0));
367         if (isNotComposing) {
368             mComposingText.clear();
369             updateComposingText(1);
370         } else {
371             if (mComposingText.size(1) != 0) {
372                 updateComposingText(1);
373             }
374         }
375     }
376 
377     /** @see jp.co.omronsoft.openwnn.OpenWnn#onConfigurationChanged */
onConfigurationChanged(Configuration newConfig)378     @Override public void onConfigurationChanged(Configuration newConfig) {
379         try {
380             super.onConfigurationChanged(newConfig);
381             if (mInputConnection != null) {
382                 updateComposingText(1);
383             }
384             /* Hardware keyboard */
385             int hiddenState = newConfig.hardKeyboardHidden;
386             boolean hidden = (hiddenState == Configuration.HARDKEYBOARDHIDDEN_YES);
387             mEnableTutorial = hidden;
388         } catch (Exception ex) {
389         }
390     }
391 
392     /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvaluateFullscreenMode */
onEvaluateFullscreenMode()393     @Override public boolean onEvaluateFullscreenMode() {
394         return false;
395     }
396 
397     /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvaluateInputViewShown */
onEvaluateInputViewShown()398     @Override public boolean onEvaluateInputViewShown() {
399         return true;
400     }
401 
402     /***********************************************************************
403      * OpenWnn
404      ***********************************************************************/
405     /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvent */
onEvent(OpenWnnEvent ev)406     @Override synchronized public boolean onEvent(OpenWnnEvent ev) {
407         /* handling events which are valid when InputConnection is not active. */
408         switch (ev.code) {
409 
410         case OpenWnnEvent.KEYUP:
411             onKeyUpEvent(ev.keyEvent);
412             return true;
413 
414         case OpenWnnEvent.INITIALIZE_LEARNING_DICTIONARY:
415             return mConverterEN.initializeDictionary( WnnEngine.DICTIONARY_TYPE_LEARN );
416 
417         case OpenWnnEvent.INITIALIZE_USER_DICTIONARY:
418             return mConverterEN.initializeDictionary( WnnEngine.DICTIONARY_TYPE_USER );
419 
420         case OpenWnnEvent.LIST_WORDS_IN_USER_DICTIONARY:
421             mUserDictionaryWords = mConverterEN.getUserDictionaryWords( );
422             return true;
423 
424         case OpenWnnEvent.GET_WORD:
425             if( mUserDictionaryWords != null ) {
426                 ev.word = mUserDictionaryWords[ 0 ];
427                 for( int i = 0 ; i < mUserDictionaryWords.length-1 ; i++ ) {
428                     mUserDictionaryWords[ i ] = mUserDictionaryWords[ i + 1 ];
429                 }
430                 mUserDictionaryWords[ mUserDictionaryWords.length-1 ] = null;
431                 if( mUserDictionaryWords[ 0 ] == null ) {
432                     mUserDictionaryWords = null;
433                 }
434                 return true;
435             }
436             break;
437 
438         case OpenWnnEvent.ADD_WORD:
439             mConverterEN.addWord(ev.word);
440             return true;
441 
442         case OpenWnnEvent.DELETE_WORD:
443             mConverterEN.deleteWord(ev.word);
444             return true;
445 
446         case OpenWnnEvent.CHANGE_MODE:
447             return false;
448 
449         case OpenWnnEvent.UPDATE_CANDIDATE:
450             updateComposingText(ComposingText.LAYER1);
451             return true;
452 
453         case OpenWnnEvent.CHANGE_INPUT_VIEW:
454             setInputView(onCreateInputView());
455             return true;
456 
457         case OpenWnnEvent.CANDIDATE_VIEW_TOUCH:
458             boolean ret;
459                 ret = ((TextCandidatesViewManager)mCandidatesViewManager).onTouchSync();
460             return ret;
461 
462         default:
463             break;
464         }
465 
466         dismissPopupKeyboard();
467         KeyEvent keyEvent = ev.keyEvent;
468         int keyCode = 0;
469         if (keyEvent != null) {
470             keyCode = keyEvent.getKeyCode();
471         }
472         if (mDirectInputMode) {
473             if (ev.code == OpenWnnEvent.INPUT_SOFT_KEY && mInputConnection != null) {
474                 mInputConnection.sendKeyEvent(keyEvent);
475                 mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
476                                                            keyEvent.getKeyCode()));
477             }
478             return false;
479         }
480 
481         if (ev.code == OpenWnnEvent.LIST_CANDIDATES_FULL) {
482             mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_FULL);
483             return true;
484         } else if (ev.code == OpenWnnEvent.LIST_CANDIDATES_NORMAL) {
485             mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
486             return true;
487         }
488 
489         boolean ret = false;
490         switch (ev.code) {
491         case OpenWnnEvent.INPUT_CHAR:
492              ((TextCandidatesViewManager)mCandidatesViewManager).setAutoHide(false);
493             EditorInfo edit = getCurrentInputEditorInfo();
494             if( edit.inputType == EditorInfo.TYPE_CLASS_PHONE){
495                 commitText(new String(ev.chars));
496             }else{
497                 setSymbolMode(null);
498                 insertCharToComposingText(ev.chars);
499                 ret = true;
500                 mPreviousEventCode = ev.code;
501             }
502             break;
503 
504         case OpenWnnEvent.INPUT_KEY:
505             keyCode = ev.keyEvent.getKeyCode();
506             /* update shift/alt state */
507             switch (keyCode) {
508             case KeyEvent.KEYCODE_ALT_LEFT:
509             case KeyEvent.KEYCODE_ALT_RIGHT:
510                 if (ev.keyEvent.getRepeatCount() == 0) {
511                     if (++mHardAlt > 2) { mHardAlt = 0; }
512                 }
513                 mAltPressing   = true;
514                 updateMetaKeyStateDisplay();
515                 return true;
516 
517             case KeyEvent.KEYCODE_SHIFT_LEFT:
518             case KeyEvent.KEYCODE_SHIFT_RIGHT:
519                 if (ev.keyEvent.getRepeatCount() == 0) {
520                     if (++mHardShift > 2) { mHardShift = 0; }
521                 }
522                 mShiftPressing = true;
523                 updateMetaKeyStateDisplay();
524                 return true;
525             }
526             setSymbolMode(null);
527             updateComposingText(1);
528             /* handle other key event */
529             ret = processKeyEvent(ev.keyEvent);
530             mPreviousEventCode = ev.code;
531             break;
532 
533         case OpenWnnEvent.INPUT_SOFT_KEY:
534             setSymbolMode(null);
535             updateComposingText(1);
536             ret = processKeyEvent(ev.keyEvent);
537             if (!ret) {
538             	int code = keyEvent.getKeyCode();
539             	if (code == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER) {
540                     sendKeyChar('\n');
541             	} else {
542                     mInputConnection.sendKeyEvent(keyEvent);
543                     mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, code));
544             	}
545                 ret = true;
546             }
547             mPreviousEventCode = ev.code;
548             break;
549 
550         case OpenWnnEvent.SELECT_CANDIDATE:
551             if (mSymbolMode) {
552                 commitText(ev.word, false);
553             } else {
554                 if (mWordSeparators.contains(ev.word.candidate) &&
555                     mPreviousEventCode == OpenWnnEvent.SELECT_CANDIDATE) {
556                     mInputConnection.deleteSurroundingText(1, 0);
557                 }
558                 commitText(ev.word, true);
559             }
560             mComposingText.clear();
561             mPreviousEventCode = ev.code;
562             updateComposingText(1);
563             break;
564 
565         case OpenWnnEvent.LIST_SYMBOLS:
566             commitText(1);
567             mComposingText.clear();
568             setSymbolMode(SymbolList.SYMBOL_ENGLISH);
569             updateComposingText(1);
570             break;
571 
572         default:
573             break;
574         }
575 
576         if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
577         	mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
578         }
579 
580         return ret;
581     }
582 
583     /***********************************************************************
584      * OpenWnnEN
585      ***********************************************************************/
586     /**
587      * Handling KeyEvent
588      * <br>
589      * This method is called from {@link #onEvent()}.
590      *
591      * @param ev   A key event
592      * @return      {@code true} if the event is processed in this method; {@code false} if the event is not processed in this method
593      */
processKeyEvent(KeyEvent ev)594     private boolean processKeyEvent(KeyEvent ev) {
595 
596         int key = ev.getKeyCode();
597         EditorInfo edit = getCurrentInputEditorInfo();
598         /* keys which produce a glyph */
599         if (ev.isPrintingKey()) {
600             /* do nothing if the character is not able to display or the character is dead key */
601             if ((mHardShift > 0 && mHardAlt > 0) || (ev.isAltPressed() && ev.isShiftPressed())) {
602                 int charCode = ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON | MetaKeyKeyListener.META_ALT_ON);
603                 if (charCode == 0 || (charCode & KeyCharacterMap.COMBINING_ACCENT) != 0 || charCode == PRIVATE_AREA_CODE) {
604                     if(mHardShift == 1){
605                         mShiftPressing = false;
606                     }
607                     if(mHardAlt == 1){
608                         mAltPressing   = false;
609                     }
610                     if(!ev.isAltPressed()){
611                         if (mHardAlt == 1) {
612                             mHardAlt = 0;
613                         }
614                     }
615                     if(!ev.isShiftPressed()){
616                         if (mHardShift == 1) {
617                             mHardShift = 0;
618                         }
619                     }
620                     if(!ev.isShiftPressed() && !ev.isAltPressed()){
621                         updateMetaKeyStateDisplay();
622                     }
623                     return true;
624                 }
625             }
626 
627             ((TextCandidatesViewManager)mCandidatesViewManager).setAutoHide(false);
628 
629             /* get the key character */
630             if (mHardShift== 0  && mHardAlt == 0) {
631                 /* no meta key is locked */
632                 int shift = (mAutoCaps) ? getShiftKeyState(edit) : 0;
633                 if (shift != mHardShift && (key >= KeyEvent.KEYCODE_A && key <= KeyEvent.KEYCODE_Z)) {
634                     /* handling auto caps for a alphabet character */
635                     insertCharToComposingText(ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON));
636                 } else {
637                     insertCharToComposingText(ev.getUnicodeChar());
638                 }
639             } else {
640                 insertCharToComposingText(ev.getUnicodeChar(mShiftKeyToggle[mHardShift]
641                                                             | mAltKeyToggle[mHardAlt]));
642                 if(mHardShift == 1){
643                     mShiftPressing = false;
644                 }
645                 if(mHardAlt == 1){
646                     mAltPressing   = false;
647                 }
648                 /* back to 0 (off) if 1 (on/not locked) */
649                 if(!ev.isAltPressed()){
650                     if (mHardAlt == 1) {
651                         mHardAlt = 0;
652                     }
653                 }
654                 if(!ev.isShiftPressed()){
655                     if (mHardShift == 1) {
656                         mHardShift = 0;
657                     }
658                 }
659                 if(!ev.isShiftPressed() && !ev.isAltPressed()){
660                     updateMetaKeyStateDisplay();
661                 }
662             }
663 
664             if (edit.inputType == EditorInfo.TYPE_CLASS_PHONE) {
665                 commitText(1);
666                 mComposingText.clear();
667                 return true;
668             }
669             return true;
670 
671         } else if (key == KeyEvent.KEYCODE_SPACE) {
672             if (ev.isAltPressed()) {
673                 /* display the symbol list (G1 specific. same as KEYCODE_SYM) */
674                 commitText(1);
675                 mComposingText.clear();
676                 setSymbolMode(SymbolList.SYMBOL_ENGLISH);
677                 updateComposingText(1);
678                 mHardAlt = 0;
679                 updateMetaKeyStateDisplay();
680             } else {
681                 insertCharToComposingText(SPACE);
682             }
683             return true;
684         } else if (key == KeyEvent.KEYCODE_SYM) {
685             /* display the symbol list */
686             commitText(1);
687             mComposingText.clear();
688             setSymbolMode(SymbolList.SYMBOL_ENGLISH);
689             updateComposingText(1);
690             mHardAlt = 0;
691             updateMetaKeyStateDisplay();
692         }
693 
694 
695         /* Functional key */
696         if (mComposingText.size(1) > 0) {
697             switch (key) {
698             case KeyEvent.KEYCODE_DEL:
699                 mComposingText.delete(1, false);
700                 updateComposingText(1);
701                 return true;
702 
703             case KeyEvent.KEYCODE_BACK:
704                 if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
705                     mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
706                 } else {
707                     mComposingText.clear();
708                     updateComposingText(1);
709                 }
710                 return true;
711 
712             case KeyEvent.KEYCODE_DPAD_LEFT:
713                 mComposingText.moveCursor(1, -1);
714                 updateComposingText(1);
715                 return true;
716 
717             case KeyEvent.KEYCODE_DPAD_RIGHT:
718                 mComposingText.moveCursor(1, 1);
719                 updateComposingText(1);
720                 return true;
721 
722             case KeyEvent.KEYCODE_ENTER:
723             case KeyEvent.KEYCODE_NUMPAD_ENTER:
724             case KeyEvent.KEYCODE_DPAD_CENTER:
725                 commitText(1);
726                 mComposingText.clear();
727                 if (mEnableAutoHideKeyboard) {
728                     mInputViewManager.closing();
729                     requestHideSelf(0);
730                 }
731                 return true;
732 
733             default:
734                 return !isThroughKeyCode(key);
735             }
736         } else {
737             /* if there is no composing string. */
738             if (mCandidatesViewManager.getCurrentView().isShown()) {
739             	if (key == KeyEvent.KEYCODE_BACK) {
740             		if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
741             			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
742             		} else {
743             			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
744             		}
745             		return true;
746             	}
747             } else {
748                 switch (key) {
749                 case KeyEvent.KEYCODE_DPAD_CENTER:
750                 case KeyEvent.KEYCODE_ENTER:
751                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
752                     if (mEnableAutoHideKeyboard) {
753                         mInputViewManager.closing();
754                         requestHideSelf(0);
755                         return true;
756                     }
757                     break;
758                 case KeyEvent.KEYCODE_BACK:
759                     /*
760                      * If 'BACK' key is pressed when the SW-keyboard is shown
761                      * and the candidates view is not shown, dismiss the SW-keyboard.
762                      */
763                     if (isInputViewShown()) {
764                         mInputViewManager.closing();
765                         requestHideSelf(0);
766                         return true;
767                     }
768                     break;
769                 default:
770                     break;
771                 }
772             }
773         }
774 
775         return false;
776     }
777 
778     /**
779      * Thread for updating the candidates view
780      */
updatePrediction()781     private void updatePrediction() {
782         int candidates = 0;
783         if (mConverter != null) {
784             /* normal prediction */
785             candidates = mConverter.predict(mComposingText, 0, -1);
786         }
787         /* update the candidates view */
788         if (candidates > 0) {
789             mCandidatesViewManager.displayCandidates(mConverter);
790         } else {
791             mCandidatesViewManager.clearCandidates();
792         }
793     }
794 
795     /**
796      * Update the composing text.
797      *
798      * @param layer  {@link mComposingText}'s layer to display
799      */
updateComposingText(int layer)800     private void updateComposingText(int layer) {
801         /* update the candidates view */
802         if (!mOptPrediction) {
803             commitText(1);
804             mComposingText.clear();
805             if (mSymbolMode) {
806                 mHandler.removeMessages(MSG_PREDICTION);
807                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION), 0);
808             }
809         } else {
810             if (mComposingText.size(1) != 0) {
811                 mHandler.removeMessages(MSG_PREDICTION);
812                 if (mCandidatesViewManager.getCurrentView().isShown()) {
813                     mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION),
814                                                 PREDICTION_DELAY_MS_SHOWING_CANDIDATE);
815                 } else {
816                     mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION),
817                                                 PREDICTION_DELAY_MS_1ST);
818                 }
819             } else {
820                 mHandler.removeMessages(MSG_PREDICTION);
821                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION), 0);
822             }
823 
824             /* notice to the input view */
825             this.mInputViewManager.onUpdateState(this);
826 
827             /* set the text for displaying as the composing text */
828             SpannableStringBuilder disp = mDisplayText;
829             disp.clear();
830             disp.insert(0, mComposingText.toString(layer));
831 
832             /* add decoration to the text */
833             int cursor = mComposingText.getCursor(layer);
834             if (disp.length() != 0) {
835                 if (cursor > 0 && cursor < disp.length()) {
836                     disp.setSpan(SPAN_EXACT_BGCOLOR_HL, 0, cursor,
837                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
838                 }
839                 if (cursor < disp.length()) {
840                     mDisplayText.setSpan(SPAN_REMAIN_BGCOLOR_HL, cursor, disp.length(),
841                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
842                     mDisplayText.setSpan(SPAN_TEXTCOLOR, 0, disp.length(),
843                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
844                 }
845 
846                 disp.setSpan(SPAN_UNDERLINE, 0, disp.length(),
847                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
848             }
849 
850             int displayCursor = cursor;
851             if (FIX_CURSOR_TEXT_END) {
852                 displayCursor = (cursor == 0) ?  0 : 1;
853             }
854             /* update the composing text on the EditView */
855             mInputConnection.setComposingText(disp, displayCursor);
856         }
857     }
858 
859     /**
860      * Commit the composing text.
861      *
862      * @param layer  {@link mComposingText}'s layer to commit.
863      */
commitText(int layer)864     private void commitText(int layer) {
865         String tmp = mComposingText.toString(layer);
866 
867         if (mOptLearning && mConverter != null && tmp.length() > 0) {
868             WnnWord word = new WnnWord(tmp, tmp);
869             mConverter.learn(word);
870         }
871 
872         mInputConnection.commitText(tmp, (FIX_CURSOR_TEXT_END ? 1 : tmp.length()));
873         mCandidatesViewManager.clearCandidates();
874     }
875 
876     /**
877      * Commit a word
878      *
879      * @param word          A word to commit
880      * @param withSpace     Append a space after the word if {@code true}.
881      */
commitText(WnnWord word, boolean withSpace)882     private void commitText(WnnWord word, boolean withSpace) {
883 
884         if (mOptLearning && mConverter != null) {
885             mConverter.learn(word);
886         }
887 
888         mInputConnection.commitText(word.candidate, (FIX_CURSOR_TEXT_END ? 1 : word.candidate.length()));
889 
890         if (withSpace) {
891             commitText(" ");
892         }
893     }
894 
895     /**
896      * Commit a string
897      * <br>
898      * The string is not registered into the learning dictionary.
899      *
900      * @param str  A string to commit
901      */
commitText(String str)902     private void commitText(String str) {
903         mInputConnection.commitText(str, (FIX_CURSOR_TEXT_END ? 1 : str.length()));
904         mCandidatesViewManager.clearCandidates();
905     }
906 
907     /**
908      * Dismiss the pop-up keyboard
909      */
dismissPopupKeyboard()910     protected void dismissPopupKeyboard() {
911         DefaultSoftKeyboardEN kbd = (DefaultSoftKeyboardEN)mInputViewManager;
912         if (kbd != null) {
913             kbd.dismissPopupKeyboard();
914         }
915     }
916 
917     /**
918      * Display current meta-key state.
919      */
updateMetaKeyStateDisplay()920     private void updateMetaKeyStateDisplay() {
921         int mode = 0;
922         if(mHardShift == 0 && mHardAlt == 0){
923             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
924         }else if(mHardShift == 1 && mHardAlt == 0){
925             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_OFF;
926         }else if(mHardShift == 2  && mHardAlt == 0){
927             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_OFF;
928         }else if(mHardShift == 0 && mHardAlt == 1){
929             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_ON;
930         }else if(mHardShift == 0 && mHardAlt == 2){
931             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_LOCK;
932         }else if(mHardShift == 1 && mHardAlt == 1){
933             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_ON;
934         }else if(mHardShift == 1 && mHardAlt == 2){
935             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_LOCK;
936         }else if(mHardShift == 2 && mHardAlt == 1){
937             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_ON;
938         }else if(mHardShift == 2 && mHardAlt == 2){
939             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_LOCK;
940         }else{
941             mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
942         }
943 
944         ((DefaultSoftKeyboard) mInputViewManager).updateIndicator(mode);
945     }
946 
947     /**
948      * Handling KeyEvent(KEYUP)
949      * <br>
950      * This method is called from {@link #onEvent()}.
951      *
952      * @param ev   An up key event
953      */
onKeyUpEvent(KeyEvent ev)954     private void onKeyUpEvent(KeyEvent ev) {
955         int key = ev.getKeyCode();
956         if(!mShiftPressing){
957             if(key == KeyEvent.KEYCODE_SHIFT_LEFT || key == KeyEvent.KEYCODE_SHIFT_RIGHT){
958                 mHardShift = 0;
959                 mShiftPressing = true;
960                 updateMetaKeyStateDisplay();
961             }
962         }
963         if(!mAltPressing ){
964             if(key == KeyEvent.KEYCODE_ALT_LEFT || key == KeyEvent.KEYCODE_ALT_RIGHT){
965                 mHardAlt = 0;
966                 mAltPressing   = true;
967                 updateMetaKeyStateDisplay();
968             }
969         }
970     }
971     /**
972      * Fits an editor info.
973      *
974      * @param preferences  The preference data.
975      * @param info          The editor info.
976      */
fitInputType(SharedPreferences preference, EditorInfo info)977     private void fitInputType(SharedPreferences preference, EditorInfo info) {
978         if (info.inputType == EditorInfo.TYPE_NULL) {
979             mDirectInputMode = true;
980             return;
981         }
982 
983         mEnableAutoHideKeyboard = false;
984 
985         /* set prediction & spell correction mode */
986         mOptPrediction      = preference.getBoolean("opt_en_prediction", true);
987         mOptSpellCorrection = preference.getBoolean("opt_en_spell_correction", true);
988         mOptLearning        = preference.getBoolean("opt_en_enable_learning", true);
989 
990         /* prediction on/off */
991         switch (info.inputType & EditorInfo.TYPE_MASK_CLASS) {
992         case EditorInfo.TYPE_CLASS_NUMBER:
993         case EditorInfo.TYPE_CLASS_DATETIME:
994         case EditorInfo.TYPE_CLASS_PHONE:
995             mOptPrediction = false;
996             mOptLearning = false;
997             break;
998 
999         case EditorInfo.TYPE_CLASS_TEXT:
1000             switch (info.inputType & EditorInfo.TYPE_MASK_VARIATION) {
1001             case EditorInfo.TYPE_TEXT_VARIATION_PASSWORD:
1002             case EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD:
1003                 mOptLearning = false;
1004                 mOptPrediction = false;
1005                 break;
1006 
1007             case EditorInfo.TYPE_TEXT_VARIATION_PHONETIC:
1008                 mOptLearning = false;
1009                 mOptPrediction = false;
1010                 break;
1011             default:
1012                 break;
1013             }
1014         }
1015 
1016         /* doesn't learn any word if it is not prediction mode */
1017         if (!mOptPrediction) {
1018             mOptLearning = false;
1019         }
1020 
1021         /* set engine's mode */
1022         if (mOptSpellCorrection) {
1023             mConverterEN.setDictionary(OpenWnnEngineEN.DICT_FOR_CORRECT_MISTYPE);
1024         } else {
1025             mConverterEN.setDictionary(OpenWnnEngineEN.DICT_DEFAULT);
1026         }
1027         checkTutorial(info.privateImeOptions);
1028     }
1029 
1030     /**
1031      * Check and start the tutorial if it is the tutorial mode.
1032      *
1033      * @param privateImeOptions IME's options
1034      */
checkTutorial(String privateImeOptions)1035     private void checkTutorial(String privateImeOptions) {
1036         if (privateImeOptions == null) return;
1037         if (privateImeOptions.equals("com.google.android.setupwizard:ShowTutorial")) {
1038             if ((mTutorial == null) && mEnableTutorial) startTutorial();
1039         } else if (privateImeOptions.equals("com.google.android.setupwizard:HideTutorial")) {
1040             if (mTutorial != null) {
1041                 if (mTutorial.close()) {
1042                     mTutorial = null;
1043                 }
1044             }
1045         }
1046     }
1047 
1048     /**
1049      * Start the tutorial
1050      */
startTutorial()1051     private void startTutorial() {
1052         DefaultSoftKeyboardEN inputManager = ((DefaultSoftKeyboardEN) mInputViewManager);
1053         View v = inputManager.getKeyboardView();
1054         v.setOnTouchListener(new View.OnTouchListener() {
1055 				public boolean onTouch(View v, MotionEvent event) {
1056 					return true;
1057 				}});
1058         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500);
1059     }
1060 
1061     /**
1062      * Close the tutorial
1063      */
tutorialDone()1064     public void tutorialDone() {
1065         mTutorial = null;
1066     }
1067 
1068     /** @see OpenWnn#close */
close()1069     @Override protected void close() {
1070         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLOSE), 0);
1071     }
1072 }
1073