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