Mercurial > 510ConnectbotMonitor
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); + } + } + +}