package org.nyanya.android.traditionalt9; import android.content.Intent; import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.inputmethodservice.KeyboardView; import android.os.Handler; import android.text.InputType; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.Toast; import org.nyanya.android.traditionalt9.LangHelper.LANGUAGE; import org.nyanya.android.traditionalt9.T9DB.DBSettings.SETTING; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; public class TraditionalT9 extends InputMethodService implements KeyboardView.OnKeyboardActionListener { private CandidateView mCandidateView; private InterfaceHandler interfacehandler = null; private StringBuilder mComposing = new StringBuilder(); private StringBuilder mComposingI = new StringBuilder(); private ArrayList mSuggestionStrings = new ArrayList(10); private ArrayList mSuggestionInts = new ArrayList(10); private AbstractList mSuggestionSym = new ArrayList(16); private static final int NON_EDIT = 0; private static final int EDITING = 1; private static final int EDITING_NOSHOW = 2; private int mEditing = NON_EDIT; private boolean mGaveUpdateWarn = false; private boolean mFirstPress = false; private boolean mIgnoreDPADKeyUp = false; private KeyEvent mDPADkeyEvent = null; protected boolean mWordFound = true; private AbsSymDialog mSymbolPopup = null; private AbsSymDialog mSmileyPopup = null; protected boolean mAddingWord = false; // private boolean mAddingSkipInput = false; private int mPrevious; private int mCharIndex; private String mPreviousWord = ""; private int mCapsMode; private LANGUAGE mLang; private int mLangIndex; private LANGUAGE[] mLangsAvailable = null; private static final int CAPS_OFF = 0; private static final int CAPS_SINGLE = 1; private static final int CAPS_ALL = 2; private final static int[] CAPS_CYCLE = { CAPS_OFF, CAPS_SINGLE, CAPS_ALL }; private final static int T9DELAY = 900; final Handler t9releasehandler = new Handler(); Runnable mt9release = new Runnable() { @Override public void run() { commitReset(); } }; private T9DB db; public static final int MODE_LANG = 0; public static final int MODE_TEXT = 1; public static final int MODE_NUM = 2; private static final int[] MODE_CYCLE = { MODE_LANG, MODE_TEXT, MODE_NUM }; private int mKeyMode; private Toast modeNotification = null; /** * Main initialization of the input method component. Be sure to call to * super class. */ @Override public void onCreate() { super.onCreate(); mPrevious = -1; mCharIndex = 0; db = T9DB.getInstance(this); if (interfacehandler == null) { interfacehandler = new InterfaceHandler(getLayoutInflater().inflate(R.layout.mainview, null), this); } } @Override public boolean onEvaluateInputViewShown() { //Log.d("T9.onEvaluateInputViewShown", "whatis"); //Log.d("T9.onEval", "fullscreen?: " + isFullscreenMode() + " isshow?: " + isInputViewShown() + " isrequestedshow?: " + isShowInputRequested()); if (mEditing == EDITING_NOSHOW) { return false; } // TODO: Verify if need this: // if (interfacehandler != null) { // interfacehandler.showView(); // } return true; } /** * Called by the framework when your view for creating input needs to be * generated. This will be called the first time your input method is * displayed, and every time it needs to be re-created such as due to a * configuration change. */ @Override public View onCreateInputView() { updateKeyMode(); View v = getLayoutInflater().inflate(R.layout.mainview, null); interfacehandler.changeView(v); if (mKeyMode == MODE_LANG) { interfacehandler.showHold(true); } else { interfacehandler.showHold(false); } return v; } /** * Called by the framework when your view for showing candidates needs to be * generated, like {@link #onCreateInputView}. */ @Override public View onCreateCandidatesView() { mCandidateView = new CandidateView(this); return mCandidateView; } protected void showSymbolPage() { if (mSymbolPopup == null) { mSymbolPopup = new SymbolDialog(this, getLayoutInflater().inflate(R.layout.symbolview, null)); } mSymbolPopup.doShow(getWindow().getWindow().getDecorView()); } protected void showSmileyPage() { if (mSmileyPopup == null) { mSmileyPopup = new SmileyDialog(this, getLayoutInflater().inflate(R.layout.symbolview, null)); } mSmileyPopup.doShow(getWindow().getWindow().getDecorView()); } private void clearState() { mSuggestionStrings.clear(); mSuggestionInts.clear(); mSuggestionSym.clear(); mPreviousWord = ""; mComposing.setLength(0); mComposingI.setLength(0); mWordFound = true; } protected void showAddWord() { if (mKeyMode == MODE_LANG) { Intent awintent = new Intent(this, AddWordAct.class); awintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); awintent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); awintent.putExtra("org.nyanya.android.traditionalt9.word", mComposing.toString()); awintent.putExtra("org.nyanya.android.traditionalt9.lang", mLang.id); clearState(); InputConnection ic = getCurrentInputConnection(); ic.setComposingText("", 0); ic.finishComposingText(); updateCandidates(); //onFinishInput(); mWordFound = true; startActivity(awintent); } } // sanitize lang and set index for cycling lang // Need to check if last lang is available, if not, set index to -1 and set lang to default to 0 private LANGUAGE sanitizeLang(LANGUAGE lang) { mLangIndex = 0; if (mLangsAvailable.length < 1 || lang == LANGUAGE.NONE) { Log.e("T9.sanitizeLang", "This shouldn't happen."); return mLangsAvailable[0]; } else { int index = LangHelper.findIndex(mLangsAvailable, lang); mLangIndex = index; return mLangsAvailable[index]; } } /** * This is the main point where we do our initialization of the input method * to begin operating on an application. At this point we have been bound to * the client, and are now receiving all of the detailed information about * the target of our edits. */ @Override public void onStartInput(EditorInfo attribute, boolean restarting) { super.onStartInput(attribute, restarting); //Log.d("onStartInput", "attribute.inputType: " + attribute.inputType + // " restarting? " + restarting); //Utils.printFlags(attribute.inputType); if (attribute.inputType == 0) { mLang = null; // don't do anything when not in any kind of edit field. // should also turn off input screen and stuff mEditing = NON_EDIT; requestHideSelf(0); hideStatusIcon(); // TODO: verify if need this // if (interfacehandler != null) { // interfacehandler.hideView(); // } return; } mFirstPress = true; mEditing = EDITING; // Reset our state. We want to do this even if restarting, because // the underlying state of the text editor could have changed in any // way. clearState(); // get settings Object[] settings = db.getSettings(new SETTING[] // 0, 1, 2, {SETTING.LANG_SUPPORT, SETTING.LAST_LANG, SETTING.MODE_NOTIFY, // 3, 4 SETTING.INPUT_MODE, SETTING.LAST_WORD} ); mLangsAvailable = LangHelper.buildLangs((Integer)settings[0]); mLang = sanitizeLang(LANGUAGE.get((Integer)settings[1])); updateCandidates(); //TODO: Check if "restarting" variable will make things faster/more effecient mKeyMode = MODE_TEXT; boolean modenotify = settings[2].equals(1); if (!modenotify && modeNotification != null) { modeNotification = null; } else if (modenotify && modeNotification == null){ modeNotification = Toast.makeText(this, "", Toast.LENGTH_SHORT); } // We are now going to initialize our state based on the type of // text being edited. switch (attribute.inputType & InputType.TYPE_MASK_CLASS) { case InputType.TYPE_CLASS_NUMBER: case InputType.TYPE_CLASS_DATETIME: // Numbers and dates default to the symbols keyboard, with // no extra features. mKeyMode = MODE_NUM; break; case InputType.TYPE_CLASS_PHONE: // Phones will also default to the symbols keyboard, though // often you will want to have a dedicated phone keyboard. mKeyMode = MODE_NUM; break; case InputType.TYPE_CLASS_TEXT: // This is general text editing. We will default to the // normal alphabetic keyboard, and assume that we should // be doing predictive text (showing candidates as the // user types). mKeyMode = (Integer)settings[3]; // We now look for a few special variations of text that will // modify our behavior. int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION; if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { // Do not display predictions / what the user is typing // when they are entering a password. mKeyMode = MODE_TEXT; } if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_URI || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { // Our predictions are not useful for e-mail addresses // or URIs. mKeyMode = MODE_TEXT; } if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { // If this is an auto-complete text view, then our predictions // will not be shown and instead we will allow the editor // to supply their own. We only show the editor's // candidates when in fullscreen mode, otherwise relying // own it displaying its own UI. // ???? mKeyMode = (Integer)settings[3]; } // handle filter list cases... do not hijack DPAD center and make // sure back's go through proper if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { mEditing = EDITING_NOSHOW; } // We also want to look at the current state of the editor // to decide whether our alphabetic keyboard should start out // shifted. updateShiftKeyState(attribute); break; default: Log.d("onStartInput", "defaulting"); // For all unknown input types, default to the alphabetic // keyboard with no special features. updateShiftKeyState(attribute); } // Special case for Softbank Sharp 007SH phone book. if (attribute.inputType == 65633) { mKeyMode = MODE_TEXT; } String prevword = null; if (attribute.privateImeOptions != null && attribute.privateImeOptions.equals("org.nyanya.android.traditionalt9.addword=true")) { mAddingWord = true; // mAddingSkipInput = true; // Log.d("onStartInput", "ADDING WORD"); mKeyMode = MODE_TEXT; } else { mAddingWord = false; // Log.d("onStartInput", "not adding word"); prevword = (String)settings[4]; if (prevword != null) { onText(prevword); db.storeSettingString(SETTING.LAST_WORD, null); } if (modenotify) { Resources r = getResources(); if (mKeyMode != MODE_NUM) modeNotify(String.format("%s %s %s", r.getStringArray(R.array.pref_lang_titles)[mLang.index], r.getStringArray(R.array.keyMode)[mKeyMode], r.getStringArray(R.array.capsMode)[mCapsMode])); else modeNotify(String.format("%s %s", r.getStringArray(R.array.pref_lang_titles)[mLang.index], r.getStringArray(R.array.keyMode)[mKeyMode])); } } // Update the label on the enter key, depending on what the application // says it will do. // mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions); setSuggestions(null, -1); setCandidatesViewShown(false); mSuggestionStrings.clear(); mSuggestionInts.clear(); mSuggestionSym.clear(); if (interfacehandler != null) { interfacehandler.midButtonUpdate(false); } updateKeyMode(); // show Window()? } /** * This is called when the user is done editing a field. We can use this to * reset our state. */ @Override public void onFinishInput() { super.onFinishInput(); // Log.d("onFinishInput", "When is this called?"); if (mEditing == EDITING || mEditing == EDITING_NOSHOW) { db.storeSettingInt(SETTING.LAST_LANG, mLang.id); commitTyped(); finish(); } } // @Override public void onFinishInputView (boolean finishingInput) { // Log.d("onFinishInputView", "? " + finishingInput); // } private void finish() { // Log.d("finish", "why?"); // Clear current composing text and candidates. pickSelectedCandidate(getCurrentInputConnection()); clearState(); // updateCandidates(); // We only hide the candidates window when finishing input on // a particular editor, to avoid popping the underlying application // up and down if the user is entering text into the bottom of // its window. // setCandidatesViewShown(false); // TODO: check this? mEditing = NON_EDIT; hideWindow(); hideStatusIcon(); } @Override public void onDestroy() { db.close(); super.onDestroy(); } // @Override public void onStartInputView(EditorInfo attribute, boolean // restarting) { // Log.d("onStartInputView", "attribute.inputType: " + attribute.inputType + // " restarting? " + restarting); // //super.onStartInputView(attribute, restarting); // } /** * Deal with the editor reporting movement of its cursor. */ @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); // If the current selection in the text view changes, we should // clear whatever candidate text we have. if ((mComposing.length() > 0 || mComposingI.length() > 0) && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd)) { mComposing.setLength(0); mComposingI.setLength(0); updateCandidates(); InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.finishComposingText(); } } } /** * This tells us about completions that the editor has determined based on * the current text in it. We want to use this in fullscreen mode to show * the completions ourself, since the editor can not be seen in that * situation. */ @Override public void onDisplayCompletions(CompletionInfo[] completions) { // ?????????????? } /** * Use this to monitor key events being delivered to the application. We get * first crack at them, and can either resume them or let them continue to * the app. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Log.d("onKeyDown", "Key: " + event + " repeat?: " + // event.getRepeatCount() + " long-time: " + event.isLongPress()); if (mEditing == NON_EDIT) { // // catch for UI weirdness on up event thing return false; } mFirstPress = false; if (keyCode == KeyMap.DPAD_CENTER) { if (interfacehandler != null) { interfacehandler.setPressed(keyCode, true); } // pass-through if (mEditing == EDITING_NOSHOW) { return false; } return handleDPAD(keyCode, event, true); } else if (keyCode == KeyMap.DPAD_DOWN || keyCode == KeyMap.DPAD_UP || keyCode == KeyMap.DPAD_LEFT || keyCode == KeyMap.DPAD_RIGHT) { if (mEditing == EDITING_NOSHOW) { return false; } return handleDPAD(keyCode, event, true); } else if (keyCode == KeyMap.SOFT_RIGHT || keyCode == KeyMap.SOFT_LEFT) { if (!isInputViewShown()) { return super.onKeyDown(keyCode, event); } } else if (keyCode == KeyMap.DEL) {// Special handling of the delete key: if we currently are // composing text for the user, we want to modify that instead // of let the application to the delete itself. // if (mComposing.length() > 0) { onKey(keyCode, null); return true; // } // break; } // only handle first press except for delete if (event.getRepeatCount() != 0) { return true; } if (mKeyMode == MODE_TEXT) { t9releasehandler.removeCallbacks(mt9release); } if (keyCode == KeyMap.BACK) {// The InputMethodService already takes care of the back // key for us, to dismiss the input method if it is shown. // but we will manage it ourselves because native Android handling // of the input view is ... flakey at best. // Log.d("onKeyDown", "back pres"); return isInputViewShown(); } else if (keyCode == KeyMap.ENTER) {// Let the underlying text editor always handle these. return false; // special case for softkeys } else if (keyCode == KeyMap.SOFT_RIGHT || keyCode == KeyMap.SOFT_LEFT) { if (interfacehandler != null) { interfacehandler.setPressed(keyCode, true); } // pass-through event.startTracking(); return true; } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1 || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3 || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5 || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 || keyCode == KeyMap.POUND || keyCode == KeyMap.STAR) { event.startTracking(); return true; } else {// KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD).getNumber(keyCode) // Log.w("onKeyDown", "Unhandled Key: " + keyCode + "(" + // event.toString() + ")"); } Log.w("onKeyDown", "Unhandled Key: " + keyCode + "(" + event.toString() + ")"); commitReset(); return super.onKeyDown(keyCode, event); } protected void launchOptions() { Intent awintent = new Intent(this, TraditionalT9Settings.class); awintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); awintent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); if (interfacehandler != null) { interfacehandler.setPressed(KeyMap.SOFT_RIGHT, false); } hideWindow(); startActivity(awintent); } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { // consume since we will assume we have already handled the long press // if greater than 1 if (event.getRepeatCount() != 1) { return true; } // Log.d("onLongPress", "LONG PRESS: " + keyCode); // HANDLE SPECIAL KEYS if (keyCode == KeyMap.POUND) { commitReset(); // do default action or insert new line if (!sendDefaultEditorAction(true)) { onText("\n"); } return true; } else if (keyCode == KeyMap.STAR) { if (mKeyMode != MODE_NUM) { if (mLangsAvailable.length > 1) { nextLang(); } else { showSmileyPage(); // TODO: replace with lang select if lang thing } return true; } } else if (keyCode == KeyMap.SOFT_LEFT) { if (interfacehandler != null) { interfacehandler.setPressed(keyCode, false); } if (mKeyMode == MODE_LANG) { if (mWordFound) { showAddWord(); } else { showSymbolPage(); } } } else if (keyCode == KeyMap.SOFT_RIGHT) { if (interfacehandler != null) { interfacehandler.setPressed(keyCode, false); } launchOptions(); // show Options return true; } if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { if (mKeyMode == MODE_LANG) { commitTyped(); onText(String.valueOf(keyCode - KeyEvent.KEYCODE_0)); } else if (mKeyMode == MODE_TEXT) { commitReset(); onText(String.valueOf(keyCode - KeyEvent.KEYCODE_0)); } else if (mKeyMode == MODE_NUM) { if (keyCode == KeyEvent.KEYCODE_0) { onText("+"); } } } return true; } /** * Use this to monitor key events being delivered to the application. We get * first crack at them, and can either resume them or let them continue to * the app. */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { // Log.d("onKeyUp", "Key: " + keyCode + " repeat?: " + // event.getRepeatCount()); if (mEditing == NON_EDIT) { // if (mButtonClose) { // //handle UI weirdness on up event // mButtonClose = false; // return true; // } // Log.d("onKeyDown", "returned false"); return false; } else if (mFirstPress) { // to make sure changing between input UI elements works correctly. return super.onKeyUp(keyCode, event); } if (keyCode == KeyMap.DPAD_CENTER) { if (interfacehandler != null) { interfacehandler.setPressed(keyCode, false); } if (mEditing == EDITING_NOSHOW) { return false; } return handleDPAD(keyCode, event, false); } else if (keyCode == KeyMap.DPAD_DOWN || keyCode == KeyMap.DPAD_UP || keyCode == KeyMap.DPAD_LEFT || keyCode == KeyMap.DPAD_RIGHT) { if (mEditing == EDITING_NOSHOW) { return false; } return handleDPAD(keyCode, event, false); } else if (keyCode == KeyMap.SOFT_RIGHT || keyCode == KeyMap.SOFT_LEFT) { if (!isInputViewShown()) { return super.onKeyDown(keyCode, event); } } if (event.isCanceled()) { return true; } if (keyCode == KeyMap.BACK) { if (isInputViewShown()) { hideWindow(); return true; } return false; } else if (keyCode == KeyMap.DEL) { return true; } else if (keyCode == KeyMap.ENTER) { return false; // special case for softkeys } else if (keyCode == KeyMap.SOFT_RIGHT || keyCode == KeyMap.SOFT_LEFT) {// if (mAddingWord){ // Log.d("onKeyUp", "key: " + keyCode + " skip: " + // mAddingSkipInput); // if (mAddingSkipInput) { // //mAddingSkipInput = false; // return true; // } // } if (interfacehandler != null) { interfacehandler.setPressed(keyCode, false); } // pass-through if (!isInputViewShown()) { showWindow(true); } onKey(keyCode, null); return true; } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1 || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3 || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5 || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 || keyCode == KeyMap.POUND || keyCode == KeyMap.STAR) { // if (!isInputViewShown()){ // Log.d("onKeyUp", "showing window."); // //showWindow(true); // } if (!isInputViewShown()) { showWindow(true); } onKey(keyCode, null); return true; } else {// KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD).getNumber(keyCode) Log.w("onKeyUp", "Unhandled Key: " + keyCode + "(" + event.toString() + ")"); } commitReset(); return super.onKeyUp(keyCode, event); } /** * Helper function to commit any text being composed in to the editor. */ private void commitTyped() { commitTyped(getCurrentInputConnection()); } private void commitTyped(InputConnection ic) { if (interfacehandler != null) { interfacehandler.midButtonUpdate(false); interfacehandler.showNotFound(false); } pickSelectedCandidate(ic); clearState(); updateCandidates(); setCandidatesViewShown(false); } /** * Helper to update the shift state of our keyboard based on the initial * editor state. */ private void updateShiftKeyState(EditorInfo attr) { // Log.d("updateShift", "CM start: " + mCapsMode); if (attr != null && mCapsMode != CAPS_ALL) { int caps = 0; if (attr.inputType != InputType.TYPE_NULL) { caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); } // mInputView.setShifted(mCapsLock || caps != 0); // Log.d("updateShift", "caps: " + caps); if ((caps & TextUtils.CAP_MODE_CHARACTERS) == TextUtils.CAP_MODE_CHARACTERS) { mCapsMode = CAPS_ALL; } else if ((caps & TextUtils.CAP_MODE_SENTENCES) == TextUtils.CAP_MODE_SENTENCES) { mCapsMode = CAPS_SINGLE; } else if ((caps & TextUtils.CAP_MODE_WORDS) == TextUtils.CAP_MODE_WORDS) { mCapsMode = CAPS_SINGLE; } else { mCapsMode = CAPS_OFF; } updateKeyMode(); } // Log.d("updateShift", "CM end: " + mCapsMode); } /** * Helper to send a key down / key up pair to the current editor. NOTE: Not * supposed to use this apparently. Need to use it for DEL. For other things * I'll have to onText */ private void keyDownUp(int keyEventCode) { InputConnection ic = getCurrentInputConnection(); KeyEvent kv = KeyEvent.changeFlags(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode), KeyEvent.FLAG_SOFT_KEYBOARD); ic.sendKeyEvent(kv); kv = KeyEvent.changeFlags(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode), KeyEvent.FLAG_SOFT_KEYBOARD); ic.sendKeyEvent(kv); } // Implementation of KeyboardViewListener @Override public void onKey(int keyCode, int[] keyCodes) { // Log.d("OnKey", "pri: " + keyCode); // Log.d("onKey", "START Cm: " + mCapsMode); // HANDLE SPECIAL KEYS if (keyCode == KeyMap.DEL) { handleBackspace(); // change case } else if (keyCode == KeyMap.STAR) { if (mKeyMode == MODE_NUM) { handleCharacter(KeyMap.STAR); } else { handleShift(); } } else if (keyCode == KeyMap.BACK) { handleClose(); // space } else if (keyCode == KeyMap.POUND) { handleCharacter(KeyMap.POUND); } else if (keyCode == KeyMap.SOFT_LEFT) { if (mWordFound) { showSymbolPage(); } else { showAddWord(); } } else if (keyCode == KeyMap.SOFT_RIGHT) { nextKeyMode(); } else { if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { handleCharacter(keyCode); } else { Log.e("onKey", "This shouldn't happen, unknown key"); } } // Log.d("onKey", "END Cm: " + mCapsMode); } @Override public void onText(CharSequence text) { InputConnection ic = getCurrentInputConnection(); if (ic == null) return; ic.beginBatchEdit(); if (mComposing.length() > 0 || mComposingI.length() > 0) { commitTyped(ic); } ic.commitText(text, 1); ic.endBatchEdit(); updateShiftKeyState(getCurrentInputEditorInfo()); } /** * Used from interface to either close the input UI if not composing text or * to accept the composing text */ protected void handleMidButton() { if (!isInputViewShown()) { showWindow(true); return; } if (mComposing.length() > 0) { switch (mKeyMode) { case MODE_LANG: commitTyped(); break; case MODE_TEXT: commitTyped(); charReset(); break; case MODE_NUM: // shouldn't happen break; } } else { hideWindow(); } } /** * Update the list of available candidates from the current composing text. * Do a lot of complicated stuffs. */ private void updateCandidates() { updateCandidates(false); } private void updateCandidates(boolean backspace) { if (mKeyMode == MODE_LANG) { int len = mComposingI.length(); if (len > 0) { if (mComposingI.charAt(len - 1) == '1') { boolean suggestions = !mSuggestionStrings.isEmpty(); String prefix = ""; if (mPreviousWord.length() == 0) { if (suggestions && !backspace) { prefix = mPreviousWord = mSuggestionStrings.get(mCandidateView.mSelectedIndex); } } else { if (backspace) { prefix = mPreviousWord; } else { if (suggestions) { prefix = mPreviousWord = mSuggestionStrings.get(mCandidateView.mSelectedIndex); } else { prefix = mPreviousWord; } } } mSuggestionInts.clear(); mSuggestionStrings.clear(); mSuggestionSym.clear(); db.updateWords("1", mSuggestionSym, mSuggestionInts, mCapsMode, mLang); for (String a : mSuggestionSym) { if (!prefix.equals("")) { mSuggestionStrings.add(prefix + a); } else { mSuggestionStrings.add(String.valueOf(a)); mComposingI.setLength(0); mComposingI.append("1"); } } } else { db.updateWords(mComposingI.toString(), mSuggestionStrings, mSuggestionInts, mCapsMode, mLang); } if (!mSuggestionStrings.isEmpty()) { mWordFound = true; mComposing.setLength(0); mComposing.append(mSuggestionStrings.get(0)); if (interfacehandler != null) { interfacehandler.showNotFound(false); } } else { mWordFound = false; mComposingI.setLength(len - 1); setCandidatesViewShown(false); if (interfacehandler != null) { interfacehandler.showNotFound(true); } } setSuggestions(mSuggestionStrings, 0); } else { setSuggestions(null, -1); setCandidatesViewShown(false); if (interfacehandler != null) { interfacehandler.showNotFound(false); } } } else if (mKeyMode == MODE_TEXT) { if (mComposing.length() > 0) { mSuggestionStrings.clear(); char[] ca = CharMap.T9TABLE[mLang.index][mPrevious]; for (char c : ca) { mSuggestionStrings.add(String.valueOf(c)); } setSuggestions(mSuggestionStrings, mCharIndex); } else { setSuggestions(null, -1); } } } private void setSuggestions(List suggestions, int initialSel) { if (suggestions != null && suggestions.size() > 0) { setCandidatesViewShown(true); } if (mCandidateView != null) { mCandidateView.setSuggestions(suggestions, initialSel); } } private void handleBackspace() { final int length = mComposing.length(); final int length2 = mComposingI.length(); if (mKeyMode == MODE_TEXT) { charReset(); if (interfacehandler != null) { interfacehandler.midButtonUpdate(false); } setCandidatesViewShown(false); } Log.d("handleBS", "Stage1: (" + length + "," + length2 + ")"); Log.d("handleBS", "Stage1: (" + mComposingI.toString() + ")"); if (length2 > 1) { if (mComposingI.charAt(length2 - 1) == '1') { // revert previous word mPreviousWord = mPreviousWord.substring(0, mPreviousWord.length() - 1); } mComposingI.delete(length2 - 1, length2); if (length2 - 1 > 1) { if (mComposingI.charAt(length2 - 2) != '1') { if (mComposingI.indexOf("1") == -1) { // no longer contains punctuation so we no longer care mPreviousWord = ""; } } } else { mPreviousWord = ""; } updateCandidates(true); getCurrentInputConnection().setComposingText(mComposing, 1); } else if (length > 0 || length2 > 0) { Log.d("handleBS", "resetting thing"); mComposing.setLength(0); mComposingI.setLength(0); interfacehandler.midButtonUpdate(false); interfacehandler.showNotFound(false); mSuggestionStrings.clear(); mPreviousWord = ""; getCurrentInputConnection().commitText("", 0); updateCandidates(); } else { mPreviousWord = ""; keyDownUp(KeyMap.DEL); } updateShiftKeyState(getCurrentInputEditorInfo()); // Log.d("handleBS", "Cm: " + mCapsMode); // Why do I need to call this twice, android... updateShiftKeyState(getCurrentInputEditorInfo()); } private void handleShift() { // do my own thing here if (mCapsMode == CAPS_CYCLE.length - 1) { mCapsMode = 0; } else { mCapsMode++; } if (mKeyMode == MODE_LANG && mComposing.length() > 0) { updateCandidates(); getCurrentInputConnection().setComposingText(mComposing, 1); } updateKeyMode(); if (modeNotification != null) modeNotify(getResources().getStringArray(R.array.capsMode)[mCapsMode]); } /** * handle input of a character. Precondition: ONLY 0-9 AND *# ARE ALLOWED * * @param keyCode */ private void handleCharacter(int keyCode) { switch (mKeyMode) { case MODE_LANG: // it begins // on POUND commit and space if (keyCode == KeyMap.POUND) { if (mComposing.length() > 0) { commitTyped(); } onText(" "); } else { // do things if (interfacehandler != null) { interfacehandler.midButtonUpdate(true); } keyCode = keyCode - KeyEvent.KEYCODE_0; mComposingI.append(keyCode); updateCandidates(); getCurrentInputConnection().setComposingText(mComposing, 1); } break; case MODE_TEXT: t9releasehandler.removeCallbacks(mt9release); if (keyCode == KeyMap.POUND) { keyCode = 10; } else { keyCode = keyCode - KeyEvent.KEYCODE_0; } // Log.d("handleChar", "PRIMARY CODE (num): " + keyCode); boolean newChar = false; if (mPrevious == keyCode) { mCharIndex++; } else { commitTyped(getCurrentInputConnection()); // updateShiftKeyState(getCurrentInputEditorInfo()); newChar = true; mCharIndex = 0; mPrevious = keyCode; } // start at caps if CapMode // Log.d("handleChar", "Cm: " + mCapsMode); if (mCharIndex == 0 && mCapsMode != CAPS_OFF) { mCharIndex = CharMap.T9CAPSTART[mLang.index][keyCode]; } // private int mPrevious; // private int mCharindex; mComposing.setLength(0); mComposingI.setLength(0); char[] ca = CharMap.T9TABLE[mLang.index][keyCode]; if (mCharIndex >= ca.length) { mCharIndex = 0; } mComposing.append(ca[mCharIndex]); getCurrentInputConnection().setComposingText(mComposing, 1); t9releasehandler.postDelayed(mt9release, T9DELAY); if (newChar) { // consume single caps if (mCapsMode == CAPS_SINGLE) { mCapsMode = CAPS_OFF; } } updateCandidates(); updateShiftKeyState(getCurrentInputEditorInfo()); break; case MODE_NUM: if (keyCode == KeyMap.POUND) { onText("#"); } else if (keyCode == KeyMap.STAR) { onText("*"); } else { onText(String.valueOf(keyCode - KeyEvent.KEYCODE_0)); } break; default: Log.e("handleCharacter", "Unknown input?"); } } // This is a really hacky way to handle DPAD long presses in a way that we can pass them on to // the underlying edit box in a somewhat reliable manner. // (somewhat because there are a few cases where this doesn't work properly or acts strangely.) private boolean handleDPAD(int keyCode, KeyEvent event, boolean keyDown) { // Log.d("handleConsumeDPAD", "keyCode: " + keyCode + " isKeyDown: " + // isKeyDown); if (keyDown) { // track key, if seeing repeat count < 0, start sending this event // and previous to super if (event.getRepeatCount() == 0) { // store event mDPADkeyEvent = event; return true; } else { if (mIgnoreDPADKeyUp) { // pass events to super return super.onKeyDown(keyCode, event); } else { // pass previous event and future events to super mIgnoreDPADKeyUp = true; getCurrentInputConnection().sendKeyEvent(mDPADkeyEvent); return super.onKeyDown(keyCode, event); } } } else { // if we have been sending previous down events to super, do the // same for up, else process the event if (mIgnoreDPADKeyUp) { mIgnoreDPADKeyUp = false; return super.onKeyUp(keyCode, event); } else { if (mKeyMode != MODE_NUM && mComposing.length() > 0) { if (keyCode == KeyMap.DPAD_DOWN) { mCandidateView.scrollSuggestion(1); getCurrentInputConnection().setComposingText(mSuggestionStrings.get(mCandidateView.mSelectedIndex), 1); return true; } else if (keyCode == KeyMap.DPAD_UP) { mCandidateView.scrollSuggestion(-1); getCurrentInputConnection().setComposingText(mSuggestionStrings.get(mCandidateView.mSelectedIndex), 1); return true; } else if (keyCode == KeyMap.DPAD_LEFT || keyCode == KeyMap.DPAD_RIGHT) { if (mKeyMode == MODE_LANG) { commitTyped(); } else if (mKeyMode == MODE_TEXT) { commitReset(); } // getCurrentInputConnection().sendKeyEvent(mDPADkeyEvent); // return super.onKeyUp(keyCode, event); return true; } } if (keyCode == KeyMap.DPAD_CENTER) { handleMidButton(); return true; } else {// Send stored event to input connection then pass current // event onto super getCurrentInputConnection().sendKeyEvent(mDPADkeyEvent); return super.onKeyUp(keyCode, event); } } } } private void commitReset() { commitTyped(getCurrentInputConnection()); charReset(); if (mCapsMode == CAPS_SINGLE) { mCapsMode = CAPS_OFF; } // Log.d("commitReset", "CM pre: " + mCapsMode); updateShiftKeyState(getCurrentInputEditorInfo()); // Log.d("commitReset", "CM post: " + mCapsMode); } private void charReset() { t9releasehandler.removeCallbacks(mt9release); mPrevious = -1; mCharIndex = 0; } private void handleClose() { commitTyped(getCurrentInputConnection()); requestHideSelf(0); } protected void nextKeyMode() { if (mKeyMode == MODE_CYCLE.length - 1) { mKeyMode = 0; } else { mKeyMode++; } updateKeyMode(); resetKeyMode(); if (modeNotification != null) modeNotify(getResources().getStringArray(R.array.keyMode)[mKeyMode]); } private void modeNotify(String s) { modeNotification.setText(s); modeNotification.show(); modeNotification.cancel(); // TODO: This will not always hide the Toast. // will probably need to implement custom view } private void nextLang() { mLangIndex++; if (mLangIndex == mLangsAvailable.length) { mLangIndex = 0; } mLang = mLangsAvailable[mLangIndex]; updateKeyMode(); if (modeNotification != null) { modeNotify(getResources().getStringArray(R.array.pref_lang_titles)[mLang.index]); } } private void resetKeyMode() { charReset(); if (mKeyMode != MODE_NUM) { commitTyped(); } mComposing.setLength(0); mComposingI.setLength(0); getCurrentInputConnection().finishComposingText(); } /** * Set the status icon that is appropriate in current mode (based on * openwmm-legacy) */ private void updateKeyMode() { int icon = 0; switch (mKeyMode) { case MODE_TEXT: interfacehandler.showHold(false); icon = LangHelper.ICONMAP[mLang.index][mKeyMode][mCapsMode]; break; case MODE_LANG: if (!db.ready) { if (!mGaveUpdateWarn) { Toast.makeText(this, getText(R.string.updating_database_unavailable), Toast.LENGTH_LONG).show(); mGaveUpdateWarn = true; } nextKeyMode(); return; } if (mLangIndex == -1) { nextKeyMode(); return; } if (mAddingWord) { interfacehandler.showHold(false); } else { interfacehandler.showHold(true); } //Log.d("T9.updateKeyMode", "lang: " + mLang + " mKeyMode: " + mKeyMode + " mCapsMode" // + mCapsMode); icon = LangHelper.ICONMAP[mLang.index][mKeyMode][mCapsMode]; break; case MODE_NUM: interfacehandler.showHold(false); icon = R.drawable.ime_number; break; default: Log.e("updateKeyMode", "How."); break; } showStatusIcon(icon); } private void pickSelectedCandidate(InputConnection ic) { pickSuggestionManually(-1, ic); } private void pickSuggestionManually(int index, InputConnection ic) { // Log.d("pickSuggestMan", "Doing"); if (mComposing.length() > 0 || mComposingI.length() > 0) { // If we were generating candidate suggestions for the current // text, we would commit one of them here. But for this sample, // we will just commit the current text. if (!mSuggestionStrings.isEmpty()) { if (index < 0) { // Log.d("pickSuggestMan", "picking SELECTED: " + // mSuggestionStrings.get(mCandidateView.mSelectedIndex)); // get and commit selected suggestion ic.commitText(mSuggestionStrings.get(mCandidateView.mSelectedIndex), 1); if (mKeyMode == MODE_LANG) { // update freq db.incrementWord(mSuggestionInts.get(mCandidateView.mSelectedIndex)); } } else { // commit suggestion index ic.commitText(mSuggestionStrings.get(index), 1); if (mKeyMode == MODE_LANG) { db.incrementWord(mSuggestionInts.get(index)); } } } } } /** * Ignore this for now. */ @Override public void swipeRight() { // if (mPredictionOn) { // pickDefaultCandidate(); // } } @Override public void swipeLeft() { handleBackspace(); } @Override public void swipeDown() { handleClose(); } @Override public void swipeUp() { } @Override public void onPress(int primaryCode) { } @Override public void onRelease(int primaryCode) { } }