Android Open Source - AirKeys Air Keys Service






From Project

Back to project page AirKeys.

License

The source code is released under:

Apache License

If you think the Android project AirKeys 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

package org.twinone.airkeys;
//from  w w  w. j  av a 2s  . c  om
import android.annotation.SuppressLint;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.TextView;

import com.google.gson.Gson;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

import java.io.File;
import java.util.ArrayList;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

public class AirKeysService extends InputMethodService {

    private static final int MAX_CLIENTS = 1;

    // Client actions
    private static final String ACTION_TEXT = "text";
    private static final String ACTION_COMMIT = "commit";
    private static final String ACTION_ENTER = "enter";
    private static final String ACTION_CURSOR = "cursor";

    // Server actions
    private static final String ACTION_CONNECTED = "connected";
    private static final String ACTION_DISCONNECTED = "disconnected";

    private static final boolean FORCE_RELOAD = true;
    private static final String TAG = AirKeysService.class.getSimpleName();
    private static final String WEBAPP_SOURCE = "root.war";
    private static final String WEBAPPS_DIR = "webapps";
    private static final String WEBAPP_NAME = "root.war";


    private TextView mMessage;
    private Button mButton;
    private InputServer mServer;

    private static final int HTTP_PORT = 8080; // TODO support ssl?

    public AirKeysService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "OnCreate");
        mServer = new InputServer();
        extractAssetsIfNecessary();
        startServer();
        super.onCreate();
    }


    private void startServer() {
        try {
            mServer.start();
        } catch (Exception e) {
            Log.w(TAG, "Exception starting Jetty", e);
        }
    }

    private void stopServer() {
        try {
            mServer.stop();
        } catch (Exception e) {
            Log.v(TAG, "Exception stopping Jetty", e);
        } catch (Error e) {
            Log.v(TAG, "Error stopping Jetty", e);
        }
    }

    @Override
    public View onCreateInputView() {
        ViewGroup vg = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.keyboard, null);
        mMessage = (TextView) vg.findViewById(R.id.keyboard_text);
        mButton = (Button) vg.findViewById(R.id.keyboard_show_mehtods);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switchToNextIME();
            }
        });
        mButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                showSelectInputMethodDialog(AirKeysService.this);
                return true;
            }
        });
        updateTextViewInfoClients();
        return vg;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "OnDestroy");
        super.onDestroy();
        stopServer();
    }

    private class InputServer extends Server {
        public InputServer() {
            super();

            // HTTP
            SelectChannelConnector httpConnector = new SelectChannelConnector();
            httpConnector.setPort(HTTP_PORT);
            setConnectors(new Connector[]{httpConnector});

            Log.d(TAG, "Webapp location: " + getWebAppLocation().getAbsolutePath());
            WebAppContext wac = new WebAppContext();
            wac.setContextPath("/");
            wac.setWar(getWebappFile().getAbsolutePath());
            wac.setExtractWAR(true);
//            ServletHolder sh = new ServletHolder(new InputServlet());
            wac.addServlet(new ServletHolder(mInputServlet), "/ws");

            setHandler(wac);
        }
    }

    private void extractAssetsIfNecessary() {
        File dir = getWebAppLocation();
        if (!dir.exists() || !dir.isDirectory() || FORCE_RELOAD) {
            Log.i(TAG, "Extracting web app from assets");
            FileUtil.copyAssetsFile(this, WEBAPP_SOURCE, getWebappFile().getAbsolutePath());
        } else {
            Log.d(TAG, "Webapp already extracted");
        }
    }

    private File getWebAppLocation() {
        return getDir(WEBAPPS_DIR, Context.MODE_PRIVATE);
    }

    private File getWebappFile() {
        return new File(getWebAppLocation(), WEBAPP_NAME);
    }


    private InputServlet mInputServlet = new InputServlet();

    private ArrayList<WSClient> mClients = new ArrayList<>();

    public class WSClient implements WebSocket.OnTextMessage {
        public WSClient(String host) {
            this.host = host;
        }

        public int id;
        private String host;
        private Connection mConnection;

        public void send(final String message) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mConnection.sendMessage(message);
                    } catch (Exception e) {
                        Log.d(TAG, "Failed to send message to a client");
                    }
                }
            }).start();
        }

        @Override
        public void onOpen(Connection connection) {
            Log.d(TAG, "OnOpen");
            mConnection = connection;
            mClients.add(this);
            updateTextViewInfoClients();
        }

        @Override
        public void onClose(int i, String s) {
            Log.d(TAG, "OnClose");
            mClients.remove(this);
            updateTextViewInfoClients();
        }

        @Override
        public void onMessage(String s) {
            Log.d(TAG, "OnMessage: " + s);
            ClientMessage m = new Gson().fromJson(s, ClientMessage.class);
            AirKeysService.this.onMessage(this, m);
        }
    }

    public class InputServlet extends WebSocketServlet {

        public InputServlet() {

        }

        @Override
        public void init() throws ServletException {
            super.init();
        }

        @Override
        public WebSocket doWebSocketConnect(HttpServletRequest req, String s) {
            if (mClients.size() > MAX_CLIENTS) {
                Log.w(TAG, "Maximum client number reached: " + MAX_CLIENTS);
                return null;
            }
            WSClient c = new WSClient(req.getRemoteHost());
            return c;
        }
    }


    private void sendMessage(ServerMessage m) {
        String message = new Gson().toJson(m);
        Log.d(TAG, "Sending: " + message);
        for (WSClient c : mClients) {
            c.send(message);
        }

    }

    class ClientMessage {
        String action;
        String text;
        int[] cursor;
    }

    class ServerMessage {
        String action;
        String text;
    }


    @SuppressLint("NewApi")
    private void switchToNextIME() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            final IBinder token = this.getWindow().getWindow().getAttributes().token;
            imm.switchToNextInputMethod(token, false);
        } else {
            showSelectInputMethodDialog(this);
        }
    }

    public static void showSelectInputMethodDialog(Context c) {
        InputMethodManager im = (InputMethodManager) c.getSystemService(INPUT_METHOD_SERVICE);
        im.showInputMethodPicker();
    }

    private void updateTextViewInfoClients() {
        if (mMessage == null) return;
        mMessage.post(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Setting text");
                synchronized (mClients) {
                    if (mClients.size() > 0) {
                        mMessage.setText("Connected to " + mClients.get(0).host);
                    } else {
                        mMessage.setText("Listening on " + NetUtil.getIPV4NetworkInterface() + ":" + HTTP_PORT);
                    }
                }
            }
        });
    }

    private String getCurrentText() {
        try {
            return (String) getCurrentInputConnection().getExtractedText(new ExtractedTextRequest(), 0).text;
        } catch (NullPointerException ignored) {
            return null;
        }
    }

    private void onMessage(WSClient client, ClientMessage m) {
        InputConnection conn = getCurrentInputConnection();

        if (conn == null) Log.w(TAG, "InputConnection null");
        synchronized (conn) {
            switch (m.action) {
                case ACTION_TEXT:
                    int size = getCurrentText().length();
                    conn.deleteSurroundingText(size, size);
                    if (m.text != null)
                        conn.commitText(m.text, 1);
                    if (m.cursor != null) {
                        conn.setSelection(m.cursor[0], m.cursor[1]);
                    }
                    break;
                case ACTION_COMMIT:
                    conn.commitText(m.text, 1);
                    break;
                case ACTION_ENTER:
                    KeyEvent enterDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
                    conn.sendKeyEvent(enterDown);
                    KeyEvent enterUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER);
                    conn.sendKeyEvent(enterUp);
                    break;
                case ACTION_CURSOR:
                    conn.setSelection(m.cursor[0], m.cursor[1]);
                    break;
//            case ACTION_BACKSPACE:
//                conn.deleteSurroundingText(1, 0);
//                break;
//            case ACTION_DELETE:
//                conn.deleteSurroundingText(0, 1);
//                break;
            }
        }
    }
}




Java Source Code List

org.twinone.airkeys.AirKeysService.java
org.twinone.airkeys.ApplicationTest.java
org.twinone.airkeys.FileUtil.java
org.twinone.airkeys.MainActivity.java
org.twinone.airkeys.NetUtil.java