Home | History | Annotate | Download | only in pinyin
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.pinyin;
     18 
     19 import android.app.AlertDialog;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.Intent;
     25 import android.content.ServiceConnection;
     26 import android.content.res.Configuration;
     27 import android.inputmethodservice.InputMethodService;
     28 import android.os.Handler;
     29 import android.os.IBinder;
     30 import android.os.RemoteException;
     31 import android.preference.PreferenceManager;
     32 import android.util.Log;
     33 import android.view.Gravity;
     34 import android.view.GestureDetector;
     35 import android.view.LayoutInflater;
     36 import android.view.KeyEvent;
     37 import android.view.MotionEvent;
     38 import android.view.View;
     39 import android.view.Window;
     40 import android.view.WindowManager;
     41 import android.view.View.MeasureSpec;
     42 import android.view.ViewGroup.LayoutParams;
     43 import android.view.inputmethod.CompletionInfo;
     44 import android.view.inputmethod.InputConnection;
     45 import android.view.inputmethod.EditorInfo;
     46 import android.view.inputmethod.InputMethodManager;
     47 import android.widget.LinearLayout;
     48 import android.widget.PopupWindow;
     49 
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Vector;
     53 
     54 /**
     55  * Main class of the Pinyin input method.
     56  */
     57 public class PinyinIME extends InputMethodService {
     58     /**
     59      * TAG for debug.
     60      */
     61     static final String TAG = "PinyinIME";
     62 
     63     /**
     64      * If is is true, IME will simulate key events for delete key, and send the
     65      * events back to the application.
     66      */
     67     private static final boolean SIMULATE_KEY_DELETE = true;
     68 
     69     /**
     70      * Necessary environment configurations like screen size for this IME.
     71      */
     72     private Environment mEnvironment;
     73 
     74     /**
     75      * Used to switch input mode.
     76      */
     77     private InputModeSwitcher mInputModeSwitcher;
     78 
     79     /**
     80      * Soft keyboard container view to host real soft keyboard view.
     81      */
     82     private SkbContainer mSkbContainer;
     83 
     84     /**
     85      * The floating container which contains the composing view. If necessary,
     86      * some other view like candiates container can also be put here.
     87      */
     88     private LinearLayout mFloatingContainer;
     89 
     90     /**
     91      * View to show the composing string.
     92      */
     93     private ComposingView mComposingView;
     94 
     95     /**
     96      * Window to show the composing string.
     97      */
     98     private PopupWindow mFloatingWindow;
     99 
    100     /**
    101      * Used to show the floating window.
    102      */
    103     private PopupTimer mFloatingWindowTimer = new PopupTimer();
    104 
    105     /**
    106      * View to show candidates list.
    107      */
    108     private CandidatesContainer mCandidatesContainer;
    109 
    110     /**
    111      * Balloon used when user presses a candidate.
    112      */
    113     private BalloonHint mCandidatesBalloon;
    114 
    115     /**
    116      * Used to notify the input method when the user touch a candidate.
    117      */
    118     private ChoiceNotifier mChoiceNotifier;
    119 
    120     /**
    121      * Used to notify gestures from soft keyboard.
    122      */
    123     private OnGestureListener mGestureListenerSkb;
    124 
    125     /**
    126      * Used to notify gestures from candidates view.
    127      */
    128     private OnGestureListener mGestureListenerCandidates;
    129 
    130     /**
    131      * The on-screen movement gesture detector for soft keyboard.
    132      */
    133     private GestureDetector mGestureDetectorSkb;
    134 
    135     /**
    136      * The on-screen movement gesture detector for candidates view.
    137      */
    138     private GestureDetector mGestureDetectorCandidates;
    139 
    140     /**
    141      * Option dialog to choose settings and other IMEs.
    142      */
    143     private AlertDialog mOptionsDialog;
    144 
    145     /**
    146      * Connection used to bind the decoding service.
    147      */
    148     private PinyinDecoderServiceConnection mPinyinDecoderServiceConnection;
    149 
    150     /**
    151      * The current IME status.
    152      *
    153      * @see com.android.inputmethod.pinyin.PinyinIME.ImeState
    154      */
    155     private ImeState mImeState = ImeState.STATE_IDLE;
    156 
    157     /**
    158      * The decoding information, include spelling(Pinyin) string, decoding
    159      * result, etc.
    160      */
    161     private DecodingInfo mDecInfo = new DecodingInfo();
    162 
    163     /**
    164      * For English input.
    165      */
    166     private EnglishInputProcessor mImEn;
    167 
    168     // receive ringer mode changes
    169     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    170         @Override
    171         public void onReceive(Context context, Intent intent) {
    172             SoundManager.getInstance(context).updateRingerMode();
    173         }
    174     };
    175 
    176     @Override
    177     public void onCreate() {
    178         mEnvironment = Environment.getInstance();
    179         if (mEnvironment.needDebug()) {
    180             Log.d(TAG, "onCreate.");
    181         }
    182         super.onCreate();
    183 
    184         startPinyinDecoderService();
    185         mImEn = new EnglishInputProcessor();
    186         Settings.getInstance(PreferenceManager
    187                 .getDefaultSharedPreferences(getApplicationContext()));
    188 
    189         mInputModeSwitcher = new InputModeSwitcher(this);
    190         mChoiceNotifier = new ChoiceNotifier(this);
    191         mGestureListenerSkb = new OnGestureListener(false);
    192         mGestureListenerCandidates = new OnGestureListener(true);
    193         mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
    194         mGestureDetectorCandidates = new GestureDetector(this,
    195                 mGestureListenerCandidates);
    196 
    197         mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
    198                 this);
    199     }
    200 
    201     @Override
    202     public void onDestroy() {
    203         if (mEnvironment.needDebug()) {
    204             Log.d(TAG, "onDestroy.");
    205         }
    206         unbindService(mPinyinDecoderServiceConnection);
    207         Settings.releaseInstance();
    208         super.onDestroy();
    209     }
    210 
    211     @Override
    212     public void onConfigurationChanged(Configuration newConfig) {
    213         Environment env = Environment.getInstance();
    214         if (mEnvironment.needDebug()) {
    215             Log.d(TAG, "onConfigurationChanged");
    216             Log.d(TAG, "--last config: " + env.getConfiguration().toString());
    217             Log.d(TAG, "---new config: " + newConfig.toString());
    218         }
    219         // We need to change the local environment first so that UI components
    220         // can get the environment instance to handle size issues. When
    221         // super.onConfigurationChanged() is called, onCreateCandidatesView()
    222         // and onCreateInputView() will be executed if necessary.
    223         env.onConfigurationChanged(newConfig, this);
    224 
    225         // Clear related UI of the previous configuration.
    226         if (null != mSkbContainer) {
    227             mSkbContainer.dismissPopups();
    228         }
    229         if (null != mCandidatesBalloon) {
    230             mCandidatesBalloon.dismiss();
    231         }
    232         super.onConfigurationChanged(newConfig);
    233         resetToIdleState(false);
    234     }
    235 
    236     @Override
    237     public boolean onKeyDown(int keyCode, KeyEvent event) {
    238         if (processKey(event, 0 != event.getRepeatCount())) return true;
    239         return super.onKeyDown(keyCode, event);
    240     }
    241 
    242     @Override
    243     public boolean onKeyUp(int keyCode, KeyEvent event) {
    244         if (processKey(event, true)) return true;
    245         return super.onKeyUp(keyCode, event);
    246     }
    247 
    248     private boolean processKey(KeyEvent event, boolean realAction) {
    249         if (ImeState.STATE_BYPASS == mImeState) return false;
    250 
    251         int keyCode = event.getKeyCode();
    252         // SHIFT-SPACE is used to switch between Chinese and English
    253         // when HKB is on.
    254         if (KeyEvent.KEYCODE_SPACE == keyCode && event.isShiftPressed()) {
    255             if (!realAction) return true;
    256 
    257             updateIcon(mInputModeSwitcher.switchLanguageWithHkb());
    258             resetToIdleState(false);
    259 
    260             int allMetaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON
    261                     | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_ON
    262                     | KeyEvent.META_SHIFT_LEFT_ON
    263                     | KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SYM_ON;
    264             getCurrentInputConnection().clearMetaKeyStates(allMetaState);
    265             return true;
    266         }
    267 
    268         // If HKB is on to input English, by-pass the key event so that
    269         // default key listener will handle it.
    270         if (mInputModeSwitcher.isEnglishWithHkb()) {
    271             return false;
    272         }
    273 
    274         if (processFunctionKeys(keyCode, realAction)) {
    275             return true;
    276         }
    277 
    278         int keyChar = 0;
    279         if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
    280             keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
    281         } else if (keyCode >= KeyEvent.KEYCODE_0
    282                 && keyCode <= KeyEvent.KEYCODE_9) {
    283             keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
    284         } else if (keyCode == KeyEvent.KEYCODE_COMMA) {
    285             keyChar = ',';
    286         } else if (keyCode == KeyEvent.KEYCODE_PERIOD) {
    287             keyChar = '.';
    288         } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
    289             keyChar = ' ';
    290         } else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE) {
    291             keyChar = '\'';
    292         }
    293 
    294         if (mInputModeSwitcher.isEnglishWithSkb()) {
    295             return mImEn.processKey(getCurrentInputConnection(), event,
    296                     mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);
    297         } else if (mInputModeSwitcher.isChineseText()) {
    298             if (mImeState == ImeState.STATE_IDLE ||
    299                     mImeState == ImeState.STATE_APP_COMPLETION) {
    300                 mImeState = ImeState.STATE_IDLE;
    301                 return processStateIdle(keyChar, keyCode, event, realAction);
    302             } else if (mImeState == ImeState.STATE_INPUT) {
    303                 return processStateInput(keyChar, keyCode, event, realAction);
    304             } else if (mImeState == ImeState.STATE_PREDICT) {
    305                 return processStatePredict(keyChar, keyCode, event, realAction);
    306             } else if (mImeState == ImeState.STATE_COMPOSING) {
    307                 return processStateEditComposing(keyChar, keyCode, event,
    308                         realAction);
    309             }
    310         } else {
    311             if (0 != keyChar && realAction) {
    312                 commitResultText(String.valueOf((char) keyChar));
    313             }
    314         }
    315 
    316         return false;
    317     }
    318 
    319     // keyCode can be from both hard key or soft key.
    320     private boolean processFunctionKeys(int keyCode, boolean realAction) {
    321         // Back key is used to dismiss all popup UI in a soft keyboard.
    322         if (keyCode == KeyEvent.KEYCODE_BACK) {
    323             if (isInputViewShown()) {
    324                 if (mSkbContainer.handleBack(realAction)) return true;
    325             }
    326         }
    327 
    328         // Chinese related input is handle separately.
    329         if (mInputModeSwitcher.isChineseText()) {
    330             return false;
    331         }
    332 
    333         if (null != mCandidatesContainer && mCandidatesContainer.isShown()
    334                 && !mDecInfo.isCandidatesListEmpty()) {
    335             if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
    336                 if (!realAction) return true;
    337 
    338                 chooseCandidate(-1);
    339                 return true;
    340             }
    341 
    342             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
    343                 if (!realAction) return true;
    344                 mCandidatesContainer.activeCurseBackward();
    345                 return true;
    346             }
    347 
    348             if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    349                 if (!realAction) return true;
    350                 mCandidatesContainer.activeCurseForward();
    351                 return true;
    352             }
    353 
    354             if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
    355                 if (!realAction) return true;
    356                 mCandidatesContainer.pageBackward(false, true);
    357                 return true;
    358             }
    359 
    360             if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    361                 if (!realAction) return true;
    362                 mCandidatesContainer.pageForward(false, true);
    363                 return true;
    364             }
    365 
    366             if (keyCode == KeyEvent.KEYCODE_DEL &&
    367                     ImeState.STATE_PREDICT == mImeState) {
    368                 if (!realAction) return true;
    369                 resetToIdleState(false);
    370                 return true;
    371             }
    372         } else {
    373             if (keyCode == KeyEvent.KEYCODE_DEL) {
    374                 if (!realAction) return true;
    375                 if (SIMULATE_KEY_DELETE) {
    376                     simulateKeyEventDownUp(keyCode);
    377                 } else {
    378                     getCurrentInputConnection().deleteSurroundingText(1, 0);
    379                 }
    380                 return true;
    381             }
    382             if (keyCode == KeyEvent.KEYCODE_ENTER) {
    383                 if (!realAction) return true;
    384                 sendKeyChar('\n');
    385                 return true;
    386             }
    387             if (keyCode == KeyEvent.KEYCODE_SPACE) {
    388                 if (!realAction) return true;
    389                 sendKeyChar(' ');
    390                 return true;
    391             }
    392         }
    393 
    394         return false;
    395     }
    396 
    397     private boolean processStateIdle(int keyChar, int keyCode, KeyEvent event,
    398             boolean realAction) {
    399         // In this status, when user presses keys in [a..z], the status will
    400         // change to input state.
    401         if (keyChar >= 'a' && keyChar <= 'z' && !event.isAltPressed()) {
    402             if (!realAction) return true;
    403             mDecInfo.addSplChar((char) keyChar, true);
    404             chooseAndUpdate(-1);
    405             return true;
    406         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
    407             if (!realAction) return true;
    408             if (SIMULATE_KEY_DELETE) {
    409                 simulateKeyEventDownUp(keyCode);
    410             } else {
    411                 getCurrentInputConnection().deleteSurroundingText(1, 0);
    412             }
    413             return true;
    414         } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
    415             if (!realAction) return true;
    416             sendKeyChar('\n');
    417             return true;
    418         } else if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
    419                 || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
    420                 || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
    421                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
    422             return true;
    423         } else if (event.isAltPressed()) {
    424             char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
    425             if (0 != fullwidth_char) {
    426                 if (realAction) {
    427                     String result = String.valueOf(fullwidth_char);
    428                     commitResultText(result);
    429                 }
    430                 return true;
    431             } else {
    432                 if (keyCode >= KeyEvent.KEYCODE_A
    433                         && keyCode <= KeyEvent.KEYCODE_Z) {
    434                     return true;
    435                 }
    436             }
    437         } else if (keyChar != 0 && keyChar != '\t') {
    438             if (realAction) {
    439                 if (keyChar == ',' || keyChar == '.') {
    440                     inputCommaPeriod("", keyChar, false, ImeState.STATE_IDLE);
    441                 } else {
    442                     if (0 != keyChar) {
    443                         String result = String.valueOf((char) keyChar);
    444                         commitResultText(result);
    445                     }
    446                 }
    447             }
    448             return true;
    449         }
    450         return false;
    451     }
    452 
    453     private boolean processStateInput(int keyChar, int keyCode, KeyEvent event,
    454             boolean realAction) {
    455         // If ALT key is pressed, input alternative key. But if the
    456         // alternative key is quote key, it will be used for input a splitter
    457         // in Pinyin string.
    458         if (event.isAltPressed()) {
    459             if ('\'' != event.getUnicodeChar(event.getMetaState())) {
    460                 if (realAction) {
    461                     char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
    462                     if (0 != fullwidth_char) {
    463                         commitResultText(mDecInfo
    464                                 .getCurrentFullSent(mCandidatesContainer
    465                                         .getActiveCandiatePos()) +
    466                                         String.valueOf(fullwidth_char));
    467                         resetToIdleState(false);
    468                     }
    469                 }
    470                 return true;
    471             } else {
    472                 keyChar = '\'';
    473             }
    474         }
    475 
    476         if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''
    477                 && !mDecInfo.charBeforeCursorIsSeparator()
    478                 || keyCode == KeyEvent.KEYCODE_DEL) {
    479             if (!realAction) return true;
    480             return processSurfaceChange(keyChar, keyCode);
    481         } else if (keyChar == ',' || keyChar == '.') {
    482             if (!realAction) return true;
    483             inputCommaPeriod(mDecInfo.getCurrentFullSent(mCandidatesContainer
    484                     .getActiveCandiatePos()), keyChar, true,
    485                     ImeState.STATE_IDLE);
    486             return true;
    487         } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
    488                 || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
    489                 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
    490                 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    491             if (!realAction) return true;
    492 
    493             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
    494                 mCandidatesContainer.activeCurseBackward();
    495             } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    496                 mCandidatesContainer.activeCurseForward();
    497             } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
    498                 // If it has been the first page, a up key will shift
    499                 // the state to edit composing string.
    500                 if (!mCandidatesContainer.pageBackward(false, true)) {
    501                     mCandidatesContainer.enableActiveHighlight(false);
    502                     changeToStateComposing(true);
    503                     updateComposingText(true);
    504                 }
    505             } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    506                 mCandidatesContainer.pageForward(false, true);
    507             }
    508             return true;
    509         } else if (keyCode >= KeyEvent.KEYCODE_1
    510                 && keyCode <= KeyEvent.KEYCODE_9) {
    511             if (!realAction) return true;
    512 
    513             int activePos = keyCode - KeyEvent.KEYCODE_1;
    514             int currentPage = mCandidatesContainer.getCurrentPage();
    515             if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
    516                 activePos = activePos
    517                         + mDecInfo.getCurrentPageStart(currentPage);
    518                 if (activePos >= 0) {
    519                     chooseAndUpdate(activePos);
    520                 }
    521             }
    522             return true;
    523         } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
    524             if (!realAction) return true;
    525             if (mInputModeSwitcher.isEnterNoramlState()) {
    526                 commitResultText(mDecInfo.getOrigianlSplStr().toString());
    527                 resetToIdleState(false);
    528             } else {
    529                 commitResultText(mDecInfo
    530                         .getCurrentFullSent(mCandidatesContainer
    531                                 .getActiveCandiatePos()));
    532                 sendKeyChar('\n');
    533                 resetToIdleState(false);
    534             }
    535             return true;
    536         } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
    537                 || keyCode == KeyEvent.KEYCODE_SPACE) {
    538             if (!realAction) return true;
    539             chooseCandidate(-1);
    540             return true;
    541         } else if (keyCode == KeyEvent.KEYCODE_BACK) {
    542             if (!realAction) return true;
    543             resetToIdleState(false);
    544             requestHideSelf(0);
    545             return true;
    546         }
    547         return false;
    548     }
    549 
    550     private boolean processStatePredict(int keyChar, int keyCode,
    551             KeyEvent event, boolean realAction) {
    552         if (!realAction) return true;
    553 
    554         // If ALT key is pressed, input alternative key.
    555         if (event.isAltPressed()) {
    556             char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
    557             if (0 != fullwidth_char) {
    558                 commitResultText(mDecInfo.getCandidate(mCandidatesContainer
    559                                 .getActiveCandiatePos()) +
    560                                 String.valueOf(fullwidth_char));
    561                 resetToIdleState(false);
    562             }
    563             return true;
    564         }
    565 
    566         // In this status, when user presses keys in [a..z], the status will
    567         // change to input state.
    568         if (keyChar >= 'a' && keyChar <= 'z') {
    569             changeToStateInput(true);
    570             mDecInfo.addSplChar((char) keyChar, true);
    571             chooseAndUpdate(-1);
    572         } else if (keyChar == ',' || keyChar == '.') {
    573             inputCommaPeriod("", keyChar, true, ImeState.STATE_IDLE);
    574         } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
    575                 || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
    576                 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
    577                 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    578             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
    579                 mCandidatesContainer.activeCurseBackward();
    580             }
    581             if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    582                 mCandidatesContainer.activeCurseForward();
    583             }
    584             if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
    585                 mCandidatesContainer.pageBackward(false, true);
    586             }
    587             if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    588                 mCandidatesContainer.pageForward(false, true);
    589             }
    590         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
    591             resetToIdleState(false);
    592         } else if (keyCode == KeyEvent.KEYCODE_BACK) {
    593             resetToIdleState(false);
    594             requestHideSelf(0);
    595         } else if (keyCode >= KeyEvent.KEYCODE_1
    596                 && keyCode <= KeyEvent.KEYCODE_9) {
    597             int activePos = keyCode - KeyEvent.KEYCODE_1;
    598             int currentPage = mCandidatesContainer.getCurrentPage();
    599             if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
    600                 activePos = activePos
    601                         + mDecInfo.getCurrentPageStart(currentPage);
    602                 if (activePos >= 0) {
    603                     chooseAndUpdate(activePos);
    604                 }
    605             }
    606         } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
    607             sendKeyChar('\n');
    608             resetToIdleState(false);
    609         } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
    610                 || keyCode == KeyEvent.KEYCODE_SPACE) {
    611             chooseCandidate(-1);
    612         }
    613 
    614         return true;
    615     }
    616 
    617     private boolean processStateEditComposing(int keyChar, int keyCode,
    618             KeyEvent event, boolean realAction) {
    619         if (!realAction) return true;
    620 
    621         ComposingView.ComposingStatus cmpsvStatus =
    622                 mComposingView.getComposingStatus();
    623 
    624         // If ALT key is pressed, input alternative key. But if the
    625         // alternative key is quote key, it will be used for input a splitter
    626         // in Pinyin string.
    627         if (event.isAltPressed()) {
    628             if ('\'' != event.getUnicodeChar(event.getMetaState())) {
    629                 char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
    630                 if (0 != fullwidth_char) {
    631                     String retStr;
    632                     if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE ==
    633                             cmpsvStatus) {
    634                         retStr = mDecInfo.getOrigianlSplStr().toString();
    635                     } else {
    636                         retStr = mDecInfo.getComposingStr();
    637                     }
    638                     commitResultText(retStr + String.valueOf(fullwidth_char));
    639                     resetToIdleState(false);
    640                 }
    641                 return true;
    642             } else {
    643                 keyChar = '\'';
    644             }
    645         }
    646 
    647         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    648             if (!mDecInfo.selectionFinished()) {
    649                 changeToStateInput(true);
    650             }
    651         } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
    652                 || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    653             mComposingView.moveCursor(keyCode);
    654         } else if ((keyCode == KeyEvent.KEYCODE_ENTER && mInputModeSwitcher
    655                 .isEnterNoramlState())
    656                 || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
    657                 || keyCode == KeyEvent.KEYCODE_SPACE) {
    658             if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
    659                 String str = mDecInfo.getOrigianlSplStr().toString();
    660                 if (!tryInputRawUnicode(str)) {
    661                     commitResultText(str);
    662                 }
    663             } else if (ComposingView.ComposingStatus.EDIT_PINYIN == cmpsvStatus) {
    664                 String str = mDecInfo.getComposingStr();
    665                 if (!tryInputRawUnicode(str)) {
    666                     commitResultText(str);
    667                 }
    668             } else {
    669                 commitResultText(mDecInfo.getComposingStr());
    670             }
    671             resetToIdleState(false);
    672         } else if (keyCode == KeyEvent.KEYCODE_ENTER
    673                 && !mInputModeSwitcher.isEnterNoramlState()) {
    674             String retStr;
    675             if (!mDecInfo.isCandidatesListEmpty()) {
    676                 retStr = mDecInfo.getCurrentFullSent(mCandidatesContainer
    677                         .getActiveCandiatePos());
    678             } else {
    679                 retStr = mDecInfo.getComposingStr();
    680             }
    681             commitResultText(retStr);
    682             sendKeyChar('\n');
    683             resetToIdleState(false);
    684         } else if (keyCode == KeyEvent.KEYCODE_BACK) {
    685             resetToIdleState(false);
    686             requestHideSelf(0);
    687             return true;
    688         } else {
    689             return processSurfaceChange(keyChar, keyCode);
    690         }
    691         return true;
    692     }
    693 
    694     private boolean tryInputRawUnicode(String str) {
    695         if (str.length() > 7) {
    696             if (str.substring(0, 7).compareTo("unicode") == 0) {
    697                 try {
    698                     String digitStr = str.substring(7);
    699                     int startPos = 0;
    700                     int radix = 10;
    701                     if (digitStr.length() > 2 && digitStr.charAt(0) == '0'
    702                             && digitStr.charAt(1) == 'x') {
    703                         startPos = 2;
    704                         radix = 16;
    705                     }
    706                     digitStr = digitStr.substring(startPos);
    707                     int unicode = Integer.parseInt(digitStr, radix);
    708                     if (unicode > 0) {
    709                         char low = (char) (unicode & 0x0000ffff);
    710                         char high = (char) ((unicode & 0xffff0000) >> 16);
    711                         commitResultText(String.valueOf(low));
    712                         if (0 != high) {
    713                             commitResultText(String.valueOf(high));
    714                         }
    715                     }
    716                     return true;
    717                 } catch (NumberFormatException e) {
    718                     return false;
    719                 }
    720             } else if (str.substring(str.length() - 7, str.length()).compareTo(
    721                     "unicode") == 0) {
    722                 String resultStr = "";
    723                 for (int pos = 0; pos < str.length() - 7; pos++) {
    724                     if (pos > 0) {
    725                         resultStr += " ";
    726                     }
    727 
    728                     resultStr += "0x" + Integer.toHexString(str.charAt(pos));
    729                 }
    730                 commitResultText(String.valueOf(resultStr));
    731                 return true;
    732             }
    733         }
    734         return false;
    735     }
    736 
    737     private boolean processSurfaceChange(int keyChar, int keyCode) {
    738         if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {
    739             return true;
    740         }
    741 
    742         if ((keyChar >= 'a' && keyChar <= 'z')
    743                 || (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())
    744                 || (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && ImeState.STATE_COMPOSING == mImeState)) {
    745             mDecInfo.addSplChar((char) keyChar, false);
    746             chooseAndUpdate(-1);
    747         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
    748             mDecInfo.prepareDeleteBeforeCursor();
    749             chooseAndUpdate(-1);
    750         }
    751         return true;
    752     }
    753 
    754     private void changeToStateComposing(boolean updateUi) {
    755         mImeState = ImeState.STATE_COMPOSING;
    756         if (!updateUi) return;
    757 
    758         if (null != mSkbContainer && mSkbContainer.isShown()) {
    759             mSkbContainer.toggleCandidateMode(true);
    760         }
    761     }
    762 
    763     private void changeToStateInput(boolean updateUi) {
    764         mImeState = ImeState.STATE_INPUT;
    765         if (!updateUi) return;
    766 
    767         if (null != mSkbContainer && mSkbContainer.isShown()) {
    768             mSkbContainer.toggleCandidateMode(true);
    769         }
    770         showCandidateWindow(true);
    771     }
    772 
    773     private void simulateKeyEventDownUp(int keyCode) {
    774         InputConnection ic = getCurrentInputConnection();
    775         if (null == ic) return;
    776 
    777         ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
    778         ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
    779     }
    780 
    781     private void commitResultText(String resultText) {
    782         InputConnection ic = getCurrentInputConnection();
    783         if (null != ic) ic.commitText(resultText, 1);
    784         if (null != mComposingView) {
    785             mComposingView.setVisibility(View.INVISIBLE);
    786             mComposingView.invalidate();
    787         }
    788     }
    789 
    790     private void updateComposingText(boolean visible) {
    791         if (!visible) {
    792             mComposingView.setVisibility(View.INVISIBLE);
    793         } else {
    794             mComposingView.setDecodingInfo(mDecInfo, mImeState);
    795             mComposingView.setVisibility(View.VISIBLE);
    796         }
    797         mComposingView.invalidate();
    798     }
    799 
    800     private void inputCommaPeriod(String preEdit, int keyChar,
    801             boolean dismissCandWindow, ImeState nextState) {
    802         if (keyChar == ',')
    803             preEdit += '\uff0c';
    804         else if (keyChar == '.')
    805             preEdit += '\u3002';
    806         else
    807             return;
    808         commitResultText(preEdit);
    809         if (dismissCandWindow) resetCandidateWindow();
    810         mImeState = nextState;
    811     }
    812 
    813     private void resetToIdleState(boolean resetInlineText) {
    814         if (ImeState.STATE_IDLE == mImeState) return;
    815 
    816         mImeState = ImeState.STATE_IDLE;
    817         mDecInfo.reset();
    818 
    819         if (null != mComposingView) mComposingView.reset();
    820         if (resetInlineText) commitResultText("");
    821         resetCandidateWindow();
    822     }
    823 
    824     private void chooseAndUpdate(int candId) {
    825         if (!mInputModeSwitcher.isChineseText()) {
    826             String choice = mDecInfo.getCandidate(candId);
    827             if (null != choice) {
    828                 commitResultText(choice);
    829             }
    830             resetToIdleState(false);
    831             return;
    832         }
    833 
    834         if (ImeState.STATE_PREDICT != mImeState) {
    835             // Get result candidate list, if choice_id < 0, do a new decoding.
    836             // If choice_id >=0, select the candidate, and get the new candidate
    837             // list.
    838             mDecInfo.chooseDecodingCandidate(candId);
    839         } else {
    840             // Choose a prediction item.
    841             mDecInfo.choosePredictChoice(candId);
    842         }
    843 
    844         if (mDecInfo.getComposingStr().length() > 0) {
    845             String resultStr;
    846             resultStr = mDecInfo.getComposingStrActivePart();
    847 
    848             // choiceId >= 0 means user finishes a choice selection.
    849             if (candId >= 0 && mDecInfo.canDoPrediction()) {
    850                 commitResultText(resultStr);
    851                 mImeState = ImeState.STATE_PREDICT;
    852                 if (null != mSkbContainer && mSkbContainer.isShown()) {
    853                     mSkbContainer.toggleCandidateMode(false);
    854                 }
    855                 // Try to get the prediction list.
    856                 if (Settings.getPrediction()) {
    857                     InputConnection ic = getCurrentInputConnection();
    858                     if (null != ic) {
    859                         CharSequence cs = ic.getTextBeforeCursor(3, 0);
    860                         if (null != cs) {
    861                             mDecInfo.preparePredicts(cs);
    862                         }
    863                     }
    864                 } else {
    865                     mDecInfo.resetCandidates();
    866                 }
    867 
    868                 if (mDecInfo.mCandidatesList.size() > 0) {
    869                     showCandidateWindow(false);
    870                 } else {
    871                     resetToIdleState(false);
    872                 }
    873             } else {
    874                 if (ImeState.STATE_IDLE == mImeState) {
    875                     if (mDecInfo.getSplStrDecodedLen() == 0) {
    876                         changeToStateComposing(true);
    877                     } else {
    878                         changeToStateInput(true);
    879                     }
    880                 } else {
    881                     if (mDecInfo.selectionFinished()) {
    882                         changeToStateComposing(true);
    883                     }
    884                 }
    885                 showCandidateWindow(true);
    886             }
    887         } else {
    888             resetToIdleState(false);
    889         }
    890     }
    891 
    892     // If activeCandNo is less than 0, get the current active candidate number
    893     // from candidate view, otherwise use activeCandNo.
    894     private void chooseCandidate(int activeCandNo) {
    895         if (activeCandNo < 0) {
    896             activeCandNo = mCandidatesContainer.getActiveCandiatePos();
    897         }
    898         if (activeCandNo >= 0) {
    899             chooseAndUpdate(activeCandNo);
    900         }
    901     }
    902 
    903     private boolean startPinyinDecoderService() {
    904         if (null == mDecInfo.mIPinyinDecoderService) {
    905             Intent serviceIntent = new Intent();
    906             serviceIntent.setClass(this, PinyinDecoderService.class);
    907 
    908             if (null == mPinyinDecoderServiceConnection) {
    909                 mPinyinDecoderServiceConnection = new PinyinDecoderServiceConnection();
    910             }
    911 
    912             // Bind service
    913             if (bindService(serviceIntent, mPinyinDecoderServiceConnection,
    914                     Context.BIND_AUTO_CREATE)) {
    915                 return true;
    916             } else {
    917                 return false;
    918             }
    919         }
    920         return true;
    921     }
    922 
    923     @Override
    924     public View onCreateCandidatesView() {
    925         if (mEnvironment.needDebug()) {
    926             Log.d(TAG, "onCreateCandidatesView.");
    927         }
    928 
    929         LayoutInflater inflater = getLayoutInflater();
    930         // Inflate the floating container view
    931         mFloatingContainer = (LinearLayout) inflater.inflate(
    932                 R.layout.floating_container, null);
    933 
    934         // The first child is the composing view.
    935         mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);
    936 
    937         mCandidatesContainer = (CandidatesContainer) inflater.inflate(
    938                 R.layout.candidates_container, null);
    939 
    940         // Create balloon hint for candidates view.
    941         mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer,
    942                 MeasureSpec.UNSPECIFIED);
    943         mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(
    944                 R.drawable.candidate_balloon_bg));
    945         mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon,
    946                 mGestureDetectorCandidates);
    947 
    948         // The floating window
    949         if (null != mFloatingWindow && mFloatingWindow.isShowing()) {
    950             mFloatingWindowTimer.cancelShowing();
    951             mFloatingWindow.dismiss();
    952         }
    953         mFloatingWindow = new PopupWindow(this);
    954         mFloatingWindow.setClippingEnabled(false);
    955         mFloatingWindow.setBackgroundDrawable(null);
    956         mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
    957         mFloatingWindow.setContentView(mFloatingContainer);
    958 
    959         setCandidatesViewShown(true);
    960         return mCandidatesContainer;
    961     }
    962 
    963     public void responseSoftKeyEvent(SoftKey sKey) {
    964         if (null == sKey) return;
    965 
    966         InputConnection ic = getCurrentInputConnection();
    967         if (ic == null) return;
    968 
    969         int keyCode = sKey.getKeyCode();
    970         // Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE,
    971         // KEYCODE_ENTER and KEYCODE_DPAD_CENTER.
    972         if (sKey.isKeyCodeKey()) {
    973             if (processFunctionKeys(keyCode, true)) return;
    974         }
    975 
    976         if (sKey.isUserDefKey()) {
    977             updateIcon(mInputModeSwitcher.switchModeForUserKey(keyCode));
    978             resetToIdleState(false);
    979             mSkbContainer.updateInputMode();
    980         } else {
    981             if (sKey.isKeyCodeKey()) {
    982                 KeyEvent eDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
    983                         keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
    984                 KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode,
    985                         0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
    986 
    987                 onKeyDown(keyCode, eDown);
    988                 onKeyUp(keyCode, eUp);
    989             } else if (sKey.isUniStrKey()) {
    990                 boolean kUsed = false;
    991                 String keyLabel = sKey.getKeyLabel();
    992                 if (mInputModeSwitcher.isChineseTextWithSkb()
    993                         && (ImeState.STATE_INPUT == mImeState || ImeState.STATE_COMPOSING == mImeState)) {
    994                     if (mDecInfo.length() > 0 && keyLabel.length() == 1
    995                             && keyLabel.charAt(0) == '\'') {
    996                         processSurfaceChange('\'', 0);
    997                         kUsed = true;
    998                     }
    999                 }
   1000                 if (!kUsed) {
   1001                     if (ImeState.STATE_INPUT == mImeState) {
   1002                         commitResultText(mDecInfo
   1003                                 .getCurrentFullSent(mCandidatesContainer
   1004                                         .getActiveCandiatePos()));
   1005                     } else if (ImeState.STATE_COMPOSING == mImeState) {
   1006                         commitResultText(mDecInfo.getComposingStr());
   1007                     }
   1008                     commitResultText(keyLabel);
   1009                     resetToIdleState(false);
   1010                 }
   1011             }
   1012 
   1013             // If the current soft keyboard is not sticky, IME needs to go
   1014             // back to the previous soft keyboard automatically.
   1015             if (!mSkbContainer.isCurrentSkbSticky()) {
   1016                 updateIcon(mInputModeSwitcher.requestBackToPreviousSkb());
   1017                 resetToIdleState(false);
   1018                 mSkbContainer.updateInputMode();
   1019             }
   1020         }
   1021     }
   1022 
   1023     private void showCandidateWindow(boolean showComposingView) {
   1024         if (mEnvironment.needDebug()) {
   1025             Log.d(TAG, "Candidates window is shown. Parent = "
   1026                     + mCandidatesContainer);
   1027         }
   1028 
   1029         setCandidatesViewShown(true);
   1030 
   1031         if (null != mSkbContainer) mSkbContainer.requestLayout();
   1032 
   1033         if (null == mCandidatesContainer) {
   1034             resetToIdleState(false);
   1035             return;
   1036         }
   1037 
   1038         updateComposingText(showComposingView);
   1039         mCandidatesContainer.showCandidates(mDecInfo,
   1040                 ImeState.STATE_COMPOSING != mImeState);
   1041         mFloatingWindowTimer.postShowFloatingWindow();
   1042     }
   1043 
   1044     private void dismissCandidateWindow() {
   1045         if (mEnvironment.needDebug()) {
   1046             Log.d(TAG, "Candidates window is to be dismissed");
   1047         }
   1048         if (null == mCandidatesContainer) return;
   1049         try {
   1050             mFloatingWindowTimer.cancelShowing();
   1051             mFloatingWindow.dismiss();
   1052         } catch (Exception e) {
   1053             Log.e(TAG, "Fail to show the PopupWindow.");
   1054         }
   1055         setCandidatesViewShown(false);
   1056 
   1057         if (null != mSkbContainer && mSkbContainer.isShown()) {
   1058             mSkbContainer.toggleCandidateMode(false);
   1059         }
   1060     }
   1061 
   1062     private void resetCandidateWindow() {
   1063         if (mEnvironment.needDebug()) {
   1064             Log.d(TAG, "Candidates window is to be reset");
   1065         }
   1066         if (null == mCandidatesContainer) return;
   1067         try {
   1068             mFloatingWindowTimer.cancelShowing();
   1069             mFloatingWindow.dismiss();
   1070         } catch (Exception e) {
   1071             Log.e(TAG, "Fail to show the PopupWindow.");
   1072         }
   1073 
   1074         if (null != mSkbContainer && mSkbContainer.isShown()) {
   1075             mSkbContainer.toggleCandidateMode(false);
   1076         }
   1077 
   1078         mDecInfo.resetCandidates();
   1079 
   1080         if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {
   1081             showCandidateWindow(false);
   1082         }
   1083     }
   1084 
   1085     private void updateIcon(int iconId) {
   1086         if (iconId > 0) {
   1087             showStatusIcon(iconId);
   1088         } else {
   1089             hideStatusIcon();
   1090         }
   1091     }
   1092 
   1093     @Override
   1094     public View onCreateInputView() {
   1095         if (mEnvironment.needDebug()) {
   1096             Log.d(TAG, "onCreateInputView.");
   1097         }
   1098         LayoutInflater inflater = getLayoutInflater();
   1099         mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,
   1100                 null);
   1101         mSkbContainer.setService(this);
   1102         mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
   1103         mSkbContainer.setGestureDetector(mGestureDetectorSkb);
   1104         return mSkbContainer;
   1105     }
   1106 
   1107     @Override
   1108     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
   1109         if (mEnvironment.needDebug()) {
   1110             Log.d(TAG, "onStartInput " + " ccontentType: "
   1111                     + String.valueOf(editorInfo.inputType) + " Restarting:"
   1112                     + String.valueOf(restarting));
   1113         }
   1114         updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));
   1115         resetToIdleState(false);
   1116     }
   1117 
   1118     @Override
   1119     public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
   1120         if (mEnvironment.needDebug()) {
   1121             Log.d(TAG, "onStartInputView " + " contentType: "
   1122                     + String.valueOf(editorInfo.inputType) + " Restarting:"
   1123                     + String.valueOf(restarting));
   1124         }
   1125         updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
   1126         resetToIdleState(false);
   1127         mSkbContainer.updateInputMode();
   1128         setCandidatesViewShown(false);
   1129     }
   1130 
   1131     @Override
   1132     public void onFinishInputView(boolean finishingInput) {
   1133         if (mEnvironment.needDebug()) {
   1134             Log.d(TAG, "onFinishInputView.");
   1135         }
   1136         resetToIdleState(false);
   1137         super.onFinishInputView(finishingInput);
   1138     }
   1139 
   1140     @Override
   1141     public void onFinishInput() {
   1142         if (mEnvironment.needDebug()) {
   1143             Log.d(TAG, "onFinishInput.");
   1144         }
   1145         resetToIdleState(false);
   1146         super.onFinishInput();
   1147     }
   1148 
   1149     @Override
   1150     public void onFinishCandidatesView(boolean finishingInput) {
   1151         if (mEnvironment.needDebug()) {
   1152             Log.d(TAG, "onFinishCandidateView.");
   1153         }
   1154         resetToIdleState(false);
   1155         super.onFinishCandidatesView(finishingInput);
   1156     }
   1157 
   1158     @Override public void onDisplayCompletions(CompletionInfo[] completions) {
   1159         if (!isFullscreenMode()) return;
   1160         if (null == completions || completions.length <= 0) return;
   1161         if (null == mSkbContainer || !mSkbContainer.isShown()) return;
   1162 
   1163         if (!mInputModeSwitcher.isChineseText() ||
   1164                 ImeState.STATE_IDLE == mImeState ||
   1165                 ImeState.STATE_PREDICT == mImeState) {
   1166             mImeState = ImeState.STATE_APP_COMPLETION;
   1167             mDecInfo.prepareAppCompletions(completions);
   1168             showCandidateWindow(false);
   1169         }
   1170     }
   1171 
   1172     private void onChoiceTouched(int activeCandNo) {
   1173         if (mImeState == ImeState.STATE_COMPOSING) {
   1174             changeToStateInput(true);
   1175         } else if (mImeState == ImeState.STATE_INPUT
   1176                 || mImeState == ImeState.STATE_PREDICT) {
   1177             chooseCandidate(activeCandNo);
   1178         } else if (mImeState == ImeState.STATE_APP_COMPLETION) {
   1179             if (null != mDecInfo.mAppCompletions && activeCandNo >= 0 &&
   1180                     activeCandNo < mDecInfo.mAppCompletions.length) {
   1181                 CompletionInfo ci = mDecInfo.mAppCompletions[activeCandNo];
   1182                 if (null != ci) {
   1183                     InputConnection ic = getCurrentInputConnection();
   1184                     ic.commitCompletion(ci);
   1185                 }
   1186             }
   1187             resetToIdleState(false);
   1188         }
   1189     }
   1190 
   1191     @Override
   1192     public void requestHideSelf(int flags) {
   1193         if (mEnvironment.needDebug()) {
   1194             Log.d(TAG, "DimissSoftInput.");
   1195         }
   1196         dismissCandidateWindow();
   1197         if (null != mSkbContainer && mSkbContainer.isShown()) {
   1198             mSkbContainer.dismissPopups();
   1199         }
   1200         super.requestHideSelf(flags);
   1201     }
   1202 
   1203     public void showOptionsMenu() {
   1204         AlertDialog.Builder builder = new AlertDialog.Builder(this);
   1205         builder.setCancelable(true);
   1206         builder.setIcon(R.drawable.app_icon);
   1207         builder.setNegativeButton(android.R.string.cancel, null);
   1208         CharSequence itemSettings = getString(R.string.ime_settings_activity_name);
   1209         CharSequence itemInputMethod = getString(com.android.internal.R.string.inputMethod);
   1210         builder.setItems(new CharSequence[] {itemSettings, itemInputMethod},
   1211                 new DialogInterface.OnClickListener() {
   1212 
   1213                     public void onClick(DialogInterface di, int position) {
   1214                         di.dismiss();
   1215                         switch (position) {
   1216                         case 0:
   1217                             launchSettings();
   1218                             break;
   1219                         case 1:
   1220                             InputMethodManager.getInstance(PinyinIME.this)
   1221                                     .showInputMethodPicker();
   1222                             break;
   1223                         }
   1224                     }
   1225                 });
   1226         builder.setTitle(getString(R.string.ime_name));
   1227         mOptionsDialog = builder.create();
   1228         Window window = mOptionsDialog.getWindow();
   1229         WindowManager.LayoutParams lp = window.getAttributes();
   1230         lp.token = mSkbContainer.getWindowToken();
   1231         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
   1232         window.setAttributes(lp);
   1233         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
   1234         mOptionsDialog.show();
   1235     }
   1236 
   1237     private void launchSettings() {
   1238         Intent intent = new Intent();
   1239         intent.setClass(PinyinIME.this, SettingsActivity.class);
   1240         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1241         startActivity(intent);
   1242     }
   1243 
   1244     private class PopupTimer extends Handler implements Runnable {
   1245         private int mParentLocation[] = new int[2];
   1246 
   1247         void postShowFloatingWindow() {
   1248             mFloatingContainer.measure(LayoutParams.WRAP_CONTENT,
   1249                     LayoutParams.WRAP_CONTENT);
   1250             mFloatingWindow.setWidth(mFloatingContainer.getMeasuredWidth());
   1251             mFloatingWindow.setHeight(mFloatingContainer.getMeasuredHeight());
   1252             post(this);
   1253         }
   1254 
   1255         void cancelShowing() {
   1256             if (mFloatingWindow.isShowing()) {
   1257                 mFloatingWindow.dismiss();
   1258             }
   1259             removeCallbacks(this);
   1260         }
   1261 
   1262         public void run() {
   1263             mCandidatesContainer.getLocationInWindow(mParentLocation);
   1264 
   1265             if (!mFloatingWindow.isShowing()) {
   1266                 mFloatingWindow.showAtLocation(mCandidatesContainer,
   1267                         Gravity.LEFT | Gravity.TOP, mParentLocation[0],
   1268                         mParentLocation[1] -mFloatingWindow.getHeight());
   1269             } else {
   1270                 mFloatingWindow
   1271                 .update(mParentLocation[0],
   1272                         mParentLocation[1] - mFloatingWindow.getHeight(),
   1273                         mFloatingWindow.getWidth(),
   1274                         mFloatingWindow.getHeight());
   1275             }
   1276         }
   1277     }
   1278 
   1279     /**
   1280      * Used to notify IME that the user selects a candidate or performs an
   1281      * gesture.
   1282      */
   1283     public class ChoiceNotifier extends Handler implements
   1284             CandidateViewListener {
   1285         PinyinIME mIme;
   1286 
   1287         ChoiceNotifier(PinyinIME ime) {
   1288             mIme = ime;
   1289         }
   1290 
   1291         public void onClickChoice(int choiceId) {
   1292             if (choiceId >= 0) {
   1293                 mIme.onChoiceTouched(choiceId);
   1294             }
   1295         }
   1296 
   1297         public void onToLeftGesture() {
   1298             if (ImeState.STATE_COMPOSING == mImeState) {
   1299                 changeToStateInput(true);
   1300             }
   1301             mCandidatesContainer.pageForward(true, false);
   1302         }
   1303 
   1304         public void onToRightGesture() {
   1305             if (ImeState.STATE_COMPOSING == mImeState) {
   1306                 changeToStateInput(true);
   1307             }
   1308             mCandidatesContainer.pageBackward(true, false);
   1309         }
   1310 
   1311         public void onToTopGesture() {
   1312         }
   1313 
   1314         public void onToBottomGesture() {
   1315         }
   1316     }
   1317 
   1318     public class OnGestureListener extends
   1319             GestureDetector.SimpleOnGestureListener {
   1320         /**
   1321          * When user presses and drags, the minimum x-distance to make a
   1322          * response to the drag event.
   1323          */
   1324         private static final int MIN_X_FOR_DRAG = 60;
   1325 
   1326         /**
   1327          * When user presses and drags, the minimum y-distance to make a
   1328          * response to the drag event.
   1329          */
   1330         private static final int MIN_Y_FOR_DRAG = 40;
   1331 
   1332         /**
   1333          * Velocity threshold for a screen-move gesture. If the minimum
   1334          * x-velocity is less than it, no gesture.
   1335          */
   1336         static private final float VELOCITY_THRESHOLD_X1 = 0.3f;
   1337 
   1338         /**
   1339          * Velocity threshold for a screen-move gesture. If the maximum
   1340          * x-velocity is less than it, no gesture.
   1341          */
   1342         static private final float VELOCITY_THRESHOLD_X2 = 0.7f;
   1343 
   1344         /**
   1345          * Velocity threshold for a screen-move gesture. If the minimum
   1346          * y-velocity is less than it, no gesture.
   1347          */
   1348         static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;
   1349 
   1350         /**
   1351          * Velocity threshold for a screen-move gesture. If the maximum
   1352          * y-velocity is less than it, no gesture.
   1353          */
   1354         static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;
   1355 
   1356         /** If it false, we will not response detected gestures. */
   1357         private boolean mReponseGestures;
   1358 
   1359         /** The minimum X velocity observed in the gesture. */
   1360         private float mMinVelocityX = Float.MAX_VALUE;
   1361 
   1362         /** The minimum Y velocity observed in the gesture. */
   1363         private float mMinVelocityY = Float.MAX_VALUE;
   1364 
   1365         /** The first down time for the series of touch events for an action. */
   1366         private long mTimeDown;
   1367 
   1368         /** The last time when onScroll() is called. */
   1369         private long mTimeLastOnScroll;
   1370 
   1371         /** This flag used to indicate that this gesture is not a gesture. */
   1372         private boolean mNotGesture;
   1373 
   1374         /** This flag used to indicate that this gesture has been recognized. */
   1375         private boolean mGestureRecognized;
   1376 
   1377         public OnGestureListener(boolean reponseGestures) {
   1378             mReponseGestures = reponseGestures;
   1379         }
   1380 
   1381         @Override
   1382         public boolean onDown(MotionEvent e) {
   1383             mMinVelocityX = Integer.MAX_VALUE;
   1384             mMinVelocityY = Integer.MAX_VALUE;
   1385             mTimeDown = e.getEventTime();
   1386             mTimeLastOnScroll = mTimeDown;
   1387             mNotGesture = false;
   1388             mGestureRecognized = false;
   1389             return false;
   1390         }
   1391 
   1392         @Override
   1393         public boolean onScroll(MotionEvent e1, MotionEvent e2,
   1394                 float distanceX, float distanceY) {
   1395             if (mNotGesture) return false;
   1396             if (mGestureRecognized) return true;
   1397 
   1398             if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG
   1399                     && Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)
   1400                 return false;
   1401 
   1402             long timeNow = e2.getEventTime();
   1403             long spanTotal = timeNow - mTimeDown;
   1404             long spanThis = timeNow - mTimeLastOnScroll;
   1405             if (0 == spanTotal) spanTotal = 1;
   1406             if (0 == spanThis) spanThis = 1;
   1407 
   1408             float vXTotal = (e2.getX() - e1.getX()) / spanTotal;
   1409             float vYTotal = (e2.getY() - e1.getY()) / spanTotal;
   1410 
   1411             // The distances are from the current point to the previous one.
   1412             float vXThis = -distanceX / spanThis;
   1413             float vYThis = -distanceY / spanThis;
   1414 
   1415             float kX = vXTotal * vXThis;
   1416             float kY = vYTotal * vYThis;
   1417             float k1 = kX + kY;
   1418             float k2 = Math.abs(kX) + Math.abs(kY);
   1419 
   1420             if (k1 / k2 < 0.8) {
   1421                 mNotGesture = true;
   1422                 return false;
   1423             }
   1424             float absVXTotal = Math.abs(vXTotal);
   1425             float absVYTotal = Math.abs(vYTotal);
   1426             if (absVXTotal < mMinVelocityX) {
   1427                 mMinVelocityX = absVXTotal;
   1428             }
   1429             if (absVYTotal < mMinVelocityY) {
   1430                 mMinVelocityY = absVYTotal;
   1431             }
   1432 
   1433             if (mMinVelocityX < VELOCITY_THRESHOLD_X1
   1434                     && mMinVelocityY < VELOCITY_THRESHOLD_Y1) {
   1435                 mNotGesture = true;
   1436                 return false;
   1437             }
   1438 
   1439             if (vXTotal > VELOCITY_THRESHOLD_X2
   1440                     && absVYTotal < VELOCITY_THRESHOLD_Y2) {
   1441                 if (mReponseGestures) onDirectionGesture(Gravity.RIGHT);
   1442                 mGestureRecognized = true;
   1443             } else if (vXTotal < -VELOCITY_THRESHOLD_X2
   1444                     && absVYTotal < VELOCITY_THRESHOLD_Y2) {
   1445                 if (mReponseGestures) onDirectionGesture(Gravity.LEFT);
   1446                 mGestureRecognized = true;
   1447             } else if (vYTotal > VELOCITY_THRESHOLD_Y2
   1448                     && absVXTotal < VELOCITY_THRESHOLD_X2) {
   1449                 if (mReponseGestures) onDirectionGesture(Gravity.BOTTOM);
   1450                 mGestureRecognized = true;
   1451             } else if (vYTotal < -VELOCITY_THRESHOLD_Y2
   1452                     && absVXTotal < VELOCITY_THRESHOLD_X2) {
   1453                 if (mReponseGestures) onDirectionGesture(Gravity.TOP);
   1454                 mGestureRecognized = true;
   1455             }
   1456 
   1457             mTimeLastOnScroll = timeNow;
   1458             return mGestureRecognized;
   1459         }
   1460 
   1461         @Override
   1462         public boolean onFling(MotionEvent me1, MotionEvent me2,
   1463                 float velocityX, float velocityY) {
   1464             return mGestureRecognized;
   1465         }
   1466 
   1467         public void onDirectionGesture(int gravity) {
   1468             if (Gravity.NO_GRAVITY == gravity) {
   1469                 return;
   1470             }
   1471 
   1472             if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {
   1473                 if (mCandidatesContainer.isShown()) {
   1474                     if (Gravity.LEFT == gravity) {
   1475                         mCandidatesContainer.pageForward(true, true);
   1476                     } else {
   1477                         mCandidatesContainer.pageBackward(true, true);
   1478                     }
   1479                     return;
   1480                 }
   1481             }
   1482         }
   1483     }
   1484 
   1485     /**
   1486      * Connection used for binding to the Pinyin decoding service.
   1487      */
   1488     public class PinyinDecoderServiceConnection implements ServiceConnection {
   1489         public void onServiceConnected(ComponentName name, IBinder service) {
   1490             mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub
   1491                     .asInterface(service);
   1492         }
   1493 
   1494         public void onServiceDisconnected(ComponentName name) {
   1495         }
   1496     }
   1497 
   1498     public enum ImeState {
   1499         STATE_BYPASS, STATE_IDLE, STATE_INPUT, STATE_COMPOSING, STATE_PREDICT,
   1500         STATE_APP_COMPLETION
   1501     }
   1502 
   1503     public class DecodingInfo {
   1504         /**
   1505          * Maximum length of the Pinyin string
   1506          */
   1507         private static final int PY_STRING_MAX = 28;
   1508 
   1509         /**
   1510          * Maximum number of candidates to display in one page.
   1511          */
   1512         private static final int MAX_PAGE_SIZE_DISPLAY = 10;
   1513 
   1514         /**
   1515          * Spelling (Pinyin) string.
   1516          */
   1517         private StringBuffer mSurface;
   1518 
   1519         /**
   1520          * Byte buffer used as the Pinyin string parameter for native function
   1521          * call.
   1522          */
   1523         private byte mPyBuf[];
   1524 
   1525         /**
   1526          * The length of surface string successfully decoded by engine.
   1527          */
   1528         private int mSurfaceDecodedLen;
   1529 
   1530         /**
   1531          * Composing string.
   1532          */
   1533         private String mComposingStr;
   1534 
   1535         /**
   1536          * Length of the active composing string.
   1537          */
   1538         private int mActiveCmpsLen;
   1539 
   1540         /**
   1541          * Composing string for display, it is copied from mComposingStr, and
   1542          * add spaces between spellings.
   1543          **/
   1544         private String mComposingStrDisplay;
   1545 
   1546         /**
   1547          * Length of the active composing string for display.
   1548          */
   1549         private int mActiveCmpsDisplayLen;
   1550 
   1551         /**
   1552          * The first full sentence choice.
   1553          */
   1554         private String mFullSent;
   1555 
   1556         /**
   1557          * Number of characters which have been fixed.
   1558          */
   1559         private int mFixedLen;
   1560 
   1561         /**
   1562          * If this flag is true, selection is finished.
   1563          */
   1564         private boolean mFinishSelection;
   1565 
   1566         /**
   1567          * The starting position for each spelling. The first one is the number
   1568          * of the real starting position elements.
   1569          */
   1570         private int mSplStart[];
   1571 
   1572         /**
   1573          * Editing cursor in mSurface.
   1574          */
   1575         private int mCursorPos;
   1576 
   1577         /**
   1578          * Remote Pinyin-to-Hanzi decoding engine service.
   1579          */
   1580         private IPinyinDecoderService mIPinyinDecoderService;
   1581 
   1582         /**
   1583          * The complication information suggested by application.
   1584          */
   1585         private CompletionInfo[] mAppCompletions;
   1586 
   1587         /**
   1588          * The total number of choices for display. The list may only contains
   1589          * the first part. If user tries to navigate to next page which is not
   1590          * in the result list, we need to get these items.
   1591          **/
   1592         public int mTotalChoicesNum;
   1593 
   1594         /**
   1595          * Candidate list. The first one is the full-sentence candidate.
   1596          */
   1597         public List<String> mCandidatesList = new Vector<String>();
   1598 
   1599         /**
   1600          * Element i stores the starting position of page i.
   1601          */
   1602         public Vector<Integer> mPageStart = new Vector<Integer>();
   1603 
   1604         /**
   1605          * Element i stores the number of characters to page i.
   1606          */
   1607         public Vector<Integer> mCnToPage = new Vector<Integer>();
   1608 
   1609         /**
   1610          * The position to delete in Pinyin string. If it is less than 0, IME
   1611          * will do an incremental search, otherwise IME will do a deletion
   1612          * operation. if {@link #mIsPosInSpl} is true, IME will delete the whole
   1613          * string for mPosDelSpl-th spelling, otherwise it will only delete
   1614          * mPosDelSpl-th character in the Pinyin string.
   1615          */
   1616         public int mPosDelSpl = -1;
   1617 
   1618         /**
   1619          * If {@link #mPosDelSpl} is big than or equal to 0, this member is used
   1620          * to indicate that whether the postion is counted in spelling id or
   1621          * character.
   1622          */
   1623         public boolean mIsPosInSpl;
   1624 
   1625         public DecodingInfo() {
   1626             mSurface = new StringBuffer();
   1627             mSurfaceDecodedLen = 0;
   1628         }
   1629 
   1630         public void reset() {
   1631             mSurface.delete(0, mSurface.length());
   1632             mSurfaceDecodedLen = 0;
   1633             mCursorPos = 0;
   1634             mFullSent = "";
   1635             mFixedLen = 0;
   1636             mFinishSelection = false;
   1637             mComposingStr = "";
   1638             mComposingStrDisplay = "";
   1639             mActiveCmpsLen = 0;
   1640             mActiveCmpsDisplayLen = 0;
   1641 
   1642             resetCandidates();
   1643         }
   1644 
   1645         public boolean isCandidatesListEmpty() {
   1646             return mCandidatesList.size() == 0;
   1647         }
   1648 
   1649         public boolean isSplStrFull() {
   1650             if (mSurface.length() >= PY_STRING_MAX - 1) return true;
   1651             return false;
   1652         }
   1653 
   1654         public void addSplChar(char ch, boolean reset) {
   1655             if (reset) {
   1656                 mSurface.delete(0, mSurface.length());
   1657                 mSurfaceDecodedLen = 0;
   1658                 mCursorPos = 0;
   1659                 try {
   1660                     mIPinyinDecoderService.imResetSearch();
   1661                 } catch (RemoteException e) {
   1662                 }
   1663             }
   1664             mSurface.insert(mCursorPos, ch);
   1665             mCursorPos++;
   1666         }
   1667 
   1668         // Prepare to delete before cursor. We may delete a spelling char if
   1669         // the cursor is in the range of unfixed part, delete a whole spelling
   1670         // if the cursor in inside the range of the fixed part.
   1671         // This function only marks the position used to delete.
   1672         public void prepareDeleteBeforeCursor() {
   1673             if (mCursorPos > 0) {
   1674                 int pos;
   1675                 for (pos = 0; pos < mFixedLen; pos++) {
   1676                     if (mSplStart[pos + 2] >= mCursorPos
   1677                             && mSplStart[pos + 1] < mCursorPos) {
   1678                         mPosDelSpl = pos;
   1679                         mCursorPos = mSplStart[pos + 1];
   1680                         mIsPosInSpl = true;
   1681                         break;
   1682                     }
   1683                 }
   1684                 if (mPosDelSpl < 0) {
   1685                     mPosDelSpl = mCursorPos - 1;
   1686                     mCursorPos--;
   1687                     mIsPosInSpl = false;
   1688                 }
   1689             }
   1690         }
   1691 
   1692         public int length() {
   1693             return mSurface.length();
   1694         }
   1695 
   1696         public char charAt(int index) {
   1697             return mSurface.charAt(index);
   1698         }
   1699 
   1700         public StringBuffer getOrigianlSplStr() {
   1701             return mSurface;
   1702         }
   1703 
   1704         public int getSplStrDecodedLen() {
   1705             return mSurfaceDecodedLen;
   1706         }
   1707 
   1708         public int[] getSplStart() {
   1709             return mSplStart;
   1710         }
   1711 
   1712         public String getComposingStr() {
   1713             return mComposingStr;
   1714         }
   1715 
   1716         public String getComposingStrActivePart() {
   1717             assert (mActiveCmpsLen <= mComposingStr.length());
   1718             return mComposingStr.substring(0, mActiveCmpsLen);
   1719         }
   1720 
   1721         public int getActiveCmpsLen() {
   1722             return mActiveCmpsLen;
   1723         }
   1724 
   1725         public String getComposingStrForDisplay() {
   1726             return mComposingStrDisplay;
   1727         }
   1728 
   1729         public int getActiveCmpsDisplayLen() {
   1730             return mActiveCmpsDisplayLen;
   1731         }
   1732 
   1733         public String getFullSent() {
   1734             return mFullSent;
   1735         }
   1736 
   1737         public String getCurrentFullSent(int activeCandPos) {
   1738             try {
   1739                 String retStr = mFullSent.substring(0, mFixedLen);
   1740                 retStr += mCandidatesList.get(activeCandPos);
   1741                 return retStr;
   1742             } catch (Exception e) {
   1743                 return "";
   1744             }
   1745         }
   1746 
   1747         public void resetCandidates() {
   1748             mCandidatesList.clear();
   1749             mTotalChoicesNum = 0;
   1750 
   1751             mPageStart.clear();
   1752             mPageStart.add(0);
   1753             mCnToPage.clear();
   1754             mCnToPage.add(0);
   1755         }
   1756 
   1757         public boolean candidatesFromApp() {
   1758             return ImeState.STATE_APP_COMPLETION == mImeState;
   1759         }
   1760 
   1761         public boolean canDoPrediction() {
   1762             return mComposingStr.length() == mFixedLen;
   1763         }
   1764 
   1765         public boolean selectionFinished() {
   1766             return mFinishSelection;
   1767         }
   1768 
   1769         // After the user chooses a candidate, input method will do a
   1770         // re-decoding and give the new candidate list.
   1771         // If candidate id is less than 0, means user is inputting Pinyin,
   1772         // not selecting any choice.
   1773         private void chooseDecodingCandidate(int candId) {
   1774             if (mImeState != ImeState.STATE_PREDICT) {
   1775                 resetCandidates();
   1776                 int totalChoicesNum = 0;
   1777                 try {
   1778                     if (candId < 0) {
   1779                         if (length() == 0) {
   1780                             totalChoicesNum = 0;
   1781                         } else {
   1782                             if (mPyBuf == null)
   1783                                 mPyBuf = new byte[PY_STRING_MAX];
   1784                             for (int i = 0; i < length(); i++)
   1785                                 mPyBuf[i] = (byte) charAt(i);
   1786                             mPyBuf[length()] = 0;
   1787 
   1788                             if (mPosDelSpl < 0) {
   1789                                 totalChoicesNum = mIPinyinDecoderService
   1790                                         .imSearch(mPyBuf, length());
   1791                             } else {
   1792                                 boolean clear_fixed_this_step = true;
   1793                                 if (ImeState.STATE_COMPOSING == mImeState) {
   1794                                     clear_fixed_this_step = false;
   1795                                 }
   1796                                 totalChoicesNum = mIPinyinDecoderService
   1797                                         .imDelSearch(mPosDelSpl, mIsPosInSpl,
   1798                                                 clear_fixed_this_step);
   1799                                 mPosDelSpl = -1;
   1800                             }
   1801                         }
   1802                     } else {
   1803                         totalChoicesNum = mIPinyinDecoderService
   1804                                 .imChoose(candId);
   1805                     }
   1806                 } catch (RemoteException e) {
   1807                 }
   1808                 updateDecInfoForSearch(totalChoicesNum);
   1809             }
   1810         }
   1811 
   1812         private void updateDecInfoForSearch(int totalChoicesNum) {
   1813             mTotalChoicesNum = totalChoicesNum;
   1814             if (mTotalChoicesNum < 0) {
   1815                 mTotalChoicesNum = 0;
   1816                 return;
   1817             }
   1818 
   1819             try {
   1820                 String pyStr;
   1821 
   1822                 mSplStart = mIPinyinDecoderService.imGetSplStart();
   1823                 pyStr = mIPinyinDecoderService.imGetPyStr(false);
   1824                 mSurfaceDecodedLen = mIPinyinDecoderService.imGetPyStrLen(true);
   1825                 assert (mSurfaceDecodedLen <= pyStr.length());
   1826 
   1827                 mFullSent = mIPinyinDecoderService.imGetChoice(0);
   1828                 mFixedLen = mIPinyinDecoderService.imGetFixedLen();
   1829 
   1830                 // Update the surface string to the one kept by engine.
   1831                 mSurface.replace(0, mSurface.length(), pyStr);
   1832 
   1833                 if (mCursorPos > mSurface.length())
   1834                     mCursorPos = mSurface.length();
   1835                 mComposingStr = mFullSent.substring(0, mFixedLen)
   1836                         + mSurface.substring(mSplStart[mFixedLen + 1]);
   1837 
   1838                 mActiveCmpsLen = mComposingStr.length();
   1839                 if (mSurfaceDecodedLen > 0) {
   1840                     mActiveCmpsLen = mActiveCmpsLen
   1841                             - (mSurface.length() - mSurfaceDecodedLen);
   1842                 }
   1843 
   1844                 // Prepare the display string.
   1845                 if (0 == mSurfaceDecodedLen) {
   1846                     mComposingStrDisplay = mComposingStr;
   1847                     mActiveCmpsDisplayLen = mComposingStr.length();
   1848                 } else {
   1849                     mComposingStrDisplay = mFullSent.substring(0, mFixedLen);
   1850                     for (int pos = mFixedLen + 1; pos < mSplStart.length - 1; pos++) {
   1851                         mComposingStrDisplay += mSurface.substring(
   1852                                 mSplStart[pos], mSplStart[pos + 1]);
   1853                         if (mSplStart[pos + 1] < mSurfaceDecodedLen) {
   1854                             mComposingStrDisplay += " ";
   1855                         }
   1856                     }
   1857                     mActiveCmpsDisplayLen = mComposingStrDisplay.length();
   1858                     if (mSurfaceDecodedLen < mSurface.length()) {
   1859                         mComposingStrDisplay += mSurface
   1860                                 .substring(mSurfaceDecodedLen);
   1861                     }
   1862                 }
   1863 
   1864                 if (mSplStart.length == mFixedLen + 2) {
   1865                     mFinishSelection = true;
   1866                 } else {
   1867                     mFinishSelection = false;
   1868                 }
   1869             } catch (RemoteException e) {
   1870                 Log.w(TAG, "PinyinDecoderService died", e);
   1871             } catch (Exception e) {
   1872                 mTotalChoicesNum = 0;
   1873                 mComposingStr = "";
   1874             }
   1875             // Prepare page 0.
   1876             if (!mFinishSelection) {
   1877                 preparePage(0);
   1878             }
   1879         }
   1880 
   1881         private void choosePredictChoice(int choiceId) {
   1882             if (ImeState.STATE_PREDICT != mImeState || choiceId < 0
   1883                     || choiceId >= mTotalChoicesNum) {
   1884                 return;
   1885             }
   1886 
   1887             String tmp = mCandidatesList.get(choiceId);
   1888 
   1889             resetCandidates();
   1890 
   1891             mCandidatesList.add(tmp);
   1892             mTotalChoicesNum = 1;
   1893 
   1894             mSurface.replace(0, mSurface.length(), "");
   1895             mCursorPos = 0;
   1896             mFullSent = tmp;
   1897             mFixedLen = tmp.length();
   1898             mComposingStr = mFullSent;
   1899             mActiveCmpsLen = mFixedLen;
   1900 
   1901             mFinishSelection = true;
   1902         }
   1903 
   1904         public String getCandidate(int candId) {
   1905             // Only loaded items can be gotten, so we use mCandidatesList.size()
   1906             // instead mTotalChoiceNum.
   1907             if (candId < 0 || candId > mCandidatesList.size()) {
   1908                 return null;
   1909             }
   1910             return mCandidatesList.get(candId);
   1911         }
   1912 
   1913         private void getCandiagtesForCache() {
   1914             int fetchStart = mCandidatesList.size();
   1915             int fetchSize = mTotalChoicesNum - fetchStart;
   1916             if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {
   1917                 fetchSize = MAX_PAGE_SIZE_DISPLAY;
   1918             }
   1919             try {
   1920                 List<String> newList = null;
   1921                 if (ImeState.STATE_INPUT == mImeState ||
   1922                         ImeState.STATE_IDLE == mImeState ||
   1923                         ImeState.STATE_COMPOSING == mImeState){
   1924                     newList = mIPinyinDecoderService.imGetChoiceList(
   1925                             fetchStart, fetchSize, mFixedLen);
   1926                 } else if (ImeState.STATE_PREDICT == mImeState) {
   1927                     newList = mIPinyinDecoderService.imGetPredictList(
   1928                             fetchStart, fetchSize);
   1929                 } else if (ImeState.STATE_APP_COMPLETION == mImeState) {
   1930                     newList = new ArrayList<String>();
   1931                     if (null != mAppCompletions) {
   1932                         for (int pos = fetchStart; pos < fetchSize; pos++) {
   1933                             CompletionInfo ci = mAppCompletions[pos];
   1934                             if (null != ci) {
   1935                                 CharSequence s = ci.getText();
   1936                                 if (null != s) newList.add(s.toString());
   1937                             }
   1938                         }
   1939                     }
   1940                 }
   1941                 mCandidatesList.addAll(newList);
   1942             } catch (RemoteException e) {
   1943                 Log.w(TAG, "PinyinDecoderService died", e);
   1944             }
   1945         }
   1946 
   1947         public boolean pageReady(int pageNo) {
   1948             // If the page number is less than 0, return false
   1949             if (pageNo < 0) return false;
   1950 
   1951             // Page pageNo's ending information is not ready.
   1952             if (mPageStart.size() <= pageNo + 1) {
   1953                 return false;
   1954             }
   1955 
   1956             return true;
   1957         }
   1958 
   1959         public boolean preparePage(int pageNo) {
   1960             // If the page number is less than 0, return false
   1961             if (pageNo < 0) return false;
   1962 
   1963             // Make sure the starting information for page pageNo is ready.
   1964             if (mPageStart.size() <= pageNo) {
   1965                 return false;
   1966             }
   1967 
   1968             // Page pageNo's ending information is also ready.
   1969             if (mPageStart.size() > pageNo + 1) {
   1970                 return true;
   1971             }
   1972 
   1973             // If cached items is enough for page pageNo.
   1974             if (mCandidatesList.size() - mPageStart.elementAt(pageNo) >= MAX_PAGE_SIZE_DISPLAY) {
   1975                 return true;
   1976             }
   1977 
   1978             // Try to get more items from engine
   1979             getCandiagtesForCache();
   1980 
   1981             // Try to find if there are available new items to display.
   1982             // If no new item, return false;
   1983             if (mPageStart.elementAt(pageNo) >= mCandidatesList.size()) {
   1984                 return false;
   1985             }
   1986 
   1987             // If there are new items, return true;
   1988             return true;
   1989         }
   1990 
   1991         public void preparePredicts(CharSequence history) {
   1992             if (null == history) return;
   1993 
   1994             resetCandidates();
   1995 
   1996             if (Settings.getPrediction()) {
   1997                 String preEdit = history.toString();
   1998                 int predictNum = 0;
   1999                 if (null != preEdit) {
   2000                     try {
   2001                         mTotalChoicesNum = mIPinyinDecoderService
   2002                                 .imGetPredictsNum(preEdit);
   2003                     } catch (RemoteException e) {
   2004                         return;
   2005                     }
   2006                 }
   2007             }
   2008 
   2009             preparePage(0);
   2010             mFinishSelection = false;
   2011         }
   2012 
   2013         private void prepareAppCompletions(CompletionInfo completions[]) {
   2014             resetCandidates();
   2015             mAppCompletions = completions;
   2016             mTotalChoicesNum = completions.length;
   2017             preparePage(0);
   2018             mFinishSelection = false;
   2019             return;
   2020         }
   2021 
   2022         public int getCurrentPageSize(int currentPage) {
   2023             if (mPageStart.size() <= currentPage + 1) return 0;
   2024             return mPageStart.elementAt(currentPage + 1)
   2025                     - mPageStart.elementAt(currentPage);
   2026         }
   2027 
   2028         public int getCurrentPageStart(int currentPage) {
   2029             if (mPageStart.size() < currentPage + 1) return mTotalChoicesNum;
   2030             return mPageStart.elementAt(currentPage);
   2031         }
   2032 
   2033         public boolean pageForwardable(int currentPage) {
   2034             if (mPageStart.size() <= currentPage + 1) return false;
   2035             if (mPageStart.elementAt(currentPage + 1) >= mTotalChoicesNum) {
   2036                 return false;
   2037             }
   2038             return true;
   2039         }
   2040 
   2041         public boolean pageBackwardable(int currentPage) {
   2042             if (currentPage > 0) return true;
   2043             return false;
   2044         }
   2045 
   2046         public boolean charBeforeCursorIsSeparator() {
   2047             int len = mSurface.length();
   2048             if (mCursorPos > len) return false;
   2049             if (mCursorPos > 0 && mSurface.charAt(mCursorPos - 1) == '\'') {
   2050                 return true;
   2051             }
   2052             return false;
   2053         }
   2054 
   2055         public int getCursorPos() {
   2056             return mCursorPos;
   2057         }
   2058 
   2059         public int getCursorPosInCmps() {
   2060             int cursorPos = mCursorPos;
   2061             int fixedLen = 0;
   2062 
   2063             for (int hzPos = 0; hzPos < mFixedLen; hzPos++) {
   2064                 if (mCursorPos >= mSplStart[hzPos + 2]) {
   2065                     cursorPos -= mSplStart[hzPos + 2] - mSplStart[hzPos + 1];
   2066                     cursorPos += 1;
   2067                 }
   2068             }
   2069             return cursorPos;
   2070         }
   2071 
   2072         public int getCursorPosInCmpsDisplay() {
   2073             int cursorPos = getCursorPosInCmps();
   2074             // +2 is because: one for mSplStart[0], which is used for other
   2075             // purpose(The length of the segmentation string), and another
   2076             // for the first spelling which does not need a space before it.
   2077             for (int pos = mFixedLen + 2; pos < mSplStart.length - 1; pos++) {
   2078                 if (mCursorPos <= mSplStart[pos]) {
   2079                     break;
   2080                 } else {
   2081                     cursorPos++;
   2082                 }
   2083             }
   2084             return cursorPos;
   2085         }
   2086 
   2087         public void moveCursorToEdge(boolean left) {
   2088             if (left)
   2089                 mCursorPos = 0;
   2090             else
   2091                 mCursorPos = mSurface.length();
   2092         }
   2093 
   2094         // Move cursor. If offset is 0, this function can be used to adjust
   2095         // the cursor into the bounds of the string.
   2096         public void moveCursor(int offset) {
   2097             if (offset > 1 || offset < -1) return;
   2098 
   2099             if (offset != 0) {
   2100                 int hzPos = 0;
   2101                 for (hzPos = 0; hzPos <= mFixedLen; hzPos++) {
   2102                     if (mCursorPos == mSplStart[hzPos + 1]) {
   2103                         if (offset < 0) {
   2104                             if (hzPos > 0) {
   2105                                 offset = mSplStart[hzPos]
   2106                                         - mSplStart[hzPos + 1];
   2107                             }
   2108                         } else {
   2109                             if (hzPos < mFixedLen) {
   2110                                 offset = mSplStart[hzPos + 2]
   2111                                         - mSplStart[hzPos + 1];
   2112                             }
   2113                         }
   2114                         break;
   2115                     }
   2116                 }
   2117             }
   2118             mCursorPos += offset;
   2119             if (mCursorPos < 0) {
   2120                 mCursorPos = 0;
   2121             } else if (mCursorPos > mSurface.length()) {
   2122                 mCursorPos = mSurface.length();
   2123             }
   2124         }
   2125 
   2126         public int getSplNum() {
   2127             return mSplStart[0];
   2128         }
   2129 
   2130         public int getFixedLen() {
   2131             return mFixedLen;
   2132         }
   2133     }
   2134 }
   2135