diff app/src/main/java/com/five_ten_sg/connectbot/monitor/MonitorService.java @ 27:807f7e4eaebe

starting update to latest toolchain
author Carl Byington <carl@five-ten-sg.com>
date Thu, 08 Nov 2018 11:39:13 -0800
parents
children 0bc0b4798d9e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/main/java/com/five_ten_sg/connectbot/monitor/MonitorService.java	Thu Nov 08 11:39:13 2018 -0800
@@ -0,0 +1,593 @@
+package com.five_ten_sg.connectbot.monitor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Locale;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.wifi.WifiManager.WifiLock;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+import android.util.Log;
+import android.widget.TextView;
+
+public class MonitorService extends Service implements OnInitListener {
+    public final static String TAG = "ConnectBot.MonitorService";
+
+    public  static final char MONITOR_CMD_INIT          = 0;
+    public  static final char MONITOR_CMD_ACTIVATE      = 1;
+    public  static final char MONITOR_CMD_KEYSTATE      = 2;
+    public  static final char MONITOR_CMD_CURSORMOVE    = 3;
+    public  static final char MONITOR_CMD_SCREENCHANGE  = 4;
+    public  static final char MONITOR_CMD_FIELDVALUE    = 5;
+    public  static final char MONITOR_CMD_SETFIELD      = 5;
+    public  static final char MONITOR_CMD_GETFIELD      = 6;
+    public  static final char MONITOR_CMD_SCREENWATCH   = 7;
+    public  static final char MONITOR_CMD_DEPRESS       = 8;
+    public  static final char MONITOR_CMD_SHOWURL       = 9;
+    public  static final char MONITOR_CMD_SWITCHSESSION = 10;
+    public  static final char MONITOR_CMD_CURSORREQUEST = 11;
+
+    public  static final char CURSOR_REQUESTED      = 0;
+    public  static final char CURSOR_SCREEN_CHANGE  = 1;
+    public  static final char CURSOR_USER_KEY       = 2;
+
+    public static final int       MONITORPORT = 6000;
+    public static ConcurrentHashMap<Integer,CommunicationThread> clients = new ConcurrentHashMap<Integer,CommunicationThread>();
+    public static int             currentConnection = -1;
+
+    private boolean               speech  = false;
+    private TextToSpeech          talker  = null;
+    private BlockingQueue<String> talkerQueue = null;
+    public  Handler               handler = null;
+    private ServerSocket          serverSocket;
+    private Thread                serverThread = null;
+    private WifiManager.WifiLock  wifiLock;
+    private PowerManager.WakeLock wakeLock;
+    final private IBinder         binder = new MonitorBinder();
+
+
+    public class MonitorBinder extends Binder {
+        public MonitorService getService() {
+            return MonitorService.this;
+        }
+    }
+
+    @Override
+    public void onInit(int status) {
+        if (status == TextToSpeech.SUCCESS) {
+            talker.setLanguage(Locale.US);
+            speech = true;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        WifiManager wMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+        wifiLock = wMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, "MyWifiLock");
+        wifiLock.acquire();
+
+        PowerManager pMgr = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        wakeLock = pMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
+        wakeLock.acquire();
+
+        talker = new TextToSpeech(this, this);
+        this.serverThread = new Thread(new ServerThread());
+        this.serverThread.start();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        startService(new Intent(this, MonitorService.class));
+        return binder;
+    }
+
+    public void printer(String msg) {
+        if (handler != null) handler.sendMessage(handler.obtainMessage(MonitorActivity.MESSAGE_CODE_PRINT, msg));
+    }
+
+    public synchronized void setCurrentConnection(int connection) {
+        currentConnection = connection;
+    }
+
+    public synchronized int nextConnection() {
+        int c = 1;
+        while (clients.get(c) != null) c++;
+        return c;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "service onStartCommand()");
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        try {
+            Log.i(TAG, "service onDestroy()");
+            teCloseAll();
+            talker.stop();
+            talker.shutdown();
+            wifiLock.release();
+            wakeLock.release();
+            serverSocket.close();
+        } catch (IOException e) {
+            Log.e(TAG, "exception in onDestroy()", e);
+        }
+        super.onDestroy();
+    }
+
+    class ServerThread extends Thread {
+        public void run() {
+            Socket  socket = null;
+            int connection = 0;
+            try {
+                serverSocket = new ServerSocket(MONITORPORT);
+            } catch (IOException e) {
+                Log.e(TAG, "exception in ServerThread.run(), cannot create listening socket", e);
+                return;
+            }
+            while (true) {
+                try{
+                   socket     = serverSocket.accept();
+                   connection = nextConnection();
+                   CommunicationThread commThread = new CommunicationThread(connection, socket);
+                   clients.put(connection, commThread);
+                   commThread.start();
+                } catch (IOException e) {
+                   Log.e(TAG, "exception in ServerThread.run(), listening socket closed", e);
+                   break;
+                }
+            }
+        }
+    }
+
+    class triple {
+        private int l, c;
+        private char[] b;
+        public triple(int l, int c, char[] b) {
+            this.l = l;
+            this.c = c;
+            this.b = b;
+        }
+    }
+
+    class CommunicationThread extends Thread {
+        public  int            thread_id;
+        public  int            connection;
+        private String         initString = null;
+        private Socket         client_socket;
+        private InputStream    client_in;
+        private OutputStream   client_out;
+        private boolean        is_closing = false;
+        private Integer        getfields_outstanding = 0;
+        private BlockingQueue<triple>  value_queue = new ArrayBlockingQueue<triple>(1);
+        private BlockingQueue<char[]>  packet_queue = new ArrayBlockingQueue<char[]>(10000);
+
+        public CommunicationThread(int handle, Socket socket) {
+            connection    = handle;
+            client_socket = socket;
+            try {
+                client_in  = client_socket.getInputStream();
+                client_out = client_socket.getOutputStream();
+            } catch (IOException e) {
+                Log.e(TAG, "exception in CommunicationThread() constructor, cannot get socket streams", e);
+            }
+        }
+
+        public synchronized void abandon() {
+            value_queue.offer(new triple(0, 0, new char[0]));
+        }
+
+        public void speak(byte [] msg, boolean flush, boolean synchronous) {
+            if (speech) {
+                String smsg = bytesToString(msg);
+                if (synchronous) {
+                    HashMap<String, String> myHashParms = new HashMap();
+                    myHashParms.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.format("connection %d", connection));
+                	talker.speak(smsg, (flush) ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, myHashParms);
+                    try {
+                        String x = talkerQueue.take(); // wait for completion
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "exception in cm.speak()", e);
+                    }
+                }
+                else {
+            	    talker.speak(smsg, (flush) ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, null);
+                }
+            }
+        }
+
+        public String bytesToString(byte[] b) {
+            char[] c = new char[b.length];
+            int bp = 0;
+            for(int i = 0; i < c.length; i++) {
+                byte b1 = 0;
+                byte b2 = b[bp++];
+                c[i] = (char) (((b1 & 0x00FF) << 8) + (b2 & 0x00FF));
+            }
+            return new String(c);
+        }
+
+        public char[] bytesToChars(byte[] b, int len) {
+            char[] c = new char[len >> 1];
+            int bp = 0;
+            for(int i = 0; i < c.length; i++) {
+                byte b1 = b[bp++];
+                byte b2 = b[bp++];
+                c[i] = (char) (((b1 & 0x00FF) << 8) + (b2 & 0x00FF));
+            }
+            return c;
+        }
+
+        public byte[] charsToBytes(char[] c) {
+            byte[] b = new byte[c.length << 1];
+            int bp = 0;
+            for (int i=0; i<c.length; i++) {
+                b[bp++] = (byte) ((c[i] & 0xff00) >> 8);
+                b[bp++] = (byte)  (c[i] & 0x00ff);
+            }
+            return b;
+        }
+
+        void cleanup(char[] buf) {
+            int i;
+            for (i=0; i<buf.length; i++) {
+                if ((int)(buf[i]) < 32) buf[i] = ' ';
+            }
+        }
+
+        public int cmGetField(char[] c) {
+            int request;
+            synchronized(getfields_outstanding) {
+                request = getfields_outstanding;
+                getfields_outstanding = getfields_outstanding + 1;
+                value_queue.clear();  // we never have more than one outstanding getfield request from the reco thread on the connection
+                clientWrite(MONITOR_CMD_GETFIELD, c);
+            }
+            return request;
+        }
+
+        public synchronized void clientWrite(char cmd, char[] c) {
+            try {
+                if (client_out != null) {
+                    c[0] = (char)(c.length - 1);    // number of chars following
+                    c[1] = cmd;
+                    Log.i(TAG, String.format("sending %d command", (int)cmd));
+                    client_out.write(charsToBytes(c));
+                    client_out.flush();
+                }
+            }
+            catch (IOException e) {
+                Log.e(TAG, "exception in monitorWrite()", e);
+                try {
+                    client_out.close();
+                }
+                catch (IOException ee) {
+                    Log.e(TAG, "exception in monitorWrite() closing socket", ee);
+                }
+                client_out = null;
+            }
+        };
+
+        private char[] forceRead(int len) throws IOException {
+            int len2 = len*2;
+            int off  = 0;
+            byte[] b = new byte[len2];
+            while (off < len2) {
+                int l = client_in.read(b, off, len2-off);
+                if (l < 0) {
+                    is_closing = true;
+                    throw new IOException("eof");
+                }
+                off += l;
+            }
+            return bytesToChars(b, len2);
+        }
+
+        public char[] readPacket() throws IOException {
+            char[] len = forceRead(1);
+            return forceRead(len[0]);
+        }
+
+        public char[] nextPacket() throws IOException, InterruptedException {
+            char[] packet = packet_queue.poll();
+            if (packet == null) {
+                packet = readPacket();
+                if (packet[0] == MONITOR_CMD_FIELDVALUE) {
+                    synchronized(getfields_outstanding) {
+                        getfields_outstanding = getfields_outstanding - 1;
+                    }
+                }
+            }
+            return packet;
+        }
+
+        private triple reformatValue(char[] packet) {
+            int plen = packet.length;
+            char[] buf = new char[plen-3];
+            System.arraycopy(packet, 3, buf, 0, plen-3);
+            cleanup(buf);
+            Log.i(TAG, String.format("teFieldValue %d line %d column %d b.len %d", connection, (int)packet[1], (int)packet[2], buf.length));
+            return new triple(packet[1], packet[2], buf);
+        }
+
+        public triple peekValue(int request) {
+            try {
+                while (true) {
+                    char[] packet = readPacket();
+                    if (packet[0] == MONITOR_CMD_FIELDVALUE) {
+                        synchronized(getfields_outstanding) {
+                            getfields_outstanding = getfields_outstanding - 1;
+                            if (request == 0) {
+                                return reformatValue(packet);
+                            }
+                            else {
+                                packet_queue.put(packet);
+                                request = request - 1;
+                            }
+                        }
+                    }
+                    else {
+                        packet_queue.put(packet);
+                    }
+                }
+            } catch (IOException e) {
+                return new triple(0, 0, new char[0]);
+            } catch (InterruptedException e) {
+                return new triple(0, 0, new char[0]);
+            }
+        }
+
+        public void run() {
+            thread_id = android.os.Process.myTid();
+            Log.i(TAG, String.format("CommunicationThread.run() client %d connected", connection));
+            while (true) {
+                try {
+                    char[] packet = nextPacket();
+                    char[] buf;
+                    char cmd = packet[0];
+                    int plen = packet.length;
+                    //Log.i(TAG, String.format("received %d command length %d", (int)cmd, plen));
+                    switch (cmd) {
+                        case MONITOR_CMD_INIT:
+                            buf = new char[plen-1];
+                            System.arraycopy(packet, 1, buf, 0, plen-1);
+                            abandonGetField(connection);
+                            initString = new String(buf);
+                            teInit(connection, initString);
+                            break;
+                        case MONITOR_CMD_ACTIVATE:
+                            abandonGetField(connection);
+                            buf = new char[plen-3];
+                            System.arraycopy(packet, 3, buf, 0, plen-3);
+                            teActivate(connection, initString, packet[1], packet[2], buf);
+                            break;
+                        case MONITOR_CMD_KEYSTATE:
+                            teKeyState(connection, (packet[1] == 1));
+                            break;
+                        case MONITOR_CMD_CURSORMOVE:
+                            teCursorMove(connection, packet[1], packet[2], packet[3]);
+                            break;
+                        case MONITOR_CMD_SCREENCHANGE:
+                            buf = new char[plen-3];
+                            System.arraycopy(packet, 3, buf, 0, plen-3);
+                            cleanup(buf);
+                            teScreenChange(connection, packet[1], packet[2], buf);
+                            break;
+                        case MONITOR_CMD_FIELDVALUE:
+                            value_queue.clear();
+                            value_queue.put(reformatValue(packet));
+                            break;
+                        default:
+                            break;
+                    }
+                } catch (IOException e) {
+                    if (!is_closing) Log.e(TAG, "exception in CommunicationThread.run()", e);
+                    break;
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "exception in CommunicationThread.run()", e);
+                    break;
+                }
+            }
+            Log.i(TAG, String.format("shutting down connection %d", connection));
+            try {
+                if (client_in     != null) client_in.close();
+                if (client_out    != null) client_out.close();
+                if (client_socket != null) client_socket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "exception in CommunicationThread.run() closing sockets", e);
+            }
+            client_in     = null;
+            client_out    = null;
+            client_socket = null;
+            clients.remove(connection);
+        }
+    }
+
+    private void abandonGetField(int except) {
+        for (CommunicationThread cm : clients.values()) {
+            if (cm.connection != except) {
+                cm.abandon();
+            }
+        }
+    }
+
+
+    ////////////////////////////////////////
+    //// these functions run on the reader thread here and call your monitoring code
+
+    public void teInit(int connection, String fn) {
+        Log.i(TAG, String.format("teInit %d file %s", connection, fn));
+        printer(String.format("init %d %s", connection, fn));
+        setCurrentConnection(connection);
+    }
+
+    public void teCloseAll() {
+        Log.i(TAG, String.format("teCloseAll"));
+    }
+
+    public void teClose(int connection) {
+        Log.i(TAG, String.format("teClose %d", connection));
+        setCurrentConnection(-1);
+    }
+
+    public void teActivate(int connection, String fn, int lines, int columns, char[] buf) {
+        Log.i(TAG, String.format("teActivate %d", connection));
+        printer(String.format("activate %d lines %d columns %d b.len %d", connection, lines, columns, buf.length));
+        boolean sameinit = false;
+        CommunicationThread cm = clients.get(currentConnection);
+        if (cm != null) {
+            sameinit = (cm.initString == fn);
+        }
+        setCurrentConnection(connection);
+    }
+
+    public void teKeyState(int connection, boolean down) {
+        String d = (down) ? "yes" : "no";
+        Log.i(TAG, String.format("teKeyState %d isdown %s", connection, d));
+        printer(String.format("keystate %d isdown %s", connection, d));
+    }
+
+    public void teCursorMove(int connection, int l, int c, int why) {
+        Log.i(TAG, String.format("teCursorMove %d line %d column %d why %d", connection, l, c, why));
+    }
+
+    public void teScreenChange(int connection, int lines, int columns, char[] buf) {
+        Log.i(TAG, String.format("teScreenChange %d lines %d columns %d b.len %d", connection, lines, columns, buf.length));
+    }
+
+
+    ////////////////////////////////////////
+    //// these functions are called from your monitoring code thread
+
+    public static void teSetField(int connection, int l, int c, char[] buf) {
+        int len = buf.length;
+        Log.i(TAG, String.format("teSetField %d request line %d column %d len %d", connection, l, c, len));
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char[] arg2 = new char[4 + len];
+            arg2[2] = (char) (l & 0x0000ffff);
+            arg2[3] = (char) (c & 0x0000ffff);
+            int base = 4;
+            System.arraycopy(buf, 0, arg2, base, len);
+            cm.clientWrite(MONITOR_CMD_SETFIELD, arg2);
+        }
+    }
+
+    public static char[] teGetField(int connection, int l, int c, int len) {
+        Log.i(TAG, String.format("teGetField %d request line %d column %d len %d", connection, l, c, len));
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char[] arg = new char[5];
+            arg[2] = (char) (l & 0x0000ffff);
+            arg[3] = (char) (c & 0x0000ffff);
+            arg[4] = (char) (len & 0x0000ffff);
+            int request = cm.cmGetField(arg);
+            try {
+                int tid = android.os.Process.myTid();
+                triple t;
+                if (tid == cm.thread_id) {
+                    // we are running on the socket reader thread, called via teCursorMove() or teScreenChange()
+                    // we need to peek command packets from the socket looking for our fieldvalue response
+                    Log.i(TAG, String.format("java teGetField() peeking value for getfield on reader thread"));
+                    t = cm.peekValue(request);
+                }
+                else {
+                    // we are running on some other thread, just wait for the reader thread to
+                    // process the fieldvalue and put it on the queue.
+                    Log.i(TAG, String.format("java teGetField() waiting for data for getfield on reco thread"));
+                    t = cm.value_queue.take(); // wait for response
+                }
+                Log.i(TAG, String.format("teGetField %d response line %d column %d len %d", connection, t.l, t.c, t.b.length));
+                return t.b;
+            } catch (InterruptedException e) {
+                Log.e(TAG, "exception in teGetField(), return empty string", e);
+            }
+        }
+        return new char[0];
+    }
+
+    public static void teScreenWatch(int connection, int l, int c, int len) {
+        Log.i(TAG, String.format("teScreenWatch %d request line %d column %d len %d", connection, l, c, len));
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char[] arg = new char[5];
+            arg[2] = (char) (l & 0x0000ffff);
+            arg[3] = (char) (c & 0x0000ffff);
+            arg[4] = (char) (len & 0x0000ffff);
+            cm.clientWrite(MONITOR_CMD_GETFIELD, arg);
+        }
+    }
+
+    public static void teSpeak(int connection, byte [] msg, boolean flush, boolean synchronous) {
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) cm.speak(msg, flush, synchronous);
+    }
+
+    public static void teDepress(int connection, int vk_key) {
+        // http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
+        Log.i(TAG, String.format("teDepress %d, %d", connection, vk_key));
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char[] arg = new char[3];
+            arg[2] = (char) (vk_key & 0x0000ffff);
+            cm.clientWrite(MONITOR_CMD_DEPRESS, arg);
+        }
+    }
+
+    public static void teShowUrl(int connection, char [] url) {
+        int len = url.length;
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char[] arg2 = new char[2 + len];
+            int base = 2;
+            System.arraycopy(url, 0, arg2, base, len);
+            cm.clientWrite(MONITOR_CMD_SHOWURL, arg2);
+        }
+    }
+
+    public static void teAbandonGetField(int connection) {
+        Log.i(TAG, String.format("teAbandonGetField %d", connection));
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            cm.abandon();
+        }
+    }
+
+    public static void teSwitchSession(int connection) {
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char [] arg2 = new char[2];
+            cm.clientWrite(MONITOR_CMD_SWITCHSESSION, arg2);
+        }
+    }
+
+    public static void teCursorRequest(int connection) {
+        CommunicationThread cm = clients.get(connection);
+        if (cm != null) {
+            char [] arg2 = new char[2];
+            cm.clientWrite(MONITOR_CMD_CURSORREQUEST, arg2);
+        }
+    }
+
+}