Mercurial > 510ConnectbotMonitor
changeset 27:807f7e4eaebe
starting update to latest toolchain
line wrap: on
line diff
--- a/.classpath Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<classpath> - <classpathentry kind="src" path="src"/> - <classpathentry kind="src" path="gen"/> - <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> - <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> - <classpathentry kind="output" path="bin/classes"/> -</classpath>
--- a/.project Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<projectDescription> - <name>510ConnectbotMonitor</name> - <comment></comment> - <projects> - </projects> - <buildSpec> - <buildCommand> - <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>org.eclipse.jdt.core.javabuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.ApkBuilder</name> - <arguments> - </arguments> - </buildCommand> - </buildSpec> - <natures> - <nature>com.android.ide.eclipse.adt.AndroidNature</nature> - <nature>org.eclipse.jdt.core.javanature</nature> - </natures> -</projectDescription>
--- a/AndroidManifest.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.five_ten_sg.connectbot.monitor" - android:versionCode="1" - android:versionName="1.0.4-0" > - - <uses-sdk - android:minSdkVersion="8" - android:targetSdkVersion="17" /> - - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - - <application - android:debuggable="true" - android:allowBackup="false" - android:icon="@drawable/icon" - android:label="@string/app_name" - android:theme="@style/AppTheme" > - <activity - android:name="com.five_ten_sg.connectbot.monitor.MonitorActivity" - android:label="@string/app_name" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - - <service - android:name="com.five_ten_sg.connectbot.monitor.MonitorService" - android:description="@string/service_desc" > - <intent-filter> - <action android:name="com.five_ten_sg.connectbot.monitor.MonitorService" /> - </intent-filter> - </service> - </application> - -</manifest>
--- a/Makefile Fri May 01 12:34:17 2015 -0700 +++ b/Makefile Thu Nov 08 11:39:13 2018 -0800 @@ -1,13 +1,22 @@ #mc40 is "On Device Storage" #tc55 is "Internal Storage" -style=release -dest=/run/user/1000/gvfs/mtp*/*torage/Download -apk='bin/510ConnectbotMonitor-$(style).apk' +style:=release +dest:=/run/user/1000/gvfs/mtp*/*torage/Download +ver:=$(shell grep versionName app/src/main/AndroidManifest.xml | cut -d'"' -f2) +apk:='app/build/outputs/apk/510ConnectbotMonitor-$(ver).apk' +id:=$(shell hg id --id || echo 1) +da:=$(shell date +%Y-%m-%d) +version:=\ +<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\ +<resources>\n\ + <string name=\"msg_version\" translatable=\"false\">510ConnectbotMonitor $(ver) ($(id) $(da))</string>\n\ +</resources>\n + ifeq ($(style),release) - debuggable=false + task:=assembleArmRelease else - debuggable=true + task:=assembleArmDebug endif all: @@ -15,10 +24,15 @@ make builder builder: - sed -i -e 's/android:debuggable=".*"/android:debuggable="$(debuggable)"/g' AndroidManifest.xml - rm -rf gen bin - android update project -p . -t android-16 - ant $(style) + rm -rf app/build/* + echo -e "$(version)" >app/src/main/res/values/version.xml + cat app/src/main/res/values/version.xml + ANDROID_HOME=/home/carl/Android/Sdk ANDROID_NDK_HOME=/home/carl/Android/Sdk/ndk-bundle ./gradlew $(task) + mv app/build/outputs/apk/arm/$(style)/app-arm-$(style).apk $(apk) + ls -al app/build/outputs/apk + +docs: + (cd xml; make) install: cp $(apk) $(dest)
--- a/ant.properties Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -source.dir=src -out.dir=bin -key.store=510Connectbot.keystore -key.alias=510Connectbot
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/build.gradle Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,89 @@ +task copyDebugLibTask(type: Copy) { + from 'build/intermediates/binaries/debug/arm/lib/armeabi' + into 'src/main/jniLibs/armeabi' +} +task copyReleaseLibTask(type: Copy) { + from 'build/intermediates/binaries/release/arm/lib/armeabi' + into 'src/main/jniLibs/armeabi' +} + +tasks.whenTaskAdded { task -> + if (task.name.contains("merge") && task.name.contains("JniLibFolders")) { + if (task.name.contains("ArmDebug")) { + task.dependsOn copyDebugLibTask + } + if (task.name.contains("ArmRelease")) { + task.dependsOn copyReleaseLibTask + } + } +} + + +apply plugin: 'com.android.application' + +android { + signingConfigs { + release { + print "\nNo Console\n" + def read = System.in.newReader().&readLine + print "\nkey store password" + storePassword = read() + print "\nkey alias password" + keyPassword = read() + print "\nDone\n" + storeFile = file("../510Connectbot.keystore") + storeType = "jks" + keyAlias = "510Connectbot" + } + } + compileSdkVersion = 16 + buildTypes { + release { + minifyEnabled = false + signingConfig = signingConfigs.release + ndk { + debuggable = false + abiFilters 'armeabi-v7a' + } + } + debug { + debuggable = true + ndk { + debuggable = true + abiFilters 'armeabi-v7a' + } + } + } + flavorDimensions "arch" + productFlavors { + arm { + dimension "arch" + } + x86 { + dimension "arch" + } + } + sourceSets { + main { + jni { + srcDir "Exec" + } + } + } + + defaultConfig { + applicationId = "com.five_ten_sg.connectbot" + minSdkVersion = 8 + targetSdkVersion = 15 + ndk { + moduleName = "com_google_ase_Exec" + } + } + + externalNativeBuild { + ndkBuild { + path "src/main/jni/Android.mk" + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/AndroidManifest.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.five_ten_sg.connectbot.monitor" + android:versionCode="1" + android:versionName="1.0.4-0" > + + <uses-sdk + android:minSdkVersion="8" + android:targetSdkVersion="17" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + + <application + android:debuggable="true" + android:allowBackup="false" + android:icon="@drawable/icon" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name="com.five_ten_sg.connectbot.monitor.MonitorActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <service + android:name="com.five_ten_sg.connectbot.monitor.MonitorService" + android:description="@string/service_desc" > + <intent-filter> + <action android:name="com.five_ten_sg.connectbot.monitor.MonitorService" /> + </intent-filter> + </service> + </application> + +</manifest>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/monitor/MonitorActivity.java Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,106 @@ +package com.five_ten_sg.connectbot.monitor; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; + +import android.app.Activity; +import android.content.ComponentName; +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.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PowerManager; +import android.util.Log; +import android.widget.TextView; + +public class MonitorActivity extends Activity { + public final static String TAG = "ConnectBot.MonitorActivity"; + + public static final int MESSAGE_CODE_PRINT = 6000; + private final int LINES = 20; + private String[] texts = new String[LINES]; + private int start = 0; + private int count = 0; + private TextView text = null; + private MonitorService bound = null; + private Handler handler = new Handler() { + @Override + public void handleMessage (Message msg) { + if (msg.what == MESSAGE_CODE_PRINT) { + printer((String)msg.obj); + } else { + super.handleMessage(msg); + } + } + }; + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.i(TAG, "onServiceConnected()"); + bound = ((MonitorService.MonitorBinder)service).getService(); + bound.handler = handler; + } + public void onServiceDisconnected(ComponentName className) { + Log.i(TAG, "onServiceDisconnected()"); + bound = null; + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + text = (TextView) findViewById(R.id.text2); + printer(getString(R.string.copyright)); + String external_dir = Environment.getExternalStorageDirectory().getAbsolutePath(); + printer(""); + printer(String.format("External directory is %s", external_dir)); + printer(""); + Log.i(TAG, "binding to monitor service"); + Intent intent = new Intent ("com.five_ten_sg.connectbot.monitor.MonitorService"); + bindService(intent, connection, Context.BIND_AUTO_CREATE); + } + + private void printer(String msg) { + if (count < LINES) count++; + else start = (start+1) % LINES; + texts[(start+count-1) % LINES] = msg + "\n"; + String c = ""; + for (int i=0; i<count; i++) c = c.concat(texts[(start+i) % LINES]); + text.setText(c); + } + + @Override + protected void onStart() { + super.onStart(); + Log.i(TAG, "activity onStart()"); + } + + @Override + protected void onRestart() { + super.onRestart(); + Log.i(TAG, "activity onRestart()"); + } + + @Override + protected void onStop() { + super.onStop(); + Log.i(TAG, "activity onStop()"); + } + + @Override + protected void onDestroy() { + Log.i(TAG, "activity onDestroy()"); + unbindService(connection); + super.onDestroy(); + } +}
--- /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); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/main.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <TextView + android:id="@+id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="" > + </TextView> + +</LinearLayout> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/menu/main.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,9 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_settings"/> + +</menu> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values-sw600dp/dimens.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,8 @@ +<resources> + + <!-- + Customize dimensions originally defined in res/values/dimens.xml (such as + screen margins) for sw600dp devices (e.g. 7" tablets) here. + --> + +</resources> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values-sw720dp-land/dimens.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,9 @@ +<resources> + + <!-- + Customize dimensions originally defined in res/values/dimens.xml (such as + screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. + --> + <dimen name="activity_horizontal_margin">128dp</dimen> + +</resources> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values-v11/styles.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,11 @@ +<resources> + + <!-- + Base application theme for API 11+. This theme completely replaces + AppBaseTheme from res/values/styles.xml on API 11+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light"> + <!-- API 11 theme customizations can go here. --> + </style> + +</resources> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values-v14/styles.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,12 @@ +<resources> + + <!-- + Base application theme for API 14+. This theme completely replaces + AppBaseTheme from BOTH res/values/styles.xml and + res/values-v11/styles.xml on API 14+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- API 14 theme customizations can go here. --> + </style> + +</resources> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values/dimens.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,7 @@ +<resources> + + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + +</resources> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values/strings.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">510 Connectbot Monitor</string> + <string name="service_desc">Maintains socket connection between 510 Connectbot terminal emulator and the monitoring application.</string> + <string name="action_settings">Settings</string> + <string name="copyright">Copyright © 2014 by 510 Software Group. Released under the GPLv3 or later. Full source code at http://www.five-ten-sg.com/510ConnectbotMonitor/</string> +</resources>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/values/styles.xml Thu Nov 08 11:39:13 2018 -0800 @@ -0,0 +1,20 @@ +<resources> + + <!-- + Base application theme, dependent on API level. This theme is replaced + by AppBaseTheme from res/values-vXX/styles.xml on newer devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Light"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + </style> + +</resources> \ No newline at end of file
--- a/build.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="510ConnectbotMonitor" default="help"> - - <property file="local.properties" /> - - <property file="ant.properties" /> - - <loadproperties srcFile="project.properties" /> - - <!-- quick check on sdk.dir --> - <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var" - unless="sdk.dir" - /> - - <!-- version-tag: custom --> - <import file="${sdk.dir}/tools/ant/build.xml" /> - -</project>
--- a/proguard-project.txt Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#}
--- a/project.properties Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# - -# Project target. -target=android-16
--- a/res/layout/main.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical" > - - <TextView - android:id="@+id/text2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="" > - </TextView> - -</LinearLayout> \ No newline at end of file
--- a/res/menu/main.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -<menu xmlns:android="http://schemas.android.com/apk/res/android" > - - <item - android:id="@+id/action_settings" - android:orderInCategory="100" - android:showAsAction="never" - android:title="@string/action_settings"/> - -</menu> \ No newline at end of file
--- a/res/values-sw600dp/dimens.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -<resources> - - <!-- - Customize dimensions originally defined in res/values/dimens.xml (such as - screen margins) for sw600dp devices (e.g. 7" tablets) here. - --> - -</resources> \ No newline at end of file
--- a/res/values-sw720dp-land/dimens.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -<resources> - - <!-- - Customize dimensions originally defined in res/values/dimens.xml (such as - screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. - --> - <dimen name="activity_horizontal_margin">128dp</dimen> - -</resources> \ No newline at end of file
--- a/res/values-v11/styles.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -<resources> - - <!-- - Base application theme for API 11+. This theme completely replaces - AppBaseTheme from res/values/styles.xml on API 11+ devices. - --> - <style name="AppBaseTheme" parent="android:Theme.Holo.Light"> - <!-- API 11 theme customizations can go here. --> - </style> - -</resources> \ No newline at end of file
--- a/res/values-v14/styles.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -<resources> - - <!-- - Base application theme for API 14+. This theme completely replaces - AppBaseTheme from BOTH res/values/styles.xml and - res/values-v11/styles.xml on API 14+ devices. - --> - <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar"> - <!-- API 14 theme customizations can go here. --> - </style> - -</resources> \ No newline at end of file
--- a/res/values/dimens.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -<resources> - - <!-- Default screen margins, per the Android Design guidelines. --> - <dimen name="activity_horizontal_margin">16dp</dimen> - <dimen name="activity_vertical_margin">16dp</dimen> - -</resources> \ No newline at end of file
--- a/res/values/strings.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="app_name">510 Connectbot Monitor</string> - <string name="service_desc">Maintains socket connection between 510 Connectbot terminal emulator and the monitoring application.</string> - <string name="action_settings">Settings</string> - <string name="copyright">Copyright © 2014 by 510 Software Group. Released under the GPLv3 or later. Full source code at http://www.five-ten-sg.com/510ConnectbotMonitor/</string> -</resources>
--- a/res/values/styles.xml Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -<resources> - - <!-- - Base application theme, dependent on API level. This theme is replaced - by AppBaseTheme from res/values-vXX/styles.xml on newer devices. - --> - <style name="AppBaseTheme" parent="android:Theme.Light"> - <!-- - Theme customizations available in newer API levels can go in - res/values-vXX/styles.xml, while customizations related to - backward-compatibility can go here. - --> - </style> - - <!-- Application theme. --> - <style name="AppTheme" parent="AppBaseTheme"> - <!-- All customizations that are NOT specific to a particular API-level can go here. --> - </style> - -</resources> \ No newline at end of file
--- a/src/com/five_ten_sg/connectbot/monitor/MonitorActivity.java Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -package com.five_ten_sg.connectbot.monitor; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.ServerSocket; -import java.net.Socket; - -import android.app.Activity; -import android.content.ComponentName; -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.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.PowerManager; -import android.util.Log; -import android.widget.TextView; - -public class MonitorActivity extends Activity { - public final static String TAG = "ConnectBot.MonitorActivity"; - - public static final int MESSAGE_CODE_PRINT = 6000; - private final int LINES = 20; - private String[] texts = new String[LINES]; - private int start = 0; - private int count = 0; - private TextView text = null; - private MonitorService bound = null; - private Handler handler = new Handler() { - @Override - public void handleMessage (Message msg) { - if (msg.what == MESSAGE_CODE_PRINT) { - printer((String)msg.obj); - } else { - super.handleMessage(msg); - } - } - }; - private ServiceConnection connection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - Log.i(TAG, "onServiceConnected()"); - bound = ((MonitorService.MonitorBinder)service).getService(); - bound.handler = handler; - } - public void onServiceDisconnected(ComponentName className) { - Log.i(TAG, "onServiceDisconnected()"); - bound = null; - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - text = (TextView) findViewById(R.id.text2); - printer(getString(R.string.copyright)); - String external_dir = Environment.getExternalStorageDirectory().getAbsolutePath(); - printer(""); - printer(String.format("External directory is %s", external_dir)); - printer(""); - Log.i(TAG, "binding to monitor service"); - Intent intent = new Intent ("com.five_ten_sg.connectbot.monitor.MonitorService"); - bindService(intent, connection, Context.BIND_AUTO_CREATE); - } - - private void printer(String msg) { - if (count < LINES) count++; - else start = (start+1) % LINES; - texts[(start+count-1) % LINES] = msg + "\n"; - String c = ""; - for (int i=0; i<count; i++) c = c.concat(texts[(start+i) % LINES]); - text.setText(c); - } - - @Override - protected void onStart() { - super.onStart(); - Log.i(TAG, "activity onStart()"); - } - - @Override - protected void onRestart() { - super.onRestart(); - Log.i(TAG, "activity onRestart()"); - } - - @Override - protected void onStop() { - super.onStop(); - Log.i(TAG, "activity onStop()"); - } - - @Override - protected void onDestroy() { - Log.i(TAG, "activity onDestroy()"); - unbindService(connection); - super.onDestroy(); - } -}
--- a/src/com/five_ten_sg/connectbot/monitor/MonitorService.java Fri May 01 12:34:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,593 +0,0 @@ -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); - } - } - -}