com.strathclyde.highlightingkeyboard.SoftKeyboardService.java Source code

Java tutorial

Introduction

Here is the source code for com.strathclyde.highlightingkeyboard.SoftKeyboardService.java

Source

/******************************************************************************
 * Based on code provided as a Copyright (C) 2008-2009 Google Inc.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Based on code provided as a Copyright 2011 KeyPoint Technologies (UK) Ltd.   
 * All rights reserved. This program and the accompanying materials   
 * are made available under the terms of the Eclipse Public License v1.0  
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: 
 * Andreas Komninos, University of Strathclyde - Additional code implementation
 * http://www.komninos.info
 * http://mobiquitous.cis.strath.ac.uk
 ******************************************************************************/

package com.strathclyde.highlightingkeyboard;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Looper;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.MetaKeyKeyListener;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import android.view.textservice.TextServicesManager;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import org.json.JSONException;
import org.json.JSONObject;

import com.kpt.adaptxt.core.coreapi.KPTParamComponentInfo;
import com.kpt.adaptxt.core.coreapi.KPTParamKeymapId;
import com.strathclyde.corehandler.CoreEngine;
import com.strathclyde.corehandler.CoreEngineInitialize;
import com.strathclyde.corehandler.CoreEngine.KPTSuggestion;
import com.strathclyde.corehandler.CoreEngineInitialize.KPT_SUGG_STATES;
import com.strathclyde.oats.R;
import com.strathclyde.spellchecking.KeyGraph;
import com.strathclyde.spellchecking.SpellForSamsung;

/**
 * Extends the InputMethodService class to provide the keyboard functionality
 * Handles the keyboard view creation and destruction
 * Manages user input and spell-checking
 * Stores user input data
 * Manages the highlighting of text in the editor views
 * Manages audio and haptic feedback
 * @author ako2
 *
 */

public class SoftKeyboardService extends InputMethodService
        implements KeyboardView.OnKeyboardActionListener, SpellCheckerSessionListener {
    static final boolean DEBUG = false;

    /**
     * This boolean indicates the optional example code for performing
     * processing of hard keys in addition to regular text generation
     * from on-screen interaction.  It would be used for input methods that
     * perform language translations (such as converting text entered on 
     * a QWERTY keyboard to Chinese), but may not be used for input methods
     * that are primarily intended to be used for on-screen text entry.
     */
    static final boolean PROCESS_HARD_KEYS = true;

    //keyboard service components
    private LatinKeyboardView mInputView; //whatever keyboard view is currently active
    private CandidateView mCandidateView; //the candidates view
    private CompletionInfo[] mCompletions;
    private LatinKeyboard mCurKeyboard; //the current keyboard
    private InputConnection ic;
    private ExtractedText extr;
    private DBmanager dbm;
    private SpellCheckerSession mScs;
    private TextServicesManager tsm;
    private StringBuilder mComposing = new StringBuilder(); //the current word under composition
    private JSONObject suspectReplacementDistribution; //loaded from JSON file in assets
    private KeyGraph keyModel;
    protected CoreEngine coreEngine;
    int lastKeyboardView;

    //keyboard service parameters
    private boolean mPredictionOn;
    private boolean mCompletionOn;
    private int mLastDisplayWidth;
    private boolean mCapsLock;
    private long mLastShiftTime;
    private long mMetaState;
    private String mWordSeparators;
    private String mSpecialSeparators;
    //keyboard service operational parameters & flags
    private List<String> suggestions = new ArrayList<String>();
    private boolean firstWordSet = false;
    private boolean captureData = true;
    private String composition;
    private String mComposingTemp = "";
    private int wordSeparatorKeyCode;
    private ExtractedText extractedText;
    private HashMap<String, String> autocorrected_words;
    private boolean replacemode = false;
    private boolean updateSuggestionList = false;
    private String origWord;
    private boolean shouldInsertSpace = false;
    private boolean errorInjection = false;
    private int errorInjectionThreshold = 10;
    private boolean errorInjectionSound = true;
    private int startingKeyboard;
    private int big_err, small_err, autocorrect, suggestion;

    //data to be logged
    protected static TypingSession currentSession; //there is one session object - every time a close event happens, the object gets flushed in the DB
    protected static TypingEvent currentEvent;
    protected char lastDeleted;
    protected int lastDeletedPos;
    private int nInjections;
    private String userid;
    private HashMap<Integer, Character> errorMap;

    /**
     * Main initialization of the input method component.  
     * Set up the word separators list
     * Initialize the core service
     * Initialize the colours to be used in highlighting
     * Initialize the list of autocorrected words
     * Load the suspect-replacement probability distribution map
     */
    @Override
    public void onCreate() {
        super.onCreate();

        //get User ID 
        try {
            Class<?> c = Class.forName("android.os.SystemProperties");
            Method get = c.getMethod("get", String.class);
            userid = (String) get.invoke(c, "ro.serialno");
            Log.i("OnCreate", "User id= " + userid);
        } catch (Exception ignored) {
            Log.i("OnCreate", "Could not obtain userid");
            userid = "xxx";
        }
        Editor e = PreferenceManager.getDefaultSharedPreferences(this).edit();
        e.putString("prefUsername", userid);
        e.commit();

        //used for managing injected errors
        errorMap = new HashMap<Integer, Character>();

        mWordSeparators = getResources().getString(R.string.word_separators);
        mSpecialSeparators = getResources().getString(R.string.special_separators);
        CoreEngineInitialize.initializeCoreService(getApplicationContext());
        initializeCore();
        assignColours();
        autocorrected_words = new HashMap<String, String>();
        try {
            suspectReplacementDistribution = new JSONObject(loadJSONFromAsset());
        } catch (JSONException ex) {
            // TODO Auto-generated catch block
            ex.printStackTrace();
        }

        //setup the upload task alarm manager
        /*
         * Twice daily, broadcast an event
         * This will be trapped by our receiver 
         */
        Intent alarmIntent = new Intent(this, UploadDataReceiver.class);
        alarmIntent.putExtra("origin", "alarm");
        alarmIntent.putExtra("insert", true);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.setInexactRepeating(AlarmManager.RTC, Calendar.getInstance().getTimeInMillis(),
                AlarmManager.INTERVAL_HALF_DAY, pendingIntent);
        //Log.i("OnCreate", "Alarm set ");
    }

    /**
     * Prepare the colours to be used in highlighting by loading them from XML
     */
    private void assignColours() {
        big_err = getResources().getColor(R.color.big_error_trans);
        small_err = getResources().getColor(R.color.slight_error_trans);
        //no_err = getResources().getColor(R.color.no_error_trans);
        autocorrect = getResources().getColor(R.color.autocorrect_trans);
        suggestion = getResources().getColor(R.color.suggestion_highlight);

    }

    /**
     * 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() {
        if (mCurKeyboard != 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;
        }

    }

    /**
     * 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.
     * 
     * Inflate the input view
     * Set the listener of the view to this service
     * Initialize OpenAdaptxt core
     */
    @Override
    public View onCreateInputView() {
        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(R.layout.input, null);
        mInputView.setOnKeyboardActionListener(this);
        initializeCore();
        return mInputView;

    }

    /**
     * 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);
        mCandidateView.setService(this);
        //mCandidateView.setLayoutParams(params);
        return mCandidateView;
    }

    /**
     * Check to see what's going on with the services, useful to check if the spell-checker service is active.
     * Probably should remove this, no longer necessary.
     * @return true if the Spellchecker service is running
     */
    private boolean isMyServiceRunning() {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            //Log.i("isService", service.service.getClassName()+",\n"+service.service.getPackageName());
            if (service.service.getClassName().contains("pell")) {
                //Log.i("isService", service.service.getClassName()+",\n"+service.service.getPackageName());
                if (service.service.getClassName().contains("ASpellChecker")) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 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.
     * 
     * Set up edit field behaviour and disable logging if pass/email/url
     * Bind to spell-checking service
     */
    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);

        //get connection to editor field
        ic = getCurrentInputConnection();

        //create a temporary keyboard to obtain the necessary options
        mCurKeyboard = new LatinKeyboard(this, R.xml.qwerty);
        keyModel = new KeyGraph(mCurKeyboard);
        /*display the coordinates of all keys
            
        List<Key> keys = mCurKeyboard.getKeys();
        for (int x=0; x<keys.size(); x++)
        {
           System.out.println(keys.get(x).label+"*"
             +keys.get(x).x+"*"
             +keys.get(x).y+"*"
             +keys.get(x).width+"*"
             +keys.get(x).height);
        }
        */

        // 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.
        mComposing.setLength(0);
        captureData = true;

        //updateCandidates();

        if (!restarting) {
            // Clear shift states.
            mMetaState = 0;
        }

        mPredictionOn = false;
        mCompletionOn = false;
        mCompletions = null;

        // We are now going to initialize our state based on the type of
        // text being edited.
        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;
            startingKeyboard = KeyboardViews.SYMBOLS;
            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;
            startingKeyboard = KeyboardViews.SYMBOLS;
            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;
            startingKeyboard = KeyboardViews.QWERTY_EN;
            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;

            //do not log data from passwords, email addresses, URL fields
            if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
                    || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                    || variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
                // Do not log data
                captureData = false;
                Log.i("OnStartInput", "DANGER FIELD - DO NOT CAPTURE");
            }

            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.
                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.
                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.
                mPredictionOn = false;
                mCompletionOn = isFullscreenMode();
            }

            // 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;
            startingKeyboard = KeyboardViews.QWERTY_EN;
            updateShiftKeyState(attribute);
        }

        // Update the label on the enter key, depending on what the application
        // says it will do.
        if (mInputView != null) {
            //Log.i("onStartInput","Setting existing keyboard");
            ((LatinKeyboard) mInputView.getKeyboard()).setImeOptions(getResources(), attribute.imeOptions);
        } else {
            //Log.i("onStartInput","Setting temp keyboard");
            mCurKeyboard.imeOptions = attribute.imeOptions;
            //mCurKeyboard.shifted=attribute.initialCapsMode;
        }

        //bind to the spell checking service
        tsm = (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);

        //Log.i("OnStartInput", "ID:"+attribute.fieldId+" "+attribute.fieldName);

    }

    //this is to ensure that the candidate view does not eat into the application space!
    @Override
    public void onComputeInsets(InputMethodService.Insets outInsets) {
        super.onComputeInsets(outInsets);
        if (!isFullscreenMode()) {
            outInsets.contentTopInsets = outInsets.visibleTopInsets;
        }
    }

    /**
     * Finish the current session
     * Set session end time
     * Record high, low errors, suggestions picked
     * Dump session data to db
     */
    public void endSession() {

        long endtime = System.currentTimeMillis();
        //Log.i("Session End", ""+endtime/1000);

        if (currentSession != null) {
            currentSession.end_time = endtime;
            currentSession.user = userid;

            if (currentSession.events.size() <= 0) {
                //Log.i("Session End","No Events\n Event Dump follows");
                //currentSession.printall();
                currentSession = null;
                return;
            }
            //if the last event was a backspace add the deleted char to the suspects
            if (currentSession.events.size() > 0 && currentSession.events
                    .get(currentSession.events.size() - 1).keyCode == Keyboard.KEYCODE_DELETE) {
                currentSession.suspects.add(lastDeleted);
                //System.out.println("Suspect = "+lastDeleted);
            }

            try {

                if (!firstWordSet)
                    currentSession.getFirstWord((String) extr.text, composition);
            } catch (Exception e) {
                System.out.println("Error getting current text");
            }

            //clear out all the characters
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
            if (sp.getBoolean("fullLogging", false) == false) {
                for (int x = 0; x < currentSession.events.size(); x++) {
                    //don't record anything but backspaces
                    if (currentSession.events.get(x).keyCode != -5) {
                        currentSession.events.get(x).keyCode = -400;
                        currentSession.events.get(x).keyChar = '$';
                    }
                }
            }

            //Log.i("Session End","Event Dump follows");
            currentSession.end_time = endtime;
            //currentSession.nHighErrors=nHighErrors;
            //currentSession.nLowErrors=nLowErrors;
            //currentSession.nSuggestionsPicked=nSuggestionsPicked;
            //currentSession.nInjections=nInjections;
            currentSession.printall();
            dbm.insert(currentSession);
            //Log.i("Session End",""+currentSession.end_time);
            currentSession = null;
            extr = null;
            firstWordSet = false;
            composition = null;
            if (coreEngine != null)
                coreEngine.resetCoreString();
            dbm.close();
            if (ic != null)
                ic.finishComposingText();
        }
    }

    /**
     * This is called when the user is done editing a field.  We can use
     * this to reset our state.
     */
    @Override
    public void onFinishInput() {
        Log.i("Finish Input", "INPUT FINISHED");

        super.onFinishInput();
        clearDots();

        // Clear current composing text and candidates.
        mComposing.setLength(0);
        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);

        mCurKeyboard = null;
        if (mInputView != null) {
            mInputView.closing();
        }

    }

    /**
     * This method is called when the keyboard is shown
     * 
     * reset errors & suggestion picked counters
     * get dots and colourbar prefs
     * get injection prefs
     * update engine core prefs
     * prepare database for writing
     * create a new typing session
     * apply selected keyboard to input view
     * switch core engine dictionaries according to keyboard
     */
    @Override
    public void onStartInputView(EditorInfo attribute, boolean restarting) {

        //Log.i("OnStartInputView","Keyboard about to be shown");
        nInjections = 0;

        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        mInputView.dots = sharedPrefs.getBoolean("dots", false);
        mInputView.colorbar = sharedPrefs.getString("colorbar", "top");
        mInputView.ycoords.clear();
        mInputView.xcoords.clear();
        mInputView.resetBackground();

        errorInjection = sharedPrefs.getBoolean("errorinjection", false);
        errorInjectionThreshold = Integer.parseInt(sharedPrefs.getString("injectionThreshold", "20"));
        errorInjectionSound = sharedPrefs.getBoolean("errorinjectionsound", true);

        if (coreEngine != null)
            updateCorePrefs();

        if (currentSession != null) {
            endSession();
        }

        //open the database for writing
        if (dbm == null)
            dbm = new DBmanager(getApplicationContext());
        dbm.open();

        //get connection to editor field
        //ic = getCurrentInputConnection();

        if (extractedText != null) {
            if (ic.deleteSurroundingText(9999, 0)) {
                ic.commitText(extractedText.text, 1);
                //extractedText=null;
                //Log.i("onStartInputView", "Text Replaced");   
            } else {
                //Log.i("onStartInputView", "IC not valid");
            }
        }

        //create a new typing session
        UserPreferences up = new UserPreferences();
        up.autocorrect = (sharedPrefs.getBoolean("autocorrect", true)) ? 1 : 0;
        up.sound = (sharedPrefs.getBoolean("audio", false)) ? 1 : 0;
        up.haptic = (sharedPrefs.getBoolean("vibrator", false)) ? 1 : 0;
        up.visual = (sharedPrefs.getBoolean("highlightwords", true)) ? 1 : 0;
        up.sugg_highlight = (sharedPrefs.getBoolean("suggestion_highlight", false)) ? 1 : 0;
        up.dots = (sharedPrefs.getBoolean("dots", false)) ? 1 : 0;

        currentSession = new TypingSession(up);
        currentSession.sess_height = mInputView.getHeight();
        currentSession.sess_width = mInputView.getWidth();
        currentSession.events.add(new TypingEvent(1, "Keyboard shown"));
        currentSession.user = userid;

        //find out what application has invoked the keyboard
        ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        if (android.os.Build.VERSION.SDK_INT < 21) //works only on Android <5, retrieve the app name
        {
            RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();
            PackageManager pm = this.getPackageManager();
            PackageInfo foregroundAppPackageInfo;
            try {
                foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
                currentSession.app = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();
                //Log.i("OnStartInputView", "ForeGround app is "+foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString());
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        } else //retrieve the package name
        {
            List<RunningAppProcessInfo> ps = am.getRunningAppProcesses();
            //Log.i("OnStartInput", "Running apps "+ps.size());
            for (int x = 0; x < ps.size(); x++) {
                RunningAppProcessInfo p = ps.get(x);
                //Log.i("OnStartInput", "App is "+p.processName+p.importance);

                if (p.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    //Log.i("OnStartInput", "ForeGround app is "+p.processName);
                    currentSession.app = p.processName;
                    break; //the first one is the foreground app
                }
            }
        }

        // Apply the selected keyboard to the input view.            
        //set the new keyboard options based on the temporary one created during OnStartInput
        if (mCurKeyboard != null) {
            //Log.i("onStartInputView","setting new keyboard to temp settings");
            mInputView.setShifted(mCurKeyboard.isShifted());
            mInputView.imeOptions = mCurKeyboard.imeOptions;

        } else {
            //Log.i("onStartInputView","mCurKeyboard is null");
        }

        mInputView.currentKeyboard = startingKeyboard;
        mInputView.switchKeyboard();

        if (coreEngine != null) {
            switch (mInputView.currentKeyboard) {
            case KeyboardViews.QWERTY_EL:
                coreEngine.activateLanguageKeymap(131092, null);
                coreEngine.setDictionaryPriority(131092, 0);
                break;
            case KeyboardViews.QWERTY_EN:
                coreEngine.activateLanguageKeymap(131081, null);
                coreEngine.setDictionaryPriority(131092, 1);
                break;
            }

            coreEngine.resetCoreString();
            updateCandidates();
            //KPTkeymapInfo();

        }

        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) {

        // Log.i("onUpdateSelection", "Cursor Moved");
        // 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);
            updateCandidates();
            if (ic != null) {
                ic.finishComposingText();
            }
        }

        ExtractedText alltext = ic.getExtractedText(new ExtractedTextRequest(), 0);

        //special case, there is nothing in the editor (been deleted)
        if (alltext == null)
            return;
        //special case - the text in the editor has all been deleted but the ic is still active
        if (newSelStart == 0 && alltext.text.length() == 0) {
            coreEngine.resetCoreString();
            updateCandidates();
            return;
        }
        try {
            if (newSelEnd - newSelStart == 0 && newSelEnd < alltext.text.length() - 1) //only if cursor movement, not actual selection, and we are not at the end of the text
            {
                //Log.i("Selection Update", "Old: "+oldSelStart+","+oldSelEnd+"...New: "+newSelStart+","+newSelEnd);

                WordDetails w = findWord(newSelStart, alltext.text); //find the current word

                //Log.i("Selection Update", "WordStart, End = "+w.wordStart+","+w.wordEnd);
                if (w.wordStart >= 0 && w.wordEnd >= 0 && w.wordStart < w.wordEnd) {

                    w.word = alltext.text.toString().substring(w.wordStart, w.wordEnd);
                    //Log.i("Selection Update","\nCurrent Word: ["+w.word+"]");

                    //check if current word has been autocorrected
                    //Log.i("Selection Update","Original word was "+autocorrected_words.get(w.word));
                    replacemode = true;
                    //ic.setComposingRegion(w.wordStart, w.wordEnd); //mark this as composing - any key input will erase the word

                    if (autocorrected_words.containsKey(w.word)) //a word that was autocorrected or highlighted as a mistake
                    {
                        //Log.i("Selection Update","Original word was "+autocorrected_words.get(w.word));
                        updateCandidatesWithSpellChecker(autocorrected_words.get(w.word));
                        //show candidates
                    } else //not a mistake word, so just use adaptxt for suggestions
                    {
                        //Log.i("Selection Update","Not a mistake Word");
                        coreEngine.resetCoreString();
                        coreEngine.insertText(w.word);
                        updateCandidates();
                    }

                }

            } else {
                //Log.i("Selection Update", "Old: "+oldSelStart+","+oldSelEnd+"...New: "+newSelStart+","+newSelEnd);
                replacemode = false;
                if (newSelEnd == alltext.text.length() && newSelEnd - oldSelEnd > 2) {
                    //Log.i("Selection Update","At sentence end");
                    ic.finishComposingText();
                    coreEngine.resetCoreString();
                    updateCandidates();

                }
            }
        } catch (Exception e) {
            //Log.i("onUpdateSelection", "Failed to get extracted text");
        }

        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);

    }

    /**
     * Find out the position of a word in the text being input
     * @param cursor starting position in the text from which to begin the search (backwards)
     * @param text the text having been input so far
     * @return a WordDetails object with the start and end position of a given word
     */
    public WordDetails findWord(int cursor, CharSequence text) {
        WordDetails word = new WordDetails();

        //loop backwards to find start of current word
        for (int x = cursor - 1; x >= 0; x--) {
            if (x == 0) {
                word.wordStart = x;
                break;
            } else if (isWordSeparator(text.charAt(x))) {
                word.wordStart = x + 1;
                break;
            }
        }
        //loop forwards to find end of current word
        for (int x = cursor - 1; x < text.length(); x++) {
            if (x < 0)
                break;
            if (isWordSeparator(text.charAt(x)) || x == text.length() - 1) {
                word.wordEnd = x;
                break;
            }
        }

        return word;
    }

    /**
     * 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) {
        if (mCompletionOn) {
            mCompletions = completions;
            if (completions == null) {
                setSuggestions(null, false, false);
                return;
            }

            List<String> stringList = new ArrayList<String>();
            for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
                CompletionInfo ci = completions[i];
                if (ci != null)
                    stringList.add(ci.getText().toString());
            }
            setSuggestions(stringList, true, true);
        }
    }

    /**
     * This translates incoming hard key events in to edit operations on an
     * InputConnection.  It is only needed when using the
     * PROCESS_HARD_KEYS option.
     */
    private boolean translateKeyDown(int keyCode, KeyEvent event) {
        mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, keyCode, event);
        int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState));
        mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState);
        //InputConnection ic = ic;
        if (c == 0 || ic == null) {
            return false;
        }

        if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
            c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
        }

        if (mComposing.length() > 0) {
            char accent = mComposing.charAt(mComposing.length() - 1);
            int composed = KeyEvent.getDeadChar(accent, c);

            if (composed != 0) {
                c = composed;
                mComposing.setLength(mComposing.length() - 1);
            }
        }

        onKey(c, null);

        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 onKeyDown(int keyCode, KeyEvent event) {

        //event.
        //Log.i("OnKeyDown", "Keycode: "+keyCode);
        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 -2: //123 button
            //Log.i("KeyDown", "Keycode: "+keyCode);
            event.startTracking();
            return true;

        case KeyEvent.KEYCODE_ENTER:
            // Let the underlying text editor always handle these.
            return false;

        default:
            // For all other keys, if we want to do transformations on
            // text being entered with a hard keyboard, we need to process
            // it and do the appropriate action.
            if (PROCESS_HARD_KEYS) {
                if (keyCode == KeyEvent.KEYCODE_SPACE && (event.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
                    // A silly example: in our input method, Alt+Space
                    // is a shortcut for 'android' in lower case.
                    //InputConnection ic = ic;
                    if (ic != null) {
                        // First, tell the editor that it is no longer in the
                        // shift state, since we are consuming this.
                        ic.clearMetaKeyStates(KeyEvent.META_ALT_ON);
                        keyDownUp(KeyEvent.KEYCODE_A);
                        keyDownUp(KeyEvent.KEYCODE_N);
                        keyDownUp(KeyEvent.KEYCODE_D);
                        keyDownUp(KeyEvent.KEYCODE_R);
                        keyDownUp(KeyEvent.KEYCODE_O);
                        keyDownUp(KeyEvent.KEYCODE_I);
                        keyDownUp(KeyEvent.KEYCODE_D);
                        // And we consume this event.
                        return true;
                    }
                }
                if (mPredictionOn && translateKeyDown(keyCode, event)) {
                    return true;
                }
            }
        }

        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) {
        // If we want to do transformations on text being entered with a hard
        // keyboard, we need to process the up events to update the meta key
        // state we are tracking.
        if (PROCESS_HARD_KEYS) {
            if (mPredictionOn) {
                mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, keyCode, 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);
            updateCandidates();
        }
    }

    /**
     * Helper function to commit any text being composed in the editor
     * @param inputConnection our current connection with the editor
     * @param isWordSeparator 
     */
    private void commitTyped(InputConnection inputConnection, boolean isWordSeparator) {
        if (mComposing.length() > 0) {

            //do some spell-checking
            mComposingTemp = mComposing.toString();
            extr = ic.getExtractedText(new ExtractedTextRequest(), 0);

            WordDetails w = findWord(extr.selectionStart, extr.text);
            if (w.wordStart >= 0 && w.wordEnd >= 0 && w.wordStart < w.wordEnd) {
                w.word = extr.text.toString().substring(w.wordStart, extr.selectionStart);
                System.out.println("Cursor Position = " + extr.selectionStart + " Word=" + w.word);
                if (w != null && mComposingTemp != null) {
                    if (w.word.length() != mComposingTemp.length()) {
                        mComposingTemp = w.word;
                        if (captureData)
                            ic.setComposingRegion(w.wordStart, extr.selectionStart);
                    }
                }

            }

            if (captureData) // don't want to be spell-checking on urls, emails, passwords
            {
                if (mScs != null) //get some suggestions
                {
                    //Log.i("CommitTyped", "About to spellcheck "+mComposingTemp);                 
                    mScs.getSuggestions(new TextInfo(mComposingTemp), 5);

                } else //handle spelling for Samsung devices
                {
                    //Log.e("CommitTyped", "mScs NULL");
                    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
                    String lang = prefs.getString("available_dicts", "el");
                    XmlResourceParser p;
                    if (lang.equals("en"))
                        p = getResources().getXml(R.xml.qwerty);
                    else
                        p = getResources().getXml(R.xml.greekqwerty);

                    SpellForSamsung sp = new SpellForSamsung(getAssets(), p,
                            getFilesDir() + File.separator + "data", lang, 5);
                    SuggestionsInfo si[] = new SuggestionsInfo[1];
                    si[0] = sp.spell(mComposingTemp);
                    onGetSuggestions(si);
                }
            } else {
                //Log.i("CommitTyped", "Will not spellcheck in an inappropriate field, mComposing = "+mComposingTemp);
                inputConnection.commitText(mComposingTemp, mComposingTemp.length());
            }

        }
        mComposing.setLength(0);
        updateCandidates();

    }

    /**
     * Helper to update the shift state of our keyboard based on the initial
     * editor state.
     */
    private void updateShiftKeyState(EditorInfo attr) {
        if (mInputView != null) {
            if (attr != null
                    //&& mQwertyKeyboard == mInputView.getKeyboard()) {
                    && (mInputView.currentKeyboard == KeyboardViews.QWERTY_EL
                            || mInputView.currentKeyboard == KeyboardViews.QWERTY_EN)) {
                int caps = 0;
                EditorInfo ei = getCurrentInputEditorInfo();
                if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
                    caps = ic.getCursorCapsMode(attr.inputType);
                }
                mInputView.setShifted(mCapsLock || caps != 0);
            }
        } else {
            int caps = 0;
            EditorInfo ei = getCurrentInputEditorInfo();
            if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
                caps = ic.getCursorCapsMode(attr.inputType);
            }
            mCurKeyboard.setShifted(mCapsLock || caps != 0);
        }
    }

    /**
     * Helper to determine if a given character code is alphabetic.
     */
    private boolean isAlphabet(int code) {
        if (Character.isLetter(code)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Helper to send a key down / key up pair to the current editor.
     */
    private void keyDownUp(int keyEventCode) {
        ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
        ic.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 {
                ic.commitText(String.valueOf((char) keyCode), 1);
                if (shouldInsertSpace) {
                    ic.commitText(" ", 1);
                    shouldInsertSpace = false;
                }

            }
            break;
        }
    }

    /**
     * Remove all touch history events
     */
    public void clearDots() {
        if (mInputView != null) {
            if (!mInputView.xcoords.isEmpty())
                mInputView.xcoords.clear();
            if (!mInputView.ycoords.isEmpty())
                mInputView.ycoords.clear();
        }
    }

    /**
     * Helper function to read the suspect character and replacement probability distributions from a JSON object
     * @return
     */
    public String loadJSONFromAsset() {
        String json = null;
        try {

            InputStream is = getAssets().open("keyJSON.txt");
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            json = new String(buffer, "UTF-8");

        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        return json;
    }

    // Implementation of KeyboardViewListener

    /**
     * Manages actual input into the editor. Here we:
     * implement our injection algorithm as required
     * store data relating to the key press
     * initiate any spell checking as required
     */
    public void onKey(int primaryCode, int[] keyCodes) {

        // touches all done, add the chars to the event and then the event to the session       
        currentEvent.keyCode = primaryCode;

        if (errorInjection && primaryCode >= 32) {
            //give a n% chance of the key being modified
            Random r = new Random();
            int res = r.nextInt(100);
            if (res <= errorInjectionThreshold) //%n chance of key being modified
            {
                //Log.i("OnKey", "Will modify");
                try {
                    //for each combination in the model, find the eucleidian distance and the replacement freq
                    JSONObject targetObj = suspectReplacementDistribution
                            .getJSONObject(Integer.toString(primaryCode));
                    Iterator<?> keys = targetObj.keys();
                    ArrayList<Character> list = new ArrayList();
                    while (keys.hasNext()) {
                        String key = (String) keys.next();
                        int freq = targetObj.getInt(key);
                        //if the frequency is 0, add the suspect as a replacement candidate
                        double dist = keyModel.distance2(primaryCode, Integer.parseInt(key));

                        if (dist > 0) {
                            if (dist > 2.0) //fix it so that only nearby keys have a chance of being elected
                                dist = 100;
                            //add to the list of candidates as many times as required if specific freq>0;
                            int sfreq = (int) Math.round(freq / dist);
                            //Log.i("Test", "Freq/Dist to "+key+": "+freq+"/"+dist+" final prob: "+sfreq);

                            if (sfreq == 0) //add the suspect as a replacement candidate
                            {
                                list.add(Character.toChars(primaryCode)[0]);
                            } else //add the other replacement candidates as required
                            {
                                for (int x = 0; x < targetObj.getInt(key); x++) {
                                    list.add(Character.toChars(Integer.parseInt(key))[0]);

                                }
                            }
                        }
                    }
                    //Log.i("OnKey", "Replace list size: "+list.size());

                    Random x = new Random();
                    int sel = x.nextInt(list.size());

                    //if the replacement eventually happens
                    if ((int) list.get(sel) != primaryCode) {

                        if (errorInjectionSound) {
                            final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
                            tg.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE);
                        }
                        //primaryCode = (int)list.get(sel);

                        //Log.w("OnKey", "Replace "+Character.toChars(primaryCode)[0]+" with "+list.get(sel));
                        errorMap.put(mComposing.length(), (char) (int) list.get(sel)); //put in our current position and the replacement
                        //nInjections++;      
                    } else
                        Log.i("OnKey", "Replacement will not happen, same key selected");

                } catch (JSONException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else {
                //Log.i("OnKey", "Will not modify, r="+res);
            }
        }
        //switch adaptxt language if necessary
        if (coreEngine != null) {
            switch (mInputView.currentKeyboard) {
            case KeyboardViews.QWERTY_EL:
                coreEngine.activateLanguageKeymap(131092, null);
                coreEngine.setDictionaryPriority(131092, 0);
                break;
            case KeyboardViews.QWERTY_EN:
                coreEngine.activateLanguageKeymap(131081, null);
                coreEngine.setDictionaryPriority(131092, 1);
                break;
            }
        }

        //get the full inputted text
        extr = ic.getExtractedText(new ExtractedTextRequest(), 0);

        if (currentEvent != null && captureData == true) {

            //Log.i("OnKey", "OK to capture data!");
            currentEvent.user = userid;

            if (primaryCode > 0)
                currentEvent.keyChar = (char) primaryCode;

            //handle the booleans
            if (currentSession.events.get(currentSession.events.size() - 1).keyChar == ' ') //space
            {
                currentEvent.followsSpace = true;
            }

            if (currentEvent.keyCode == Keyboard.KEYCODE_DELETE) {
                System.out.println("Backspace Pressed!");

                //if a delete is pressed after another delete
                //and its cursor position is not -1 from the previous delete
                //we must commit the previous deletion as a suspect character.
                if (currentSession.events.get(currentSession.events.size() - 1).keyCode == Keyboard.KEYCODE_DELETE
                        && extr.selectionStart != lastDeletedPos - 1) {
                    currentSession.suspects.add(lastDeleted);
                    //System.out.println("Suspect = "+lastDeleted);
                }

                //get all the text before the backspace press and the current cursor position
                if (extr.selectionStart > 0) {
                    lastDeleted = extr.text.charAt(extr.selectionStart - 1);
                    lastDeletedPos = extr.selectionStart;
                    //System.out.println("Deleted = "+lastDeleted+"\nCursor Position = "+extr.selectionStart);
                }
            }

            //if the current key is NOT a backspace but the previous one was
            if (currentEvent.keyCode != Keyboard.KEYCODE_DELETE && currentSession.events
                    .get(currentSession.events.size() - 1).keyCode == Keyboard.KEYCODE_DELETE) {
                currentSession.suspects.add(lastDeleted);
                //System.out.println("Suspect = "+lastDeleted);

            }

        }

        //do the handling     
        if (isWordSeparator(primaryCode)) {
            // Handle separator
            //System.out.println("Detected a word separator \""+primaryCode+"\"");
            if (primaryCode != 32) {
                shouldInsertSpace = false;
                if (extr.text.length() > 0) {
                    //Log.i("On Key ", "last letter after separator was ["+extr.text.charAt(extr.selectionStart-1)+"]");
                    //check if the previous char was a space, if so delete it.
                    if (extr.text.charAt(extr.selectionStart - 1) == ' ' && !isSpecialSeparator(primaryCode)) //detecting if the current char is not part of a smiley face
                    {
                        onKey(-5, null);
                    }
                }
            }

            //clear the touch history
            clearDots();

            //ensure spell checker is using correct language          
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

            Editor ed = prefs.edit();
            if (captureData) {
                if (mInputView.currentLang == 1) //english
                {
                    ed.putString("available_dicts", "en");
                    ed.commit();
                    if (mScs != null)
                        mScs.close();
                    String lang = prefs.getString("available_dicts", "jam");
                    Log.i("OnKey", "Spellcheck lang set to " + lang);
                    Locale english = new Locale("en", "GB");
                    mScs = tsm.newSpellCheckerSession(null, english, this, false);
                    if (mScs == null)
                        Log.e("OnKey", "Failed to obtain spell-checker session");
                } else {
                    ed.putString("available_dicts", "el");
                    ed.commit();
                    if (mScs != null)
                        mScs.close();
                    String lang = prefs.getString("available_dicts", "jam");
                    Log.i("OnKey", "Spellcheck lang set to " + lang);
                    Locale greek = new Locale("el", "GR");
                    mScs = tsm.newSpellCheckerSession(null, greek, this, false);
                    if (mScs == null)
                        Log.e("OnKey", "Failed to obtain spell-checker session");
                }
            }

            //handle space for Adaptxt
            if (Character.isSpaceChar(primaryCode)) {
                if (coreEngine != null) {
                    //Log.i("Handle Character", "Space pressed");
                    coreEngine.resetCoreString();
                    //Log.i("Space Pressed", "Word is "+mComposing+" ");
                    coreEngine.insertText(mComposing.toString() + " ");
                    updateCandidates();
                }
            }

            if (!firstWordSet && mComposing.length() > 1) {
                if (captureData)
                    currentSession.firstWord = mComposing.toString();
                else
                    currentSession.firstWord = "$$$$";

                firstWordSet = true;
                //System.out.println("First Word\""+mComposing.toString()+"\"");
            }

            //effect any injections as required
            if (mComposing.length() > 0) {
                //commitTyped(getCurrentInputConnection());
                //check the errormap for any replacements 
                if (errorMap.size() > 0) {

                    //restrict the errormap to the 25% of word length cap

                    int replacementstodelete = errorMap.size() - (int) Math.round(mComposing.length() * 0.25); //total replacements - those to keep
                    if (replacementstodelete < 0)
                        replacementstodelete = 0;
                    //allow at least one
                    if (errorMap.size() == replacementstodelete)
                        replacementstodelete = errorMap.size() - 1;

                    if (replacementstodelete > 0) {
                        List<Integer> keys = new ArrayList<Integer>(errorMap.keySet());

                        for (int z = 0; z < replacementstodelete; z++) {
                            Random random = new Random();
                            int listposition = random.nextInt(keys.size());
                            int randomKey = keys.get(listposition);
                            //remove this from the error map and the list
                            errorMap.remove(randomKey);
                            keys.remove(listposition);

                        }
                    }

                    //effect the injections
                    String oldmComposing = mComposing.toString();
                    Iterator it = errorMap.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry pair = (Map.Entry) it.next();
                        mComposing.replace((Integer) pair.getKey(), (Integer) pair.getKey() + 1,
                                "" + (Character) pair.getValue());
                        //it.remove(); // avoids a ConcurrentModificationException
                    }
                    nInjections += errorMap.size();
                    currentSession.nInjections = nInjections;
                    //Log.i("Injections", "Will replace "+oldmComposing+" with "+mComposing+", nInjections="+nInjections);
                    errorMap.clear();
                }

                wordSeparatorKeyCode = primaryCode;
                if (captureData)
                    commitTyped(ic, isWordSeparator(primaryCode));
                else {
                    if (primaryCode != Keyboard.KEYCODE_DONE && primaryCode != 10) //done and go/enter
                        handleCharacter(primaryCode, keyCodes);
                    else
                        sendKey(primaryCode);
                    commitTyped(ic);
                }
            } else {
                sendKey(primaryCode);
            }

            updateShiftKeyState(getCurrentInputEditorInfo());

        } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
            if (errorMap.get(mComposing.length() - 1) != null) {
                //Log.i("Injection", "Delete from map pos="+(mComposing.length()-1)+", char="+errorMap.get(mComposing.length()-1));
                errorMap.remove(mComposing.length() - 1);
            }

            handleBackspace();
        } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
            handleShift();
        } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { //keyboard hiding button
            //override this for settings activity
            //handleClose();
            Intent intent = new Intent(this, LoggingIMESettings.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
            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 (mInputView.currentKeyboard == KeyboardViews.SYMBOLS
                    || mInputView.currentKeyboard == KeyboardViews.SYMBOLS_SHIFTED) {
                //mInputView.currentKeyboard = KeyboardViews.QWERTY_EN;
                mInputView.currentKeyboard = lastKeyboardView;
            } else { //about to change to symbols
                lastKeyboardView = mInputView.currentKeyboard; //keep track of where we came from
                mInputView.currentKeyboard = KeyboardViews.SYMBOLS;
            }
            mInputView.switchKeyboard();
            if (mInputView.currentKeyboard == KeyboardViews.SYMBOLS) {
                mInputView.getKeyboard().setShifted(false);
            }
        } else {
            handleCharacter(primaryCode, keyCodes);
        }
    }

    /**
     * Retrieves key map information from AdapTxt core
     */
    public void KPTkeymapInfo() {
        KPTParamKeymapId[] keymapParamIds = coreEngine.getAvailableKeymaps();
        for (int x = 0; x < keymapParamIds.length; x++) {
            Log.i("KPTKeymapInfo", "Available " + keymapParamIds[x].getLanguage().getLanguage() + ", keyMapLangid="
                    + keymapParamIds[x].getLanguageId() + ", langId" + keymapParamIds[x].getLanguage().getId());
        }

        KPTParamComponentInfo[] components = coreEngine.getAvailableComponents();
        for (int x = 0; x < components.length; x++) {
            if (components[x].getComponentType() == KPTParamComponentInfo.KPT_COMPONENT_TYPE_DICTIONARY)
                Log.i("KPTComponentInfo",
                        "Available id=" + components[x].getComponentId() + ", type="
                                + components[x].getComponentType() + ", name="
                                + components[x].getExtraDetails().getDictDisplayName() + ", priority="
                                + components[x].getExtraDetails().getDictPriority());
        }

    }

    public void onText(CharSequence text) {
        if (ic == null)
            return;
        ic.beginBatchEdit();
        if (mComposing.length() > 0) {
            commitTyped(ic);
        }
        ic.commitText(text, 0);
        ic.endBatchEdit();
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    /**
     * Update the list of available candidates from the current composing
     * text, using the AdapTxt core
     */
    private void updateCandidates() {

        if (coreEngine != null) {
            //get the KPT suggestions
            List<KPTSuggestion> suglist = coreEngine.getSuggestions();
            suggestions.clear();
            for (int i = 0; i < suglist.size(); i++) {
                String sug = suglist.get(i).getsuggestionString();
                if (sug != null) {
                    suggestions.add(sug);
                    //Log.i("Suggestion "+i, sug+"-"+suglist.get(i).getsuggestionType());
                }

            }

            if (!mCompletionOn) {
                if (mComposing.length() >= 0) {
                    ArrayList<String> list = new ArrayList<String>();
                    list.add(mComposing.toString());
                    setSuggestions(list, true, true);
                    setSuggestions(suggestions, true, true);
                } else {
                    setSuggestions(null, false, false);
                }
            }
        }
    }

    /**
     * Update the list of available candidates using the spell-checker (for when a user moves the cursor within a word)
     * @param word the word to pass into the spell checker
     */
    public void updateCandidatesWithSpellChecker(String word) {
        if (mScs != null) {
            origWord = word;
            mScs.getSuggestions(new TextInfo(word), 5);
            updateSuggestionList = true;
        } else //handle this for Samsung devices
        {
            origWord = word;
            //Log.e("UpdateCandidatesWithSpell", "mScs NULL");
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
            String lang = prefs.getString("available_dicts", "el");
            XmlResourceParser p;
            if (lang.equals("en"))
                p = getResources().getXml(R.xml.qwerty);
            else
                p = getResources().getXml(R.xml.greekqwerty);

            SpellForSamsung sp = new SpellForSamsung(getAssets(), p, getFilesDir() + File.separator + "data", lang,
                    5);
            SuggestionsInfo si[] = new SuggestionsInfo[1];
            si[0] = sp.spell(word);
            updateSuggestionList = true;
            onGetSuggestions(si);
            //Log.e("updateCandsWithSpell", "mScs NULL");
        }
    }

    /**
     * Pass the suggestions in to the suggestion bar view
     * @param corrections
     */
    public void updateSuggestionListWithSpellChecker(List<String> corrections) {
        corrections.add(0, origWord);
        suggestions.clear();
        suggestions = corrections;
        setSuggestions(corrections, true, true);
    }

    /**
     * draw the suggestions into the suggestion bar
     * @param suggestions
     * @param completions
     * @param typedWordValid
     */
    public void setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid) {
        if (suggestions != null && suggestions.size() > 0) {
            setCandidatesViewShown(true);
        } else if (isExtractViewShown()) {
            setCandidatesViewShown(true);
        }
        if (mCandidateView != null) {
            mCandidateView.setSuggestions(suggestions, completions, typedWordValid);
        }
    }

    /**
     * Handle a press of the backspace key
     */
    private void handleBackspace() {

        coreEngine.removeString(true, 1);

        final int length = mComposing.length();
        if (length > 1) {
            mComposing.delete(length - 1, length);
            ic.setComposingText(mComposing, 1);
            updateCandidates();
        } else if (length > 0) {
            mComposing.setLength(0);
            ic.commitText("", 0);
            updateCandidates();
        } else {
            keyDownUp(KeyEvent.KEYCODE_DEL);
        }
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    /**
     * Handle a press of the shift key
     */
    private void handleShift() {
        if (mInputView == null) {
            return;
        }

        //Keyboard currentKeyboard = mInputView.getKeyboard();
        if (mInputView.currentKeyboard == KeyboardViews.QWERTY_EL
                || mInputView.currentKeyboard == KeyboardViews.QWERTY_EN) {
            // Alphabet keyboard
            //Log.i("Handle Shift", "it is pressed");
            checkToggleCapsLock();
            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
        } else if (mInputView.currentKeyboard == KeyboardViews.SYMBOLS) {
            mInputView.getKeyboard().setShifted(true);
            mInputView.currentKeyboard = KeyboardViews.SYMBOLS_SHIFTED;
            mInputView.switchKeyboard();
            mInputView.getKeyboard().setShifted(true);
        } else if (mInputView.currentKeyboard == KeyboardViews.SYMBOLS_SHIFTED) {
            mInputView.getKeyboard().setShifted(false);
            mInputView.currentKeyboard = KeyboardViews.SYMBOLS;
            mInputView.switchKeyboard();
            mInputView.getKeyboard().setShifted(false);
        }
    }

    /**
     * Handle the input of any normal character
     * @param primaryCode the button key code
     * @param keyCodes the characters associated with the button key code
     */
    private void handleCharacter(int primaryCode, int[] keyCodes) {
        if (isInputViewShown()) {
            if (mInputView.isShifted()) {
                primaryCode = Character.toUpperCase(primaryCode);
                //Log.i("Handle Character", "it is "+(char)primaryCode);
            }
        }
        if (isAlphabet(primaryCode) && mPredictionOn) {

            if (coreEngine != null)
                coreEngine.addChar((char) primaryCode, false, false, false);

            mComposing.append((char) primaryCode);
            ic.setComposingText(mComposing, 1);
            updateShiftKeyState(getCurrentInputEditorInfo());
            updateCandidates();
        } else {
            //Log.i("handleCharacter", "adding "+(char) primaryCode+" to mComposing");
            mComposing.append((char) primaryCode);
            ic.setComposingText(mComposing, 1);
        }
    }

    /**
     * Handle shutting down of the keyboard
     */
    private void handleClose() {
        //Log.i("Keyboard hiding", ""+System.currentTimeMillis()/1000);
        composition = mComposing.toString();
        commitTyped(ic);
        requestHideSelf(0);
        mInputView.closing();
    }

    /**
     * Helper to manage the toggling of the caps lock function of the shift key
     */
    private void checkToggleCapsLock() {
        long now = System.currentTimeMillis();
        if (mLastShiftTime + 800 > now) {
            mCapsLock = !mCapsLock;
            mLastShiftTime = 0;
        } else {
            mLastShiftTime = now;
        }
    }

    /**
     * get the word separators
     * @param special
     * @return
     */
    private String getWordSeparators(boolean special) {
        if (special)
            return mSpecialSeparators;
        else
            return mWordSeparators;
    }

    /**
     * check if a character input is a word separator
     * @param code the input character
     * @return true if it is a word separator, else return false
     */
    public boolean isWordSeparator(int code) {
        String separators = getWordSeparators(false);
        return separators.contains(String.valueOf((char) code));
    }

    /**
     * check if a character is a special separator
     * @param code the input character
     * @return true if it is a special word separator, else return false
     */
    public boolean isSpecialSeparator(int code) {
        String separators = getWordSeparators(true);
        return separators.contains(String.valueOf((char) code));
    }

    /**
     * helper to pick the best available suggestion
     */
    public void pickDefaultCandidate() {
        pickSuggestionManually(0);
    }

    /**
     * Handles the manual selection of suggestions from the suggestion bar
     * @param index the position of the suggestion in the suggestion list
     */
    public void pickSuggestionManually(int index) {
        if (mCompletionOn && mCompletions != null && index >= 0 && index < mCompletions.length) {
            CompletionInfo ci = mCompletions[index];
            ic.commitCompletion(ci);
            if (mCandidateView != null) {
                mCandidateView.clear();
            }
            updateShiftKeyState(getCurrentInputEditorInfo());
        } else if (mComposing.length() >= 0) {

            String picked;
            if (!replacemode)
                picked = suggestions.get(index) + " ";
            else
                picked = suggestions.get(index);

            //Log.i("Suggestion picked 2", picked);

            //ic.commitText(picked, picked.length());

            getCurrentInputEditorInfo();

            //colour the text according to user preferences
            SpannableString text = new SpannableString(picked);
            SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
            if (sharedPrefs.getBoolean("suggestion_highlight", false))
                text.setSpan(new BackgroundColorSpan(suggestion), 0, picked.length() - 1,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            currentSession.nSuggestionsPicked++;

            if (replacemode) {
                replacemode = false;
                //remove from list of autocorrected words.
                //Log.i("PickSuggestion", "Removed "+autocorrected_words.remove(text.toString()));

                //find the current word
                extr = ic.getExtractedText(new ExtractedTextRequest(), 0);
                WordDetails w = findWord(extr.selectionStart, extr.text);
                //Log.i("FindWord", w.word+", "+w.wordStart+" - "+w.wordEnd);
                ic.setComposingRegion(w.wordStart, w.wordEnd);
                text.setSpan(null, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                /*
                //clear any spans       
                BackgroundColorSpan[] spans=(new SpannableString(extr.text.toString())).getSpans(w.wordStart, w.wordEnd, BackgroundColorSpan.class);
                for(int i=0; i<spans.length; i++){
                  text.removeSpan(spans[i]);
                }*/

                //commit the update
                ic.commitText(text, 1);

            } else
                ic.commitText(text, picked.length());

            coreEngine.resetCoreString();
            coreEngine.insertText(picked);
            mComposing.setLength(0);
            updateCandidates();
        }
    }

    /**
    * initialize the OpenAdaptxt core
    */
    public void initializeCore() {

        if (coreEngine == null) {
            //Log.w("Initialize Core","Starting...");
            new Thread(new Runnable() {
                public void run() {
                    Looper.prepare();

                    if (CoreEngineInitialize.initializeCore(getApplicationContext())) {
                        coreEngine = CoreEngineInitialize.getCoreInstance();
                        updateCorePrefs();
                        //Log.i("KPT Engine core","core initialized");
                    } else {
                        //Log.e("KPT Engine core","core NOT initialized");
                    }
                }
            }).start();
        }
    }

    /**
     * Set the adaptxt core preferences according to the users' preferences
     */
    public void updateCorePrefs() {
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        coreEngine.setErrorCorrection(sharedPrefs.getBoolean("corrections", true));
        coreEngine.setCompletions(sharedPrefs.getBoolean("completions", true));
        coreEngine.setProximitySuggestion(sharedPrefs.getBoolean("proximity", true));
        coreEngine.setCapsStates(KPT_SUGG_STATES.KPT_SUGG_FORCE_LOWER);
        coreEngine.setMaxSuggestions(Integer.valueOf(sharedPrefs.getString("maxsuggs", "10")));
    }

    /**
     * Stop the adaptxt core
     */
    public void destroyCore() {
        if (coreEngine != null) {
            coreEngine.destroyCore();
        }
        CoreEngineInitialize.clearCore();
        coreEngine = null;
    }

    /**
     * called when the keyboard view is being hidden
     */
    @Override
    public void onFinishInputView(boolean finishingInput) {
        if (composition == null)
            composition = mComposing.toString();
        currentSession.events.add(new TypingEvent(2, "Keyboard hidden"));

        //Log.i("onFinishInputView","KEYBOARD DOWN");
        endSession();

        super.onFinishInputView(finishingInput);
    }

    public void swipeRight() {
        if (mCompletionOn) {
            pickDefaultCandidate();
        }
    }

    public void swipeLeft() {
        handleBackspace();
    }

    public void swipeDown() {
        handleClose();
    }

    public void swipeUp() {
    }

    public void onPress(int primaryCode) {
        //Log.i("OnPress","Pressed "+primaryCode);

    }

    /**
     * handle key release
     */
    public void onRelease(int primaryCode) {
        //Log.i("OnRelease","Released "+primaryCode);
        if (currentEvent != null) {
            currentEvent.timeUp = System.currentTimeMillis();
            //calculate the time since last event and also the duration of the keypress
            if (currentSession.events.size() > 1) //at least two events so we can do the calculation
            {
                currentEvent.calcTimeSinceLast(currentSession.events.get(currentSession.events.size() - 1).timeUp);
            }
            currentEvent.calcDuration();
            //add to the session
            currentSession.events.add(currentEvent);
        }
    }

    /**
     * handle the stop of the input method service
     */
    @Override
    public void onDestroy() {
        if (coreEngine != null) {
            coreEngine.resetCoreString();
            destroyCore();
        }
        super.onDestroy();
    }

    /**
     * handle the receipt of suggestions from the spell checker
     * colour the text in the editor as required
     * pass information to the keyboard view so it can draw the colour bar
     * initiate audio and haptic feedback as required
     */
    @Override
    public void onGetSuggestions(SuggestionsInfo[] results) {
        // TODO Auto-generated method stub
        int colortype = -1;
        final StringBuilder sb = new StringBuilder();

        if (updateSuggestionList) {
            updateSuggestionList = false;
            ArrayList<String> s = new ArrayList<String>();
            for (int i = 0; i < results.length; ++i) {
                final int length = results[i].getSuggestionsCount();
                for (int j = 0; j < length; ++j) {
                    s.add(results[i].getSuggestionAt(j));
                }
            }
            updateSuggestionListWithSpellChecker(s);
        } else {

            for (int i = 0; i < results.length; ++i) {
                // Returned suggestions are contained in SuggestionsInfo

                final int len = results[i].getSuggestionsCount();
                sb.append("Suggestion Attribs: " + results[i].getSuggestionsAttributes());
                if ((results[i].getSuggestionsAttributes()
                        & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) {
                    sb.append("The word was found in the dictionary\n");
                    mInputView.wordcompletedtype = 3;
                } else {

                    if ((results[i].getSuggestionsAttributes()
                            & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) == SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) {
                        if ((results[i].getSuggestionsAttributes()
                                & SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS) == SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS) {
                            colortype = 1; //yellow
                            mInputView.wordcompletedtype = 1;
                            sb.append("There are strong candidates for this word\n");
                            currentSession.nLowErrors++;
                        } else {
                            colortype = 2; //red
                            mInputView.wordcompletedtype = 2;
                            sb.append("The word looks like a typo\n");
                            currentSession.nHighErrors++;

                        }
                    }

                }

                sb.append("\n--These are the suggestions--\n");
                for (int j = 0; j < len; ++j) {
                    sb.append("," + results[i].getSuggestionAt(j));
                }
                sb.append(" (" + len + ")");
            }
            //Log.i("Spelling suggestions", sb.toString());

            //this comes after a word separator, hence just add 1 to the cursor
            SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

            SpannableString text = new SpannableString(mComposingTemp);

            if (sharedPrefs.getBoolean("highlightwords", true)) {
                switch (colortype) {
                case 1:
                    text.setSpan(new BackgroundColorSpan(small_err), 0, mComposingTemp.length(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                case 2:
                    text.setSpan(new BackgroundColorSpan(big_err), 0, mComposingTemp.length(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                default:
                    break;
                }
            }

            if (sharedPrefs.getBoolean("autocorrect", true) && mInputView.wordcompletedtype == 1) //handle autocorrection
            {
                SpannableString autoc = autocorrect(results);
                autocorrected_words.put(autoc.toString(), text.toString()); //autocorrected word, original input
                //Log.i("Autocorrecting","Key= "+autoc.toString()+", Value= "+text.toString());
                text = autoc;
                if (sharedPrefs.getBoolean("highlightwords", true))
                    text.setSpan(new BackgroundColorSpan(autocorrect), 0, text.length(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                mInputView.wordcompletedtype = 4;
            } else //autocorrection is turned off
            {
                if (!sharedPrefs.getBoolean("autocorrect", true) && colortype >= 1) //a mistake word
                {
                    //Log.i("OnGetSentenceSuggestions","Key= "+text.toString()+", Value= "+text.toString());
                    //no autocorrects, just put the word in and itself as the replacement
                    autocorrected_words.put(text.toString(), text.toString());
                }
            }

            if (sharedPrefs.getBoolean("vibrator", false)) {
                Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                final int on_time = Integer.parseInt(sharedPrefs.getString("shortvibe", "35"));

                switch (mInputView.wordcompletedtype) {
                case 1: //small err
                    // Vibrate for 300 milliseconds
                    v.vibrate(on_time);
                    break;
                case 2: //big err
                    //v.vibrate(Integer.parseInt(sharedPrefs.getString("longvibe", "300")));
                    v.vibrate(new long[] { 0, on_time, 200, on_time }, -1);
                    break;
                case 4: //autocorr
                    v.vibrate(on_time);
                    break;
                default:
                    break;

                }
            }

            if (sharedPrefs.getBoolean("audio", false)) {
                final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
                switch (mInputView.wordcompletedtype) {
                case 1: //small err
                    tg.startTone(ToneGenerator.TONE_PROP_BEEP);
                    break;
                case 2: //big err
                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
                    break;
                case 4: //autocorr
                    tg.startTone(ToneGenerator.TONE_PROP_BEEP);
                    break;
                default:
                    break;

                }
            }

            mInputView.invalidateAllKeys();
            ic.commitText(text, 1);
            sendKey(wordSeparatorKeyCode);
            coreEngine.resetCoreString();
            updateCandidates();
        }

    }

    /**
     * create a spannable string object that can be coloured from a spell checker suggestion
     * @param results
     * @return
     */
    public SpannableString autocorrect(SuggestionsInfo[] results) {
        SpannableString text = new SpannableString(results[0].getSuggestionAt(0));
        return text;
    }

    /**
     * for passing entire sentences to the spell checker, not used
     */

    @Override
    public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
        // TODO Auto-generated method stub
        Log.i("OnGetSentenceSugs", "Sentence Sugs = " + results.length);
        for (int x = 0; x < results.length; x++) {
            int sugCount = results[x].getSuggestionsCount();
            Log.i("OnGetSentenceSugs", "Sentence Sugs " + x + ", SugInfos = " + sugCount);
            for (int z = 0; z < sugCount; z++) {
                int sugCount2 = results[x].getSuggestionsInfoAt(z).getSuggestionsCount();
                Log.i("OnGetSentenceSugs",
                        "Sentence Sugs " + x + ", SugInfos = " + sugCount + ", Suggestions = " + sugCount2);
                for (int y = 0; y < sugCount2; y++) {
                    Log.i("OnGetSentenceSugs", results[x].getSuggestionsInfoAt(z).getSuggestionAt(y));
                }
            }
        }

    }

    /**
     * handle keyboard orientation being changed
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        /*
        Log.i("Configuration Changed", "Now in mode: "+newConfig.orientation);
        Log.i("Configuration Changed", "FullScreen mode: "+isFullscreenMode());
        Log.i("Configuration Changed", "ExtractView shown: "+isExtractViewShown());
        */
        //get what's input so far
        try {
            ExtractedTextRequest req = new ExtractedTextRequest();
            req.token = 0;
            req.flags = InputConnection.GET_TEXT_WITH_STYLES;
            extractedText = ic.getExtractedText(req, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.onConfigurationChanged(newConfig);

    }

}