• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate()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
onDestroy()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
onConfigurationChanged(Configuration newConfig)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
onKeyDown(int keyCode, KeyEvent event)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
onKeyUp(int keyCode, KeyEvent event)243     public boolean onKeyUp(int keyCode, KeyEvent event) {
244         if (processKey(event, true)) return true;
245         return super.onKeyUp(keyCode, event);
246     }
247 
processKey(KeyEvent event, boolean realAction)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.
processFunctionKeys(int keyCode, boolean realAction)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 
processStateIdle(int keyChar, int keyCode, KeyEvent event, boolean realAction)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 
processStateInput(int keyChar, int keyCode, KeyEvent event, boolean realAction)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 
processStatePredict(int keyChar, int keyCode, KeyEvent event, boolean realAction)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 
processStateEditComposing(int keyChar, int keyCode, KeyEvent event, boolean realAction)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 
tryInputRawUnicode(String str)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 
processSurfaceChange(int keyChar, int keyCode)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 
changeToStateComposing(boolean updateUi)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 
changeToStateInput(boolean updateUi)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 
simulateKeyEventDownUp(int keyCode)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 
commitResultText(String resultText)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 
updateComposingText(boolean visible)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 
inputCommaPeriod(String preEdit, int keyChar, boolean dismissCandWindow, ImeState nextState)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 
resetToIdleState(boolean resetInlineText)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 
chooseAndUpdate(int candId)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.
chooseCandidate(int 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 
startPinyinDecoderService()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
onCreateCandidatesView()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 
responseSoftKeyEvent(SoftKey sKey)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 
showCandidateWindow(boolean showComposingView)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 
dismissCandidateWindow()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 
resetCandidateWindow()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 
updateIcon(int iconId)1085     private void updateIcon(int iconId) {
1086         if (iconId > 0) {
1087             showStatusIcon(iconId);
1088         } else {
1089             hideStatusIcon();
1090         }
1091     }
1092 
1093     @Override
onCreateInputView()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
onStartInput(EditorInfo editorInfo, boolean restarting)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
onStartInputView(EditorInfo editorInfo, boolean restarting)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
onFinishInputView(boolean finishingInput)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
onFinishInput()1141     public void onFinishInput() {
1142         if (mEnvironment.needDebug()) {
1143             Log.d(TAG, "onFinishInput.");
1144         }
1145         resetToIdleState(false);
1146         super.onFinishInput();
1147     }
1148 
1149     @Override
onFinishCandidatesView(boolean finishingInput)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 
onDisplayCompletions(CompletionInfo[] completions)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 
onChoiceTouched(int activeCandNo)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
requestHideSelf(int flags)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 
showOptionsMenu()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 
launchSettings()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 
postShowFloatingWindow()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 
cancelShowing()1255         void cancelShowing() {
1256             if (mFloatingWindow.isShowing()) {
1257                 mFloatingWindow.dismiss();
1258             }
1259             removeCallbacks(this);
1260         }
1261 
run()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 
ChoiceNotifier(PinyinIME ime)1287         ChoiceNotifier(PinyinIME ime) {
1288             mIme = ime;
1289         }
1290 
onClickChoice(int choiceId)1291         public void onClickChoice(int choiceId) {
1292             if (choiceId >= 0) {
1293                 mIme.onChoiceTouched(choiceId);
1294             }
1295         }
1296 
onToLeftGesture()1297         public void onToLeftGesture() {
1298             if (ImeState.STATE_COMPOSING == mImeState) {
1299                 changeToStateInput(true);
1300             }
1301             mCandidatesContainer.pageForward(true, false);
1302         }
1303 
onToRightGesture()1304         public void onToRightGesture() {
1305             if (ImeState.STATE_COMPOSING == mImeState) {
1306                 changeToStateInput(true);
1307             }
1308             mCandidatesContainer.pageBackward(true, false);
1309         }
1310 
onToTopGesture()1311         public void onToTopGesture() {
1312         }
1313 
onToBottomGesture()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 
OnGestureListener(boolean reponseGestures)1377         public OnGestureListener(boolean reponseGestures) {
1378             mReponseGestures = reponseGestures;
1379         }
1380 
1381         @Override
onDown(MotionEvent e)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
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)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
onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY)1462         public boolean onFling(MotionEvent me1, MotionEvent me2,
1463                 float velocityX, float velocityY) {
1464             return mGestureRecognized;
1465         }
1466 
onDirectionGesture(int gravity)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 {
onServiceConnected(ComponentName name, IBinder service)1489         public void onServiceConnected(ComponentName name, IBinder service) {
1490             mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub
1491                     .asInterface(service);
1492         }
1493 
onServiceDisconnected(ComponentName name)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 
DecodingInfo()1625         public DecodingInfo() {
1626             mSurface = new StringBuffer();
1627             mSurfaceDecodedLen = 0;
1628         }
1629 
reset()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 
isCandidatesListEmpty()1645         public boolean isCandidatesListEmpty() {
1646             return mCandidatesList.size() == 0;
1647         }
1648 
isSplStrFull()1649         public boolean isSplStrFull() {
1650             if (mSurface.length() >= PY_STRING_MAX - 1) return true;
1651             return false;
1652         }
1653 
addSplChar(char ch, boolean reset)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.
prepareDeleteBeforeCursor()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 
length()1692         public int length() {
1693             return mSurface.length();
1694         }
1695 
charAt(int index)1696         public char charAt(int index) {
1697             return mSurface.charAt(index);
1698         }
1699 
getOrigianlSplStr()1700         public StringBuffer getOrigianlSplStr() {
1701             return mSurface;
1702         }
1703 
getSplStrDecodedLen()1704         public int getSplStrDecodedLen() {
1705             return mSurfaceDecodedLen;
1706         }
1707 
getSplStart()1708         public int[] getSplStart() {
1709             return mSplStart;
1710         }
1711 
getComposingStr()1712         public String getComposingStr() {
1713             return mComposingStr;
1714         }
1715 
getComposingStrActivePart()1716         public String getComposingStrActivePart() {
1717             assert (mActiveCmpsLen <= mComposingStr.length());
1718             return mComposingStr.substring(0, mActiveCmpsLen);
1719         }
1720 
getActiveCmpsLen()1721         public int getActiveCmpsLen() {
1722             return mActiveCmpsLen;
1723         }
1724 
getComposingStrForDisplay()1725         public String getComposingStrForDisplay() {
1726             return mComposingStrDisplay;
1727         }
1728 
getActiveCmpsDisplayLen()1729         public int getActiveCmpsDisplayLen() {
1730             return mActiveCmpsDisplayLen;
1731         }
1732 
getFullSent()1733         public String getFullSent() {
1734             return mFullSent;
1735         }
1736 
getCurrentFullSent(int activeCandPos)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 
resetCandidates()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 
candidatesFromApp()1757         public boolean candidatesFromApp() {
1758             return ImeState.STATE_APP_COMPLETION == mImeState;
1759         }
1760 
canDoPrediction()1761         public boolean canDoPrediction() {
1762             return mComposingStr.length() == mFixedLen;
1763         }
1764 
selectionFinished()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.
chooseDecodingCandidate(int candId)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 
updateDecInfoForSearch(int totalChoicesNum)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 
choosePredictChoice(int choiceId)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 
getCandidate(int candId)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 
getCandiagtesForCache()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 
pageReady(int pageNo)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 
preparePage(int pageNo)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 
preparePredicts(CharSequence history)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 
prepareAppCompletions(CompletionInfo completions[])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 
getCurrentPageSize(int currentPage)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 
getCurrentPageStart(int currentPage)2028         public int getCurrentPageStart(int currentPage) {
2029             if (mPageStart.size() < currentPage + 1) return mTotalChoicesNum;
2030             return mPageStart.elementAt(currentPage);
2031         }
2032 
pageForwardable(int currentPage)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 
pageBackwardable(int currentPage)2041         public boolean pageBackwardable(int currentPage) {
2042             if (currentPage > 0) return true;
2043             return false;
2044         }
2045 
charBeforeCursorIsSeparator()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 
getCursorPos()2055         public int getCursorPos() {
2056             return mCursorPos;
2057         }
2058 
getCursorPosInCmps()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 
getCursorPosInCmpsDisplay()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 
moveCursorToEdge(boolean left)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.
moveCursor(int offset)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 
getSplNum()2126         public int getSplNum() {
2127             return mSplStart[0];
2128         }
2129 
getFixedLen()2130         public int getFixedLen() {
2131             return mFixedLen;
2132         }
2133     }
2134 }
2135