Mercurial > 510ConnectbotMonitor
view src/com/five_ten_sg/connectbot/monitor/MonitorService.java @ 26:3a5df66c0480
Added tag stable-1.0.4-0 for changeset 3975d341e3dd
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Fri, 01 May 2015 12:34:17 -0700 |
parents | 4f1cc4f44c41 |
children |
line wrap: on
line source
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); } } }