comparison 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
comparison
equal deleted inserted replaced
26:3a5df66c0480 27:807f7e4eaebe
1 package com.five_ten_sg.connectbot.monitor;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.net.ServerSocket;
7 import java.net.Socket;
8 import java.util.HashMap;
9 import java.util.concurrent.ArrayBlockingQueue;
10 import java.util.concurrent.BlockingQueue;
11 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.Locale;
13
14 import android.app.Activity;
15 import android.app.Service;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.ServiceConnection;
19 import android.net.wifi.WifiManager.WifiLock;
20 import android.net.wifi.WifiManager;
21 import android.os.Binder;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.Message;
26 import android.os.PowerManager;
27 import android.speech.tts.TextToSpeech;
28 import android.speech.tts.TextToSpeech.OnInitListener;
29 import android.util.Log;
30 import android.widget.TextView;
31
32 public class MonitorService extends Service implements OnInitListener {
33 public final static String TAG = "ConnectBot.MonitorService";
34
35 public static final char MONITOR_CMD_INIT = 0;
36 public static final char MONITOR_CMD_ACTIVATE = 1;
37 public static final char MONITOR_CMD_KEYSTATE = 2;
38 public static final char MONITOR_CMD_CURSORMOVE = 3;
39 public static final char MONITOR_CMD_SCREENCHANGE = 4;
40 public static final char MONITOR_CMD_FIELDVALUE = 5;
41 public static final char MONITOR_CMD_SETFIELD = 5;
42 public static final char MONITOR_CMD_GETFIELD = 6;
43 public static final char MONITOR_CMD_SCREENWATCH = 7;
44 public static final char MONITOR_CMD_DEPRESS = 8;
45 public static final char MONITOR_CMD_SHOWURL = 9;
46 public static final char MONITOR_CMD_SWITCHSESSION = 10;
47 public static final char MONITOR_CMD_CURSORREQUEST = 11;
48
49 public static final char CURSOR_REQUESTED = 0;
50 public static final char CURSOR_SCREEN_CHANGE = 1;
51 public static final char CURSOR_USER_KEY = 2;
52
53 public static final int MONITORPORT = 6000;
54 public static ConcurrentHashMap<Integer,CommunicationThread> clients = new ConcurrentHashMap<Integer,CommunicationThread>();
55 public static int currentConnection = -1;
56
57 private boolean speech = false;
58 private TextToSpeech talker = null;
59 private BlockingQueue<String> talkerQueue = null;
60 public Handler handler = null;
61 private ServerSocket serverSocket;
62 private Thread serverThread = null;
63 private WifiManager.WifiLock wifiLock;
64 private PowerManager.WakeLock wakeLock;
65 final private IBinder binder = new MonitorBinder();
66
67
68 public class MonitorBinder extends Binder {
69 public MonitorService getService() {
70 return MonitorService.this;
71 }
72 }
73
74 @Override
75 public void onInit(int status) {
76 if (status == TextToSpeech.SUCCESS) {
77 talker.setLanguage(Locale.US);
78 speech = true;
79 }
80 }
81
82 @Override
83 public void onCreate() {
84 WifiManager wMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
85 wifiLock = wMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, "MyWifiLock");
86 wifiLock.acquire();
87
88 PowerManager pMgr = (PowerManager) getSystemService(Context.POWER_SERVICE);
89 wakeLock = pMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
90 wakeLock.acquire();
91
92 talker = new TextToSpeech(this, this);
93 this.serverThread = new Thread(new ServerThread());
94 this.serverThread.start();
95 }
96
97 @Override
98 public IBinder onBind(Intent intent) {
99 startService(new Intent(this, MonitorService.class));
100 return binder;
101 }
102
103 public void printer(String msg) {
104 if (handler != null) handler.sendMessage(handler.obtainMessage(MonitorActivity.MESSAGE_CODE_PRINT, msg));
105 }
106
107 public synchronized void setCurrentConnection(int connection) {
108 currentConnection = connection;
109 }
110
111 public synchronized int nextConnection() {
112 int c = 1;
113 while (clients.get(c) != null) c++;
114 return c;
115 }
116
117 @Override
118 public int onStartCommand(Intent intent, int flags, int startId) {
119 Log.i(TAG, "service onStartCommand()");
120 return START_STICKY;
121 }
122
123 @Override
124 public void onDestroy() {
125 try {
126 Log.i(TAG, "service onDestroy()");
127 teCloseAll();
128 talker.stop();
129 talker.shutdown();
130 wifiLock.release();
131 wakeLock.release();
132 serverSocket.close();
133 } catch (IOException e) {
134 Log.e(TAG, "exception in onDestroy()", e);
135 }
136 super.onDestroy();
137 }
138
139 class ServerThread extends Thread {
140 public void run() {
141 Socket socket = null;
142 int connection = 0;
143 try {
144 serverSocket = new ServerSocket(MONITORPORT);
145 } catch (IOException e) {
146 Log.e(TAG, "exception in ServerThread.run(), cannot create listening socket", e);
147 return;
148 }
149 while (true) {
150 try{
151 socket = serverSocket.accept();
152 connection = nextConnection();
153 CommunicationThread commThread = new CommunicationThread(connection, socket);
154 clients.put(connection, commThread);
155 commThread.start();
156 } catch (IOException e) {
157 Log.e(TAG, "exception in ServerThread.run(), listening socket closed", e);
158 break;
159 }
160 }
161 }
162 }
163
164 class triple {
165 private int l, c;
166 private char[] b;
167 public triple(int l, int c, char[] b) {
168 this.l = l;
169 this.c = c;
170 this.b = b;
171 }
172 }
173
174 class CommunicationThread extends Thread {
175 public int thread_id;
176 public int connection;
177 private String initString = null;
178 private Socket client_socket;
179 private InputStream client_in;
180 private OutputStream client_out;
181 private boolean is_closing = false;
182 private Integer getfields_outstanding = 0;
183 private BlockingQueue<triple> value_queue = new ArrayBlockingQueue<triple>(1);
184 private BlockingQueue<char[]> packet_queue = new ArrayBlockingQueue<char[]>(10000);
185
186 public CommunicationThread(int handle, Socket socket) {
187 connection = handle;
188 client_socket = socket;
189 try {
190 client_in = client_socket.getInputStream();
191 client_out = client_socket.getOutputStream();
192 } catch (IOException e) {
193 Log.e(TAG, "exception in CommunicationThread() constructor, cannot get socket streams", e);
194 }
195 }
196
197 public synchronized void abandon() {
198 value_queue.offer(new triple(0, 0, new char[0]));
199 }
200
201 public void speak(byte [] msg, boolean flush, boolean synchronous) {
202 if (speech) {
203 String smsg = bytesToString(msg);
204 if (synchronous) {
205 HashMap<String, String> myHashParms = new HashMap();
206 myHashParms.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.format("connection %d", connection));
207 talker.speak(smsg, (flush) ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, myHashParms);
208 try {
209 String x = talkerQueue.take(); // wait for completion
210 } catch (InterruptedException e) {
211 Log.e(TAG, "exception in cm.speak()", e);
212 }
213 }
214 else {
215 talker.speak(smsg, (flush) ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, null);
216 }
217 }
218 }
219
220 public String bytesToString(byte[] b) {
221 char[] c = new char[b.length];
222 int bp = 0;
223 for(int i = 0; i < c.length; i++) {
224 byte b1 = 0;
225 byte b2 = b[bp++];
226 c[i] = (char) (((b1 & 0x00FF) << 8) + (b2 & 0x00FF));
227 }
228 return new String(c);
229 }
230
231 public char[] bytesToChars(byte[] b, int len) {
232 char[] c = new char[len >> 1];
233 int bp = 0;
234 for(int i = 0; i < c.length; i++) {
235 byte b1 = b[bp++];
236 byte b2 = b[bp++];
237 c[i] = (char) (((b1 & 0x00FF) << 8) + (b2 & 0x00FF));
238 }
239 return c;
240 }
241
242 public byte[] charsToBytes(char[] c) {
243 byte[] b = new byte[c.length << 1];
244 int bp = 0;
245 for (int i=0; i<c.length; i++) {
246 b[bp++] = (byte) ((c[i] & 0xff00) >> 8);
247 b[bp++] = (byte) (c[i] & 0x00ff);
248 }
249 return b;
250 }
251
252 void cleanup(char[] buf) {
253 int i;
254 for (i=0; i<buf.length; i++) {
255 if ((int)(buf[i]) < 32) buf[i] = ' ';
256 }
257 }
258
259 public int cmGetField(char[] c) {
260 int request;
261 synchronized(getfields_outstanding) {
262 request = getfields_outstanding;
263 getfields_outstanding = getfields_outstanding + 1;
264 value_queue.clear(); // we never have more than one outstanding getfield request from the reco thread on the connection
265 clientWrite(MONITOR_CMD_GETFIELD, c);
266 }
267 return request;
268 }
269
270 public synchronized void clientWrite(char cmd, char[] c) {
271 try {
272 if (client_out != null) {
273 c[0] = (char)(c.length - 1); // number of chars following
274 c[1] = cmd;
275 Log.i(TAG, String.format("sending %d command", (int)cmd));
276 client_out.write(charsToBytes(c));
277 client_out.flush();
278 }
279 }
280 catch (IOException e) {
281 Log.e(TAG, "exception in monitorWrite()", e);
282 try {
283 client_out.close();
284 }
285 catch (IOException ee) {
286 Log.e(TAG, "exception in monitorWrite() closing socket", ee);
287 }
288 client_out = null;
289 }
290 };
291
292 private char[] forceRead(int len) throws IOException {
293 int len2 = len*2;
294 int off = 0;
295 byte[] b = new byte[len2];
296 while (off < len2) {
297 int l = client_in.read(b, off, len2-off);
298 if (l < 0) {
299 is_closing = true;
300 throw new IOException("eof");
301 }
302 off += l;
303 }
304 return bytesToChars(b, len2);
305 }
306
307 public char[] readPacket() throws IOException {
308 char[] len = forceRead(1);
309 return forceRead(len[0]);
310 }
311
312 public char[] nextPacket() throws IOException, InterruptedException {
313 char[] packet = packet_queue.poll();
314 if (packet == null) {
315 packet = readPacket();
316 if (packet[0] == MONITOR_CMD_FIELDVALUE) {
317 synchronized(getfields_outstanding) {
318 getfields_outstanding = getfields_outstanding - 1;
319 }
320 }
321 }
322 return packet;
323 }
324
325 private triple reformatValue(char[] packet) {
326 int plen = packet.length;
327 char[] buf = new char[plen-3];
328 System.arraycopy(packet, 3, buf, 0, plen-3);
329 cleanup(buf);
330 Log.i(TAG, String.format("teFieldValue %d line %d column %d b.len %d", connection, (int)packet[1], (int)packet[2], buf.length));
331 return new triple(packet[1], packet[2], buf);
332 }
333
334 public triple peekValue(int request) {
335 try {
336 while (true) {
337 char[] packet = readPacket();
338 if (packet[0] == MONITOR_CMD_FIELDVALUE) {
339 synchronized(getfields_outstanding) {
340 getfields_outstanding = getfields_outstanding - 1;
341 if (request == 0) {
342 return reformatValue(packet);
343 }
344 else {
345 packet_queue.put(packet);
346 request = request - 1;
347 }
348 }
349 }
350 else {
351 packet_queue.put(packet);
352 }
353 }
354 } catch (IOException e) {
355 return new triple(0, 0, new char[0]);
356 } catch (InterruptedException e) {
357 return new triple(0, 0, new char[0]);
358 }
359 }
360
361 public void run() {
362 thread_id = android.os.Process.myTid();
363 Log.i(TAG, String.format("CommunicationThread.run() client %d connected", connection));
364 while (true) {
365 try {
366 char[] packet = nextPacket();
367 char[] buf;
368 char cmd = packet[0];
369 int plen = packet.length;
370 //Log.i(TAG, String.format("received %d command length %d", (int)cmd, plen));
371 switch (cmd) {
372 case MONITOR_CMD_INIT:
373 buf = new char[plen-1];
374 System.arraycopy(packet, 1, buf, 0, plen-1);
375 abandonGetField(connection);
376 initString = new String(buf);
377 teInit(connection, initString);
378 break;
379 case MONITOR_CMD_ACTIVATE:
380 abandonGetField(connection);
381 buf = new char[plen-3];
382 System.arraycopy(packet, 3, buf, 0, plen-3);
383 teActivate(connection, initString, packet[1], packet[2], buf);
384 break;
385 case MONITOR_CMD_KEYSTATE:
386 teKeyState(connection, (packet[1] == 1));
387 break;
388 case MONITOR_CMD_CURSORMOVE:
389 teCursorMove(connection, packet[1], packet[2], packet[3]);
390 break;
391 case MONITOR_CMD_SCREENCHANGE:
392 buf = new char[plen-3];
393 System.arraycopy(packet, 3, buf, 0, plen-3);
394 cleanup(buf);
395 teScreenChange(connection, packet[1], packet[2], buf);
396 break;
397 case MONITOR_CMD_FIELDVALUE:
398 value_queue.clear();
399 value_queue.put(reformatValue(packet));
400 break;
401 default:
402 break;
403 }
404 } catch (IOException e) {
405 if (!is_closing) Log.e(TAG, "exception in CommunicationThread.run()", e);
406 break;
407 } catch (InterruptedException e) {
408 Log.e(TAG, "exception in CommunicationThread.run()", e);
409 break;
410 }
411 }
412 Log.i(TAG, String.format("shutting down connection %d", connection));
413 try {
414 if (client_in != null) client_in.close();
415 if (client_out != null) client_out.close();
416 if (client_socket != null) client_socket.close();
417 } catch (IOException e) {
418 Log.e(TAG, "exception in CommunicationThread.run() closing sockets", e);
419 }
420 client_in = null;
421 client_out = null;
422 client_socket = null;
423 clients.remove(connection);
424 }
425 }
426
427 private void abandonGetField(int except) {
428 for (CommunicationThread cm : clients.values()) {
429 if (cm.connection != except) {
430 cm.abandon();
431 }
432 }
433 }
434
435
436 ////////////////////////////////////////
437 //// these functions run on the reader thread here and call your monitoring code
438
439 public void teInit(int connection, String fn) {
440 Log.i(TAG, String.format("teInit %d file %s", connection, fn));
441 printer(String.format("init %d %s", connection, fn));
442 setCurrentConnection(connection);
443 }
444
445 public void teCloseAll() {
446 Log.i(TAG, String.format("teCloseAll"));
447 }
448
449 public void teClose(int connection) {
450 Log.i(TAG, String.format("teClose %d", connection));
451 setCurrentConnection(-1);
452 }
453
454 public void teActivate(int connection, String fn, int lines, int columns, char[] buf) {
455 Log.i(TAG, String.format("teActivate %d", connection));
456 printer(String.format("activate %d lines %d columns %d b.len %d", connection, lines, columns, buf.length));
457 boolean sameinit = false;
458 CommunicationThread cm = clients.get(currentConnection);
459 if (cm != null) {
460 sameinit = (cm.initString == fn);
461 }
462 setCurrentConnection(connection);
463 }
464
465 public void teKeyState(int connection, boolean down) {
466 String d = (down) ? "yes" : "no";
467 Log.i(TAG, String.format("teKeyState %d isdown %s", connection, d));
468 printer(String.format("keystate %d isdown %s", connection, d));
469 }
470
471 public void teCursorMove(int connection, int l, int c, int why) {
472 Log.i(TAG, String.format("teCursorMove %d line %d column %d why %d", connection, l, c, why));
473 }
474
475 public void teScreenChange(int connection, int lines, int columns, char[] buf) {
476 Log.i(TAG, String.format("teScreenChange %d lines %d columns %d b.len %d", connection, lines, columns, buf.length));
477 }
478
479
480 ////////////////////////////////////////
481 //// these functions are called from your monitoring code thread
482
483 public static void teSetField(int connection, int l, int c, char[] buf) {
484 int len = buf.length;
485 Log.i(TAG, String.format("teSetField %d request line %d column %d len %d", connection, l, c, len));
486 CommunicationThread cm = clients.get(connection);
487 if (cm != null) {
488 char[] arg2 = new char[4 + len];
489 arg2[2] = (char) (l & 0x0000ffff);
490 arg2[3] = (char) (c & 0x0000ffff);
491 int base = 4;
492 System.arraycopy(buf, 0, arg2, base, len);
493 cm.clientWrite(MONITOR_CMD_SETFIELD, arg2);
494 }
495 }
496
497 public static char[] teGetField(int connection, int l, int c, int len) {
498 Log.i(TAG, String.format("teGetField %d request line %d column %d len %d", connection, l, c, len));
499 CommunicationThread cm = clients.get(connection);
500 if (cm != null) {
501 char[] arg = new char[5];
502 arg[2] = (char) (l & 0x0000ffff);
503 arg[3] = (char) (c & 0x0000ffff);
504 arg[4] = (char) (len & 0x0000ffff);
505 int request = cm.cmGetField(arg);
506 try {
507 int tid = android.os.Process.myTid();
508 triple t;
509 if (tid == cm.thread_id) {
510 // we are running on the socket reader thread, called via teCursorMove() or teScreenChange()
511 // we need to peek command packets from the socket looking for our fieldvalue response
512 Log.i(TAG, String.format("java teGetField() peeking value for getfield on reader thread"));
513 t = cm.peekValue(request);
514 }
515 else {
516 // we are running on some other thread, just wait for the reader thread to
517 // process the fieldvalue and put it on the queue.
518 Log.i(TAG, String.format("java teGetField() waiting for data for getfield on reco thread"));
519 t = cm.value_queue.take(); // wait for response
520 }
521 Log.i(TAG, String.format("teGetField %d response line %d column %d len %d", connection, t.l, t.c, t.b.length));
522 return t.b;
523 } catch (InterruptedException e) {
524 Log.e(TAG, "exception in teGetField(), return empty string", e);
525 }
526 }
527 return new char[0];
528 }
529
530 public static void teScreenWatch(int connection, int l, int c, int len) {
531 Log.i(TAG, String.format("teScreenWatch %d request line %d column %d len %d", connection, l, c, len));
532 CommunicationThread cm = clients.get(connection);
533 if (cm != null) {
534 char[] arg = new char[5];
535 arg[2] = (char) (l & 0x0000ffff);
536 arg[3] = (char) (c & 0x0000ffff);
537 arg[4] = (char) (len & 0x0000ffff);
538 cm.clientWrite(MONITOR_CMD_GETFIELD, arg);
539 }
540 }
541
542 public static void teSpeak(int connection, byte [] msg, boolean flush, boolean synchronous) {
543 CommunicationThread cm = clients.get(connection);
544 if (cm != null) cm.speak(msg, flush, synchronous);
545 }
546
547 public static void teDepress(int connection, int vk_key) {
548 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
549 Log.i(TAG, String.format("teDepress %d, %d", connection, vk_key));
550 CommunicationThread cm = clients.get(connection);
551 if (cm != null) {
552 char[] arg = new char[3];
553 arg[2] = (char) (vk_key & 0x0000ffff);
554 cm.clientWrite(MONITOR_CMD_DEPRESS, arg);
555 }
556 }
557
558 public static void teShowUrl(int connection, char [] url) {
559 int len = url.length;
560 CommunicationThread cm = clients.get(connection);
561 if (cm != null) {
562 char[] arg2 = new char[2 + len];
563 int base = 2;
564 System.arraycopy(url, 0, arg2, base, len);
565 cm.clientWrite(MONITOR_CMD_SHOWURL, arg2);
566 }
567 }
568
569 public static void teAbandonGetField(int connection) {
570 Log.i(TAG, String.format("teAbandonGetField %d", connection));
571 CommunicationThread cm = clients.get(connection);
572 if (cm != null) {
573 cm.abandon();
574 }
575 }
576
577 public static void teSwitchSession(int connection) {
578 CommunicationThread cm = clients.get(connection);
579 if (cm != null) {
580 char [] arg2 = new char[2];
581 cm.clientWrite(MONITOR_CMD_SWITCHSESSION, arg2);
582 }
583 }
584
585 public static void teCursorRequest(int connection) {
586 CommunicationThread cm = clients.get(connection);
587 if (cm != null) {
588 char [] arg2 = new char[2];
589 cm.clientWrite(MONITOR_CMD_CURSORREQUEST, arg2);
590 }
591 }
592
593 }