Android Open Source - WriteRight Soft Keyboard






From Project

Back to project page WriteRight.

License

The source code is released under:

GNU General Public License

If you think the Android project WriteRight listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 *    Alternative input method that resizes its virtual keys according to
 *    usage statistics by the user (when, where and which words were typed.
 *    Copyright (C) June 2011  Panagiotis Sakkos <panos.sakkos@gmail.com>
 */*from   ww  w  . jav  a  2s.  co  m*/
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package panos.sakkos.softkeyboard.writeright;

import android.content.Context;
import android.database.Cursor;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import android.inputmethodservice.KeyboardView;
import android.os.SystemClock;
import android.os.Vibrator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;

import panos.sakkos.softkeyboard.writeright.R;

public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener 
{
    static final char NO_INPUT = 0;
    private KeyboardView mInputView;
    
    private StringBuilder mComposing = new StringBuilder();
    private boolean mPredictionOn;
    private int mLastDisplayWidth;
    private boolean mCapsLock;
    private long mLastShiftTime;
    
    private LatinKeyboard mSymbolsKeyboard;
    private LatinKeyboard mSymbolsShiftedKeyboard;
    private LatinKeyboard mQwertyKeyboard;
    
    private LatinKeyboard mCurKeyboard;
    
    private String mWordSeparators;
    
    private Predictor predictor;
    private int initialKeyHeight, initialKeyWidth;  
    private int smallHeight, smallWidth;
    private int k = 26;
    private int continuousSuccesses = 0;
    private final int AGGRESIVE_THRESHOLD = 5;
    
    private boolean unprobableKeysMoved = false;
    List<Character> probableKeys;

    private boolean landscape = false;
    OrientationEventListener myOrientationEventListener; 
    
    private boolean predictionCanceled = false;
    private Vibrator vibrator;    
    
    private DataBaseHelper myDbHelper;    
    
    private int editorInfo;
    private boolean autocomplete;
    
    /**
     * Main initialization of the input method component.  Be sure to call
     * to super class.
     */
    
    @Override public void onCreate() 
    {
        super.onCreate();
        Log.i("DEBUG", "onCreate");
        mWordSeparators = getResources().getString(R.string.word_separators);

        Init();
    }

    @Override public void onDestroy() 
    {
      Log.i("DEBUG", "onDestroy");
      OnClose();
      super.onDestroy();
    }
    
    
    /**
     * This is the point where you can do all of your UI initialization.  It
     * is called after creation and any configuration change.
     */
    @Override public void onInitializeInterface() 
    {
    Log.i("DEBUG", "onInitializeInterface");    

    if (mQwertyKeyboard != null) {
            // Configuration changes can happen after the keyboard gets recreated,
            // so we need to be able to re-build the keyboards if the available
            // space has changed.
            int displayWidth = getMaxWidth();
            if (displayWidth == mLastDisplayWidth) return;
            mLastDisplayWidth = displayWidth;

            /* keyboard width changed, do the proper initializations for the UI */
            
            if (getResources().getConfiguration().orientation == 1 && landscape == true)
            {
              landscape = false;
                unprobableKeysMoved = false;
            }
            else if(getResources().getConfiguration().orientation == 2 && landscape == false)
            {
              landscape = true;
                unprobableKeysMoved = false;
            }
            
    }
    
        mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty);
        mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols);
        mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift);

    GetkeyInitialSizes();        

    /* If the user changes orientation in the middle of typing a word, resize */
    
    if(mComposing.length() > 0)
    {
      TopKResize(predictor.GetPredictions());
    }
    }
    
    /**
     * 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() {      
    Log.i("DEBUG", "onCreateInputView");    

    mInputView = (KeyboardView) getLayoutInflater().inflate(
                R.layout.input, null);
        mInputView.setOnKeyboardActionListener(this);
        mInputView.setKeyboard(mQwertyKeyboard);
        
        return mInputView;
    }

    /**
     * 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.i("DEBUG", "onStartInput");    
        
        // 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.
        
        //FUTURE REVIEW the line bellow messed my words when orientation changed
        //mComposing.setLength(0);
                
        mPredictionOn = false;
        
        // We are now going to initialize our state based on the type of
        // text being edited.
        
        editorInfo = attribute.inputType&EditorInfo.TYPE_MASK_CLASS;
        
        switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
            case EditorInfo.TYPE_CLASS_NUMBER:
            case EditorInfo.TYPE_CLASS_DATETIME:
                // Numbers and dates default to the symbols keyboard, with
                // no extra features.
                mCurKeyboard = mSymbolsKeyboard;
                Log.i("DEBUG", "NUMBER OR DATETIME");
                break;
                
            case EditorInfo.TYPE_CLASS_PHONE:
                // Phones will also default to the symbols keyboard, though
                // often you will want to have a dedicated phone keyboard.
                mCurKeyboard = mSymbolsKeyboard;
                Log.i("DEBUG", "PHONE");
                break;
                
            case EditorInfo.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).
                mCurKeyboard = mQwertyKeyboard;
                mPredictionOn = true;
                
                // We now look for a few special variations of text that will
                // modify our behavior.
                int variation = attribute.inputType &  EditorInfo.TYPE_MASK_VARIATION;
                if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
                        variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
                    // Do not display predictions / what the user is typing
                    // when they are entering a password.
                  Log.i("DEBUG", "PASSWORD");
                    mPredictionOn = false;
                }
                
                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 
                        || variation == EditorInfo.TYPE_TEXT_VARIATION_URI
                        || variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
                    // Our predictions are not useful for e-mail addresses
                    // or URIs.
                  Log.i("DEBUG", "MAIL OR URI");
                    mPredictionOn = false;
                }
                
                if ((attribute.inputType&EditorInfo.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.
                  Log.i("DEBUG", "AUTO COMPLETE");
                  autocomplete = true;
                    mPredictionOn = false;
                }
                else
                {
                  autocomplete = false;
                }
                
                // 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:
                // For all unknown input types, default to the alphabetic
                // keyboard with no special features.
                mCurKeyboard = mQwertyKeyboard;
                updateShiftKeyState(attribute);
        }
        
        // Update the label on the enter key, depending on what the application
        // says it will do.
        mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
        
        if(mPredictionOn == false)
        {
            RestoreInitialSizes();
        }
    }

    /**
     * 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.i("DEBUG", "onFinishInput");    
        
        // Clear current composing text and candidates.
        mComposing.setLength(0);
        
        // 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);
        
        mCurKeyboard = mQwertyKeyboard;
        if (mInputView != null) {
            mInputView.closing();
        }
        
        WordseparatorTyped();
    }
    
    @Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
        super.onStartInputView(attribute, restarting);
        // Apply the selected keyboard to the input view.
    Log.i("DEBUG", "onStartInputView");    
        mInputView.setKeyboard(mCurKeyboard);
        mInputView.closing();
    }
    
    /**
     * 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);
        Log.i("DEBUG", "onupdateSelection");            


//code version 2 for fixing the reader-twitter bug ;). Code bellow was moved here from onrelease method
        
        if(PredictionFriendlyInput())
        {
      if(CursorIsAtTheEnd() == false && isWordSeparator(FirstCharacterBeforeCursor()) == false)
      {
        mPredictionOn = false;
        predictor.SetIdle();
        RestoreInitialSizes();
        Log.d("DEBUG", "CURSOR NOT AT END AND NOT separator; PREDICTOR SET TO IDLE");
      }
      else if(CursorIsAtTheEnd() && isWordSeparator(FirstCharacterBeforeCursor()))
      {
        mPredictionOn = true;
        predictor.SetNotIdle();
        Log.d("DEBUG", "CURSOR AT END AND separator; PREDICTOR SET TO IDLE");
      }   
  
          // If the current selection in the text view changes, we should
          // clear whatever candidate text we have.
          if (mComposing.length() > 0 && (newSelStart != candidatesEnd
                  || newSelEnd != candidatesEnd)) {
              mComposing.setLength(0);
              InputConnection ic = getCurrentInputConnection();
              if (ic != null) {
                ic.finishComposingText();
              }
          }
        }
    }

    /**
     * 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) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                // The InputMethodService already takes care of the back
                // key for us, to dismiss the input method if it is shown.
                // However, our keyboard could be showing a pop-up window
                // that back should dismiss, so we first allow it to do that.
                if (event.getRepeatCount() == 0 && mInputView != null) {
                    if (mInputView.handleBack()) {
                        return true;
                    }
                }
                break;
                
            case KeyEvent.KEYCODE_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(Keyboard.KEYCODE_DELETE, null);
                    return true;
                }
                break;
                
            case KeyEvent.KEYCODE_ENTER:
                // Let the underlying text editor always handle these.
                return false;
                
            default:
        }
        
        return super.onKeyDown(keyCode, event);
    }

    /**
     * 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) 
    {
        return super.onKeyUp(keyCode, event);
    }

    /**
     * Helper function to commit any text being composed in to the editor.
     */
    private void commitTyped(InputConnection inputConnection) 
    {
        if (mComposing.length() > 0) 
        { 
            inputConnection.commitText(mComposing, mComposing.length());
            mComposing.setLength(0);
            RestoreInitialSizes();
        }
    }

    /**
     * Helper to update the shift state of our keyboard based on the initial
     * editor state.
     */
    private void updateShiftKeyState(EditorInfo attr) {
        if (attr != null && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) {
            int caps = 0;
            EditorInfo ei = getCurrentInputEditorInfo();
            if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
                caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
            }
            mInputView.setShifted(mCapsLock || caps != 0);
        }
    }
    
    /**
     * Helper to determine if a given character code is alphabetic.
     */
    private boolean isAlphabet(int code) 
    {
        return Character.isLetter(code);
    }
    
    /**
     * Helper to send a key down / key up pair to the current editor.
     */
    private void keyDownUp(int keyEventCode) 
    {
        getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
        getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
    }
    
    /**
     * Helper to send a character to the editor as raw key events.
     */
    private void sendKey(int keyCode) {
        switch (keyCode) {
            case '\n':
                keyDownUp(KeyEvent.KEYCODE_ENTER);
                break;
            default:
                if (keyCode >= '0' && keyCode <= '9') {
                    keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0);
                } else {
                    getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1);
                }
                break;
        }
    }

    /* Implementation of KeyboardViewListener */

    public void onKey(int primaryCode, int[] keyCodes) {
        if (isWordSeparator(primaryCode)) {
            // Handle separator
            if (mComposing.length() > 0) {
                commitTyped(getCurrentInputConnection());
            }
            sendKey(primaryCode);
            updateShiftKeyState(getCurrentInputEditorInfo());
        } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
            handleBackspace();
        } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
            handleShift();
        } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
            handleClose();
            return;
        } else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) {
            // Show a menu or somethin'
        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
                && mInputView != null) {
            Keyboard current = mInputView.getKeyboard();
            if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) {
                current = mQwertyKeyboard;
            } else {
                current = mSymbolsKeyboard;
            }
            mInputView.setKeyboard(current);
            if (current == mSymbolsKeyboard) {
                current.setShifted(false);
            }
        } else {
            handleCharacter(primaryCode, keyCodes);
        }
    }

    public void onText(CharSequence text) {
        InputConnection ic = getCurrentInputConnection();
        if (ic == null) return;
        ic.beginBatchEdit();
        if (mComposing.length() > 0) {
            commitTyped(ic);
        }
        ic.commitText(text, 0);
        ic.endBatchEdit();
        updateShiftKeyState(getCurrentInputEditorInfo());
    }
        
    private void handleBackspace() {
        final int length = mComposing.length();
        if (length > 1) {
            mComposing.delete(length - 1, length);
            getCurrentInputConnection().setComposingText(mComposing, 1);
        } else if (length > 0) {
            mComposing.setLength(0);
            getCurrentInputConnection().commitText("", 0);
        } else {
            keyDownUp(KeyEvent.KEYCODE_DEL);
        }
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    private void handleShift() {
        if (mInputView == null) {
            return;
        }
        
        Keyboard currentKeyboard = mInputView.getKeyboard();
        if (mQwertyKeyboard == currentKeyboard) {
            // Alphabet keyboard
            checkToggleCapsLock();
            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
        } else if (currentKeyboard == mSymbolsKeyboard) {
            mSymbolsKeyboard.setShifted(true);
            mInputView.setKeyboard(mSymbolsShiftedKeyboard);
            mSymbolsShiftedKeyboard.setShifted(true);
        } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
            mSymbolsShiftedKeyboard.setShifted(false);
            mInputView.setKeyboard(mSymbolsKeyboard);
            mSymbolsKeyboard.setShifted(false);
        }
    }
    
    private void handleCharacter(int primaryCode, int[] keyCodes) {
        if (isInputViewShown()) {
            if (mInputView.isShifted()) {
                primaryCode = Character.toUpperCase(primaryCode);
            }
        }
        if (isAlphabet(primaryCode) && mPredictionOn) {
            mComposing.append((char) primaryCode);
            getCurrentInputConnection().setComposingText(mComposing, 1);
            updateShiftKeyState(getCurrentInputEditorInfo());
        } else {
            getCurrentInputConnection().commitText(
                    String.valueOf((char) primaryCode), 1);
        }
    }

    private void handleClose() {
        commitTyped(getCurrentInputConnection());
        requestHideSelf(0);
        mInputView.closing();
    }

    private void checkToggleCapsLock() {
        long now = System.currentTimeMillis();
        if (mLastShiftTime + 800 > now) {
            mCapsLock = !mCapsLock;
            mLastShiftTime = 0;
        } else {
            mLastShiftTime = now;
        }
    }
    
    private String getWordSeparators() 
    {
        return mWordSeparators;
    }
    
    public boolean isWordSeparator(int code) 
    {
        String separators = getWordSeparators();
        return separators.contains(String.valueOf((char)code));
    }
    
    public void swipeDown() 
    {
        handleClose();
    }
    
    /* On up, left and right swipe, the user cancels the current predictions and when he finishes
     * typing the word (when he types a word separator) the new word will be added 
     * to the predictor (see WordSeparatorTyped)
     */    

    public void swipeUp() 
    {
      CancelPrediction();    
    }
    
    public void swipeRight() 
    {
      CancelPrediction();
    }

    public void swipeLeft() 
    {
      CancelPrediction();
    }
    
    public void onRelease(int primaryCode) 
    {
      vibrator.vibrate(25);

      /* Do nothing if the resize is disabled */
      
      if(mPredictionOn == false)
      {
        return;
      }

      if (primaryCode == Keyboard.KEYCODE_DELETE) 
        {
          Log.d("DEBUG", "BACKSPACE PRESSED");           
          DeleteTyped();
        }

      if (isWordSeparator(primaryCode)) 
        {
        Log.d("DEBUG", "WORD separator PRESSED");
          WordseparatorTyped();
        }
        else if (Character.isLetter(primaryCode))
        {
        Log.d("DEBUG", "CHARACTER PRESSED");
            CharacterTyped(primaryCode);   
        }
      
      /* Handle cursor not at the end of input and not last character word separator */
    }
    
    public void onPress(int primaryCode) 
    {       
    }

  private void DeleteTyped() 
  {
    char lastCharacter = FirstCharacterBeforeCursor(); 
    
    if(isWordSeparator(lastCharacter))
    {
      predictor.SetNotIdle();
      RestoreInitialSizes();
      Log.d("DEBUG", "WORD separator DETECTED; PREDICTOR NOT IDLE");      
    }
    else if(lastCharacter == NO_INPUT)
    {
      predictor.SetNotIdle();      
      RestoreInitialSizes();
      Log.d("DEBUG", "NO INPUT DETECTED; PREDICTOR NOT IDLE");      
    }
    else if(GetLastWordBeforeCursor().length() > 0)
    {
      /* "Backward" top k resize */
      
      BackwardTopKResize();
    }
    else
    {
      predictor.SetIdle();
      RestoreInitialSizes();
      Log.d("DEBUG", "PREDICTOR IDLE");      
    }
  }

  private void BackwardTopKResize() 
  {
    predictor.SetIdle(); predictor.SetNotIdle();
    for(int i = 0; i < GetLastWordBeforeCursor().length(); i++)
    {
      try
      {
        predictor.CharacterTyped(GetLastWordBeforeCursor().charAt(i));
      }
      catch(Exception ex)
      {
        Log.d("DEBUG", ex.getMessage());
      }
    }
    
    TopKResize(predictor.GetPredictions());
  }

  private void CharacterTyped(int primaryCode) 
  {
    char character = (char) primaryCode; 
    
    try 
    {
      predictor.CharacterTyped(character);
    } 
    catch (Exception e) 
    {
        Log.d("DEBUG", "PREDICTOR.CHARACTER_TYPED: FAILED");
    }

    HashMap<Character, Float> predictions = predictor.GetPredictions();

    if( predictions.isEmpty())
    {
      RestoreInitialSizes();
      Log.d("DEBUG", "NO PREDICTIONS RETURNED");
    }
    else
    {      
      TopKResize(predictions);        
    }
  }

  private void WordseparatorTyped() 
  {    
    if(predictionCanceled)
    {
      Log.d("DEBUG", "NEW WORD TO BE ADDED TO PREDICTOR: " + GetLastWordBeforeCursor());
      predictor.LearnNewWord(GetLastWordBeforeCursor()); 
      predictionCanceled = false;
    }
    else
    {
      SuccessfullPrediction();
    }

    try 
    {
      predictor.WordTyped();
          Log.i("DEBUG", "PREDICTOR NOT IDLE");
    }
    catch (Exception e) 
    {
      Log.e("DEBUG", "FAILED TO HANDLE separator");
    }

    /* Restore initial key sizes when a word is typed */
    
    RestoreInitialSizes();
  }
    
  /* Checks for external memory read/write availability. If available
   * and the clean_wordnet file does not exist, it copies it. If the predictor instance file
   * doesn't exist, it creates it. In case of external memory read/write unavailability, raises 
   * a keyboardInactive flag, in order to avoid exceptions etc. 
   */
    
    private void Init()
    {      
      /* Create database if doesn't exist*/

        myDbHelper = new DataBaseHelper((Context) this);
 
        try 
        {
          myDbHelper.createDataBase();
          myDbHelper.openDataBase();
        }
        catch(Exception exception)
        {
          Log.e("DEBUG", exception.getMessage());
        }      

        /* Load predictor essentials */
        
        Cursor cursor = myDbHelper.GetEssentials();
        cursor.moveToNext();
        k = cursor.getInt(0);
        continuousSuccesses = cursor.getInt(1);

        /* Create and load the predictor */
        
        try 
      {        
            long start = SystemClock.currentThreadTimeMillis();
        predictor = new Predictor(myDbHelper);
        long elapsed = SystemClock.currentThreadTimeMillis() - start;
        Log.i("DEBUG", "PREDICTOR CREATED IN " + Long.toString(elapsed) + " MILLISECONDS");
      } 
      catch (Exception e) 
      {
        Log.e("DEBUG", "LOAD PREDICTOR_INSTANCE: FAILED");
    }        
      
      vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    }
    
    /* Gets the height and width that keys have at start, in order to 
     * be able to resize them at their initial size in the future
     */
    
    private void GetkeyInitialSizes()
    {      
        smallWidth  = smallHeight = getMaxWidth() / 12;
      initialKeyHeight = mQwertyKeyboard.getKeys().get(0).height;
      initialKeyWidth = mQwertyKeyboard.getKeys().get(0).width;
    }
    
    /*
     * Restores every keys height to it's initial size. Used whenever a word is typed or
     * there is no prediction for next letter.
     */
    
    private void RestoreInitialSizes()
    {
      boolean changed = false;
    
      for(Key key : mQwertyKeyboard.getKeys())
      {
          if(KeyMustBeProccessed(key))
        {
            key.height = initialKeyHeight;
          key.width = initialKeyWidth;
        }          

          if( unprobableKeysMoved == true && KeyMustBeProccessed(key) && probableKeys.contains(new Character(key.label.charAt(0))) == false)
      {
            if(landscape == false)
            {
              key.y -= initialKeyHeight / 3;
            }
            else
            {
              key.x -= getMaxWidth() / 50;
            }
            
            changed = true;
      }
      }
      
      if(changed)
      {
        unprobableKeysMoved = false;
      }

      if(mInputView != null)
        mInputView.invalidateAllKeys();
    }
    
    private void TopKResize(HashMap<Character, Float> predictions)
    {            
      /* Find top k next predicted letter */
      
      TreeSet<Float> sortedEvaluations = new TreeSet<Float>(predictions.values());      
        sortedEvaluations.remove(new Float(0.0));

        /* Take top k evaluations */
        
        List<Float> topKEvaluations = new ArrayList<Float>();

        while(sortedEvaluations.size() > 0 && topKEvaluations.size() <= k)
        {
          Float evaluation = sortedEvaluations.last();
          sortedEvaluations.remove(evaluation);
          topKEvaluations.add(evaluation);
        } 
                
      RestoreInitialSizes();

        /* If there are no predictions, do not resize the keys */

        if(topKEvaluations.size() == 0)
        {
          Log.d("DEBUG", "NO NON-ZERO VALUES FOUND");
          return; 
        }
 
    List<Key> keys =  mQwertyKeyboard.getKeys();
    probableKeys = new ArrayList<Character>();

    /* Keep every probable next letter in the probableKeys list */

    for(int i = 0; i < topKEvaluations.size(); i++)
        {
          for(Key key : keys)
          {
            if( KeyMustBeProccessed(key) && ProbabilitiesAreEqual(predictions.get(new Character(key.label.charAt(0))), topKEvaluations.get(i)))
            {
                probableKeys.add(new Character(key.label.charAt(0)));
            }
          }
        }
    
    /* Resize every key that it's letter is not in the probableKeys list */
    
    for(Key key : keys)
    {
      if(KeyMustBeProccessed(key) && probableKeys.contains(new Character(key.label.charAt(0))) == false)
      {
        if(landscape == false)
        {
          key.height = smallHeight;
          key.width = smallWidth;
        }
        else
        {
          key.height = key.width = getMaxWidth() / 15;
        }
        
        if( unprobableKeysMoved == false )
        {
               if(landscape == false)
               {
            key.y += initialKeyHeight / 3; 
               }
               else
               {
                 key.x += getMaxWidth() / 50;
               }
        }
      }
    }
    
    unprobableKeysMoved = true;
    mInputView.invalidateAllKeys();

    }
    
    /* Saves the predictor instance */
    
    private void OnClose()
    {
      try
      {
           predictor.Save();
         Log.i("DEBUG", "PREDICTOR STATE SAVED");
           myDbHelper.UpdateEssentials(k, continuousSuccesses);
         Log.i("DEBUG", "ESSENTIALS UPDATED");
         myDbHelper.UpgradeDatabase();
        Log.i("DEBUG", "DATABASE UPGRADED");
         }
      catch(Exception ex)
       {
         Log.e("DEBUG", "ERROR WHILE SAVING DATA");
       }      
    }
    
    /* Helper to avoid arithmetic instability */
    
    private boolean ProbabilitiesAreEqual(Float probabilityA, Float probabilityB)
    {
      return (double) (probabilityA.floatValue() * 10) == (double) (probabilityB.floatValue() * 10);
    }

    /* Helper which returns the last character at input */
    
    private char FirstCharacterBeforeCursor()
    {
      InputConnection ic = getCurrentInputConnection();
      CharSequence input =  ic.getTextBeforeCursor(100, 0);
      
      if(input.length() == 0)
      {
        Log.e("DEBUG", "NO INPUT");
        return NO_INPUT;
      }
      else
      {
//        Log.e("DEBUG", Character.toString(input.charAt(ic.getTextBeforeCursor(100, 0).length() - 1)));

        return input.charAt(ic.getTextBeforeCursor(100, 0).length() - 1);
      }
    }
    
    private char FirstCharacterAfterCursor()
    {
      InputConnection ic = getCurrentInputConnection();
      CharSequence input =  ic.getTextAfterCursor(100, 0);
    
      if(input.length() == 0)
      {
        Log.e("DEBUG", ".");
        return '.'; //return a separator
      }
      else
      {
        Log.e("DEBUG", Character.toString(input.charAt(0)));
        return input.charAt(0);
      }
    }
    
    /* Helper that returns if the cursor is at the end of input */
    
    private boolean CursorIsAtTheEnd()
    {
      InputConnection inputConnection = getCurrentInputConnection();
      CharSequence textAfterCursor = inputConnection.getTextAfterCursor(100, 0);
      return textAfterCursor.length() == 0;
    }
    
    /* Helper which determines if the key must be processed */
    
    private boolean KeyMustBeProccessed(Key key)
    {
      return key.label != null && key.label.length() == 1 && Character.isLetter(key.label.charAt(0));
    }
    
    /* Helper which returns the last word before the cursor*/

    private String GetLastWordBeforeCursor()
    {
      String input = getCurrentInputConnection().getTextBeforeCursor(100, 0).toString();
      
      if(input == null || input.length() == 0)
      {
        Log.d("DEBUG", "NO LAST WORD FOUND");
        return null;
      }

      int lastSeparatorIndex = 0;
      for(int i = 0; i < input.length() - 1; i++)
      {
        if(isWordSeparator(input.charAt(i)))
        {
          lastSeparatorIndex = i;
        }
      }
      
      return input.substring(lastSeparatorIndex, input.length()).trim();
    }
    
    /* Helper to cancel the current prediction */
    
  private void CancelPrediction() 
  {
    predictionCanceled = true;
      predictor.PredictionCanceled();
      RestoreInitialSizes();
      FailedPrediction();
  }

  /* Helpers to decide aggressiveness according to user's satisfaction */

  private void FailedPrediction() 
  {
    if(k < 26)
    {
      k++;
      Log.i("DEBUG", "PREDICTOR IS NOW LESS AGGRESSIVE");
    }
    
    continuousSuccesses = 0;
    Log.i("DEBUG", "FAILED PREDICTION");
  }
  
  private void SuccessfullPrediction()
  {
    continuousSuccesses++;
    
    if(continuousSuccesses == AGGRESIVE_THRESHOLD)
    {
      continuousSuccesses = 0;
      
      if(k > 1)
      {
        k--;
        Log.i("DEBUG", "PREDICTOR IS NOW MORE AGGRESSIVE");
      }
    }
    
    Log.i("DEBUG", "SUCCESSFUL PREDICTION");    
  }

  private boolean PredictionFriendlyInput()
  {
    return editorInfo == EditorInfo.TYPE_CLASS_TEXT && autocomplete == false;
  }

}




Java Source Code List

panos.sakkos.softkeyboard.writeright.DataBaseHelper.java
panos.sakkos.softkeyboard.writeright.LatinKeyboardView.java
panos.sakkos.softkeyboard.writeright.LatinKeyboard.java
panos.sakkos.softkeyboard.writeright.Predictor.java
panos.sakkos.softkeyboard.writeright.SoftKeyboard.java
panos.sakkos.softkeyboard.writeright.Statistics.java
panos.sakkos.softkeyboard.writeright.Trie.java