Java/Android stuff (me coding dirty \o/)
authormar-v-in <github@rvin.mooo.com>
Wed, 25 Sep 2013 17:29:53 +0000 (19:29 +0200)
committermar-v-in <github@rvin.mooo.com>
Wed, 25 Sep 2013 17:29:53 +0000 (19:29 +0200)
18 files changed:
AndTuer/AndroidManifest.xml [new file with mode: 0644]
AndTuer/project.properties [new file with mode: 0644]
AndTuer/res/drawable/icon.png [new file with mode: 0644]
AndTuer/res/layout/door.xml [new file with mode: 0644]
AndTuer/res/menu/main.xml [new file with mode: 0644]
AndTuer/res/values/strings.xml [new file with mode: 0644]
AndTuer/res/xml/preferences.xml [new file with mode: 0644]
AndTuer/src/de/hacksaar/andtuer/AndroidLogging.java [new file with mode: 0644]
AndTuer/src/de/hacksaar/andtuer/AsyncTyshell.java [new file with mode: 0644]
AndTuer/src/de/hacksaar/andtuer/DoorActivity.java [new file with mode: 0644]
AndTuer/src/de/hacksaar/andtuer/DoorSettings.java [new file with mode: 0644]
AndTuer/src/de/hacksaar/andtuer/IntegerTextPreference.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/DummyLogging.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/InteractiveLogin.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/SshClient.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/StderrLogging.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/TuerLogging.java [new file with mode: 0644]
JavaTuer/src/de/hacksaar/javatuer/TyshellClient.java [new file with mode: 0644]

diff --git a/AndTuer/AndroidManifest.xml b/AndTuer/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..42c81c6
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="de.hacksaar.andtuer">
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:name="de.hacksaar.andtuer.DoorActivity" android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="de.hacksaar.andtuer.DoorSettings" android:label="Settings"/>
+    </application>
+</manifest>
diff --git a/AndTuer/project.properties b/AndTuer/project.properties
new file mode 100644 (file)
index 0000000..8ef3988
--- /dev/null
@@ -0,0 +1,3 @@
+# This file is automatically generated by IntelliJ IDEA
+# Project target.
+target=android-7
diff --git a/AndTuer/res/drawable/icon.png b/AndTuer/res/drawable/icon.png
new file mode 100644 (file)
index 0000000..08ee50d
Binary files /dev/null and b/AndTuer/res/drawable/icon.png differ
diff --git a/AndTuer/res/layout/door.xml b/AndTuer/res/layout/door.xml
new file mode 100644 (file)
index 0000000..d19b224
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+    <LinearLayout android:layout_height="wrap_content" android:layout_width="wrap_content"
+                  android:layout_centerInParent="true" android:orientation="vertical">
+        <Button android:layout_height="wrap_content" android:layout_width="wrap_content"
+                android:id="@+id/connect_button"
+                android:text="@string/connect"/>
+        <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/buzz_button"
+                android:text="@string/buzz" android:enabled="false"/>
+        <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/open_button"
+                android:text="@string/open_door" android:enabled="false"/>
+        <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/close_button"
+                android:text="@string/close_door" android:enabled="false"/>
+        <Button android:layout_height="wrap_content" android:layout_width="wrap_content"
+                android:id="@+id/disconnect_button" android:text="@string/disconnect" android:enabled="false"/>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/AndTuer/res/menu/main.xml b/AndTuer/res/menu/main.xml
new file mode 100644 (file)
index 0000000..0717476
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_settings" android:icon="@android:drawable/ic_menu_preferences"
+          android:title="@string/preferences" android:showAsAction="always"/>
+</menu>
\ No newline at end of file
diff --git a/AndTuer/res/values/strings.xml b/AndTuer/res/values/strings.xml
new file mode 100644 (file)
index 0000000..a0e5153
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Hacksaar AndTuer</string>
+    <string name="connect">Connect</string>
+    <string name="disconnect">Disconnect</string>
+    <string name="buzz">Buzz</string>
+    <string name="open_door">Open door</string>
+    <string name="close_door">Close door</string>
+    <string name="preferences">Preferences</string>
+</resources>
\ No newline at end of file
diff --git a/AndTuer/res/xml/preferences.xml b/AndTuer/res/xml/preferences.xml
new file mode 100644 (file)
index 0000000..b2037f2
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory android:title="Server">
+        <EditTextPreference android:key="pref_server_hostname" android:title="Hostname"
+                            android:defaultValue="192.168.178.222"/>
+        <de.hacksaar.andtuer.IntegerTextPreference android:key="pref_server_port" android:title="Port"
+                                                   android:inputType="number" android:defaultValue="22"/>
+    </PreferenceCategory>
+    <PreferenceCategory android:title="User">
+        <EditTextPreference android:key="pref_user_username" android:title="Username"/>
+        <EditTextPreference android:key="pref_user_keyfile" android:title="Keyfile"
+                            android:defaultValue="/sdcard/.ssh/id_rsa"/>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/AndTuer/src/de/hacksaar/andtuer/AndroidLogging.java b/AndTuer/src/de/hacksaar/andtuer/AndroidLogging.java
new file mode 100644 (file)
index 0000000..e4d0b19
--- /dev/null
@@ -0,0 +1,21 @@
+package de.hacksaar.andtuer;
+
+import android.util.Log;
+import de.hacksaar.javatuer.TuerLogging;
+
+public class AndroidLogging extends TuerLogging {
+       @Override
+       public void debug(String tag, String msg) {
+               Log.d(tag, msg);
+       }
+
+       @Override
+       public void exception(String tag, Throwable t) {
+               Log.w(tag, t);
+       }
+
+       @Override
+       public void info(String tag, String msg) {
+               Log.i(tag, msg);
+       }
+}
diff --git a/AndTuer/src/de/hacksaar/andtuer/AsyncTyshell.java b/AndTuer/src/de/hacksaar/andtuer/AsyncTyshell.java
new file mode 100644 (file)
index 0000000..4dcdcaf
--- /dev/null
@@ -0,0 +1,137 @@
+package de.hacksaar.andtuer;
+
+import android.os.AsyncTask;
+import android.util.Log;
+import de.hacksaar.javatuer.InteractiveLogin;
+import de.hacksaar.javatuer.TyshellClient;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+class AsyncTyshell extends AsyncTask<Void, Void, Void> {
+
+       private static final String TAG = "AsyncTyshell";
+       private final String hostname;
+       private final int port;
+       private final String username;
+       private final String keyFile;
+       private final Prompter prompter;
+       private final Queue<String> messages = new LinkedList<>();
+       private final Object promptWait = new Object();
+       private String promptResultString;
+       private boolean promptResultBoolean;
+       private boolean disconnect = true;
+
+       AsyncTyshell(String hostname, int port, String username, String keyFile, Prompter prompter) {
+               this.hostname = hostname;
+               this.port = port;
+               this.username = username;
+               this.keyFile = keyFile;
+               this.prompter = prompter;
+       }
+
+       private void awaitPrompt() {
+               synchronized (promptWait) {
+                       try {
+                               promptWait.wait();
+                       } catch (InterruptedException e) {
+                               Log.w(TAG, e);
+                       }
+               }
+       }
+
+       public void disconnect() {
+               disconnect = false;
+               synchronized (promptWait) {
+                       promptWait.notify();
+               }
+               synchronized (messages) {
+                       messages.notify();
+               }
+       }
+
+       @Override
+       protected Void doInBackground(Void... voids) {
+               TyshellClient client = new TyshellClient(hostname, port, new AndroidLogging());
+               client.connect(username, keyFile, new AsyncInteractiveLogin());
+               while (disconnect) {
+                       String msg = null;
+                       synchronized (messages) {
+                               while (disconnect && (msg = messages.poll()) == null) {
+                                       try {
+                                               messages.wait();
+                                       } catch (InterruptedException e) {
+                                               return null;
+                                       }
+                               }
+                       }
+                       if (msg == null) {
+                               break;
+                       }
+                       client.sendCommand(msg);
+               }
+               client.disconnect();
+               return null;
+       }
+
+       public void promptResult(boolean result) {
+               promptResultBoolean = result;
+               synchronized (promptWait) {
+                       promptWait.notify();
+               }
+       }
+
+       public void promptResult(String result) {
+               promptResultString = result;
+               synchronized (promptWait) {
+                       promptWait.notify();
+               }
+       }
+
+       public void sendCommand(String string) {
+               synchronized (messages) {
+                       messages.add(string);
+                       messages.notify();
+               }
+       }
+
+       public interface Prompter {
+               void promptBoolean(String message);
+
+               void promptString(String message);
+
+               void sendMessage(String message);
+       }
+
+       private class AsyncInteractiveLogin extends InteractiveLogin {
+
+               AsyncInteractiveLogin() {
+               }
+
+               @Override
+               public String promptKeyPassphrase(String question) {
+                       prompter.promptString(question);
+                       awaitPrompt();
+                       return promptResultString;
+               }
+
+               @Override
+               public String promptUserPassword(String question) {
+                       prompter.promptString(question);
+                       awaitPrompt();
+                       return promptResultString;
+               }
+
+               @Override
+               public boolean promptYesNo(String question) {
+                       prompter.promptBoolean(question);
+                       awaitPrompt();
+                       return promptResultBoolean;
+               }
+
+               @Override
+               public void showMessage(String s) {
+                       prompter.sendMessage(s);
+               }
+       }
+}
diff --git a/AndTuer/src/de/hacksaar/andtuer/DoorActivity.java b/AndTuer/src/de/hacksaar/andtuer/DoorActivity.java
new file mode 100644 (file)
index 0000000..fc2fc9e
--- /dev/null
@@ -0,0 +1,237 @@
+package de.hacksaar.andtuer;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.InputType;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class DoorActivity extends Activity implements View.OnClickListener {
+
+       private static final String TAG = "DoorActivity";
+       private static final int DIALOG_BOOLEAN = 7;
+       private static final int DIALOG_STRING = 14;
+       private static final int TEXT_ID = 42;
+       private final AsyncTyshell.Prompter prompter = new DialogPrompter();
+       private AsyncTyshell task;
+       private String pendingMessage;
+
+       private Dialog askBooleanDialog() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(this);
+               builder.setTitle(null);
+               builder.setMessage(pendingMessage);
+
+               builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+                       @Override
+                       public void onClick(DialogInterface dialog, int whichButton) {
+                               assert task != null;
+                               task.promptResult(true);
+                       }
+               });
+
+               builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
+                       @Override
+                       public void onClick(DialogInterface dialog, int which) {
+                               assert task != null;
+                               task.promptResult(false);
+                       }
+               });
+
+               return builder.create();
+       }
+
+       private Dialog askStringDialog() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(this);
+               builder.setTitle(null);
+               builder.setMessage(pendingMessage);
+
+               final EditText input = new EditText(this);
+               input.setId(TEXT_ID);
+               input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+               builder.setView(input);
+
+               builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+                       @Override
+                       public void onClick(DialogInterface dialog, int whichButton) {
+                               assert task != null;
+                               String value = input.getText().toString();
+                               task.promptResult(value);
+                       }
+               });
+
+               builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+                       @Override
+                       public void onClick(DialogInterface dialog, int whichButton) {
+                               assert task != null;
+                               task.promptResult(null);
+                       }
+               });
+
+               return builder.create();
+       }
+
+       private void onBuzzClick() {
+               if (task != null) {
+                       task.sendCommand("buzz");
+               }
+       }
+
+       @Override
+       public void onClick(View view) {
+               switch (view.getId()) {
+                       case R.id.connect_button:
+                               onConnectClick();
+                               break;
+                       case R.id.buzz_button:
+                               onBuzzClick();
+                               break;
+                       case R.id.open_button:
+                               onOpenClick();
+                               break;
+                       case R.id.close_button:
+                               onCloseClick();
+                               break;
+                       case R.id.disconnect_button:
+                               onDisconnectClick();
+                               break;
+               }
+       }
+
+       private void onCloseClick() {
+               if (task != null) {
+                       task.sendCommand("close");
+               }
+       }
+
+       private void onConnectClick() {
+               SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+               task = new AsyncTyshell(preferences.getString(DoorSettings.PREF_SERVER_HOSTNAME, DoorSettings.DEFAULT_HOST),
+                                                               preferences.getInt(DoorSettings.PREF_SERVER_PORT, DoorSettings.DEFAULT_PORT),
+                                                               preferences.getString(DoorSettings.PREF_USER_USERNAME, null),
+                                                               preferences.getString(DoorSettings.PREF_USER_KEYFILE, DoorSettings.DEFAULT_KEYFILE),
+                                                               prompter);
+               task.execute();
+               findViewById(R.id.open_button).setEnabled(true);
+               findViewById(R.id.close_button).setEnabled(true);
+               findViewById(R.id.buzz_button).setEnabled(true);
+               findViewById(R.id.disconnect_button).setEnabled(true);
+       }
+
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+               setContentView(R.layout.door);
+               findViewById(R.id.open_button).setOnClickListener(this);
+               findViewById(R.id.close_button).setOnClickListener(this);
+               findViewById(R.id.buzz_button).setOnClickListener(this);
+               findViewById(R.id.disconnect_button).setOnClickListener(this);
+               findViewById(R.id.connect_button).setOnClickListener(this);
+       }
+
+       @Override
+       protected Dialog onCreateDialog(int id) {
+               switch (id) {
+                       case DIALOG_BOOLEAN:
+                               return askBooleanDialog();
+                       case DIALOG_STRING:
+                               return askStringDialog();
+                       default:
+                               return super.onCreateDialog(id);
+               }
+       }
+
+       @Override
+       public boolean onCreateOptionsMenu(Menu menu) {
+               getMenuInflater().inflate(R.menu.main, menu);
+               return super.onCreateOptionsMenu(menu);
+       }
+
+       private void onDisconnectClick() {
+               if (task != null) {
+                       task.sendCommand("exit");
+                       task.disconnect();
+               }
+               findViewById(R.id.open_button).setEnabled(false);
+               findViewById(R.id.close_button).setEnabled(false);
+               findViewById(R.id.buzz_button).setEnabled(false);
+               findViewById(R.id.disconnect_button).setEnabled(false);
+       }
+
+       @Override
+       public boolean onMenuItemSelected(int featureId, MenuItem item) {
+               switch (item.getItemId()) {
+                       case R.id.menu_settings:
+                               startActivity(new Intent(this, DoorSettings.class));
+                               return true;
+                       default:
+                               return super.onMenuItemSelected(featureId, item);
+               }
+       }
+
+       private void onOpenClick() {
+               if (task != null) {
+                       task.sendCommand("open");
+               }
+       }
+
+       @Override
+       protected void onPrepareDialog(int id, Dialog dialog) {
+               switch (id) {
+                       case DIALOG_STRING:
+                               ((TextView) dialog.findViewById(TEXT_ID)).setText("");
+                       case DIALOG_BOOLEAN:
+                               ((AlertDialog) dialog).setMessage(pendingMessage);
+                               break;
+                       default:
+                               super.onPrepareDialog(id, dialog);
+               }
+
+       }
+
+       private class DialogPrompter implements AsyncTyshell.Prompter {
+               DialogPrompter() {
+               }
+
+               @Override
+               public void promptBoolean(String message) {
+                       pendingMessage = message;
+                       runOnUiThread(new Runnable() {
+                               @Override
+                               public void run() {
+                                       showDialog(DIALOG_BOOLEAN);
+                               }
+                       });
+               }
+
+               @Override
+               public void promptString(String message) {
+                       pendingMessage = message;
+                       runOnUiThread(new Runnable() {
+                               @Override
+                               public void run() {
+                                       showDialog(DIALOG_STRING);
+                               }
+                       });
+               }
+
+               @Override
+               public void sendMessage(final String message) {
+                       runOnUiThread(new Runnable() {
+                               @Override
+                               public void run() {
+                                       Log.d(TAG, "Message: " + message);
+                               }
+                       });
+               }
+       }
+}
\ No newline at end of file
diff --git a/AndTuer/src/de/hacksaar/andtuer/DoorSettings.java b/AndTuer/src/de/hacksaar/andtuer/DoorSettings.java
new file mode 100644 (file)
index 0000000..1fd0d7a
--- /dev/null
@@ -0,0 +1,49 @@
+package de.hacksaar.andtuer;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Environment;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+
+public class DoorSettings extends PreferenceActivity implements Preference.OnPreferenceChangeListener {
+       public static final int DEFAULT_PORT = 22;
+       public static final String DEFAULT_HOST = "192.168.178.222";
+       public static final String DEFAULT_KEYFILE = Environment.getExternalStorageDirectory().getPath() + "/.ssh/id_rsa";
+       private static final String UNSET = "<unset>";
+       public static final String PREF_SERVER_PORT = "pref_server_port";
+       public static final String PREF_USER_USERNAME = "pref_user_username";
+       public static final String PREF_USER_KEYFILE = "pref_user_keyfile";
+       public static final String PREF_SERVER_HOSTNAME = "pref_server_hostname";
+
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+               addPreferencesFromResource(R.xml.preferences);
+               Preference hostnamePreference = findPreference(PREF_SERVER_HOSTNAME);
+               hostnamePreference.setOnPreferenceChangeListener(this);
+               SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
+               hostnamePreference.setSummary(sharedPreferences.getString(PREF_SERVER_HOSTNAME, DEFAULT_HOST));
+               Preference portPreference = findPreference(PREF_SERVER_PORT);
+               portPreference.setOnPreferenceChangeListener(this);
+               portPreference.setSummary(Integer.toString(sharedPreferences.getInt(PREF_SERVER_PORT, DEFAULT_PORT)));
+               Preference usernamePreference = findPreference(PREF_USER_USERNAME);
+               usernamePreference.setOnPreferenceChangeListener(this);
+               usernamePreference.setSummary(sharedPreferences.getString(PREF_USER_USERNAME, UNSET));
+               Preference keyfilePreference = findPreference(PREF_USER_KEYFILE);
+               keyfilePreference.setOnPreferenceChangeListener(this);
+               keyfilePreference.setSummary(sharedPreferences.getString(PREF_USER_KEYFILE, DEFAULT_KEYFILE));
+       }
+
+       @Override
+       public boolean onPreferenceChange(Preference preference, Object o) {
+               SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
+               if (preference.getKey().equals(PREF_SERVER_PORT)) {
+                       preference.setSummary(
+                                       Integer.toString(sharedPreferences.getInt(preference.getKey(), DEFAULT_PORT)));
+               } else {
+                       preference.setSummary(sharedPreferences.getString(preference.getKey(), UNSET));
+               }
+               return true;
+       }
+}
\ No newline at end of file
diff --git a/AndTuer/src/de/hacksaar/andtuer/IntegerTextPreference.java b/AndTuer/src/de/hacksaar/andtuer/IntegerTextPreference.java
new file mode 100644 (file)
index 0000000..69b750c
--- /dev/null
@@ -0,0 +1,29 @@
+package de.hacksaar.andtuer;
+
+import android.content.Context;
+import android.preference.EditTextPreference;
+import android.util.AttributeSet;
+
+public class IntegerTextPreference extends EditTextPreference {
+       public IntegerTextPreference(Context context, AttributeSet attrs, int defStyle) {
+               super(context, attrs, defStyle);
+       }
+
+       public IntegerTextPreference(Context context, AttributeSet attrs) {
+               super(context, attrs);
+       }
+
+       public IntegerTextPreference(Context context) {
+               super(context);
+       }
+
+       @Override
+       protected String getPersistedString(String defaultReturnValue) {
+               return String.valueOf(getPersistedInt(-1));
+       }
+
+       @Override
+       protected boolean persistString(String value) {
+               return persistInt(Integer.valueOf(value));
+       }
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/DummyLogging.java b/JavaTuer/src/de/hacksaar/javatuer/DummyLogging.java
new file mode 100644 (file)
index 0000000..ab37f5b
--- /dev/null
@@ -0,0 +1,18 @@
+package de.hacksaar.javatuer;
+
+public class DummyLogging extends TuerLogging {
+       @Override
+       public void debug(String tag, String msg) {
+               // do nothing
+       }
+
+       @Override
+       public void exception(String tag, Throwable t) {
+               // do nothing
+       }
+
+       @Override
+       public void info(String tag, String msg) {
+               // do nothing
+       }
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/InteractiveLogin.java b/JavaTuer/src/de/hacksaar/javatuer/InteractiveLogin.java
new file mode 100644 (file)
index 0000000..29c0c38
--- /dev/null
@@ -0,0 +1,34 @@
+package de.hacksaar.javatuer;
+
+import com.jcraft.jsch.UserInfo;
+
+public abstract class InteractiveLogin implements UserInfo {
+       private String password;
+       private String passphrase;
+
+       @Override
+       public String getPassphrase() {
+               return passphrase;
+       }
+
+       @Override
+       public String getPassword() {
+               return password;
+       }
+
+       public abstract String promptKeyPassphrase(String question);
+
+       @Override
+       public boolean promptPassphrase(String s) {
+               passphrase = promptKeyPassphrase(s);
+               return passphrase != null;
+       }
+
+       @Override
+       public boolean promptPassword(String s) {
+               password = promptUserPassword(s);
+               return password != null;
+       }
+
+       public abstract String promptUserPassword(String question);
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/SshClient.java b/JavaTuer/src/de/hacksaar/javatuer/SshClient.java
new file mode 100644 (file)
index 0000000..7e63d31
--- /dev/null
@@ -0,0 +1,158 @@
+package de.hacksaar.javatuer;
+
+import com.jcraft.jsch.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class SshClient {
+       private static final String TAG = "SshClient";
+       private final String host;
+       private final String username;
+       private final int port;
+       private final JSch jSch;
+       private final TuerLogging log;
+       private Session session;
+       private Channel channel;
+
+       public SshClient(String host, String username, int port, TuerLogging log) {
+               this.host = host;
+               this.username = username;
+               this.port = port;
+               this.log = log;
+               JSch.setLogger(log);
+               jSch = new JSch();
+       }
+
+       public void addPrivateKey(String keyFilePath) throws JSchException {
+               jSch.addIdentity(keyFilePath);
+       }
+
+       public void addPrivateKey(String keyFilePath, String passphrase) throws JSchException {
+               jSch.addIdentity(keyFilePath, passphrase);
+       }
+
+       public void disconnect() {
+               channel.disconnect();
+               channel = null;
+               session.disconnect();
+               session = null;
+       }
+
+       public String getHost() {
+               return host;
+       }
+
+       public InputStream getInputStream() throws IOException {
+               if (channel == null) {
+                       return null;
+               }
+               return channel.getInputStream();
+       }
+
+       public OutputStream getOutputStream() throws IOException {
+               if (channel == null) {
+                       return null;
+               }
+               return channel.getOutputStream();
+       }
+
+       public int getPort() {
+               return port;
+       }
+
+       public String getUsername() {
+               return username;
+       }
+
+       public boolean isConnected() {
+               return channel != null && channel.isConnected() && session != null && session.isConnected();
+       }
+
+       public void login(final String password) throws JSchException {
+               login(new UserInfo() {
+
+                       @Override
+                       public String getPassphrase() {
+                               return null;
+                       }
+
+                       @Override
+                       public String getPassword() {
+                               return password;
+                       }
+
+                       @Override
+                       public boolean promptPassphrase(String s) {
+                               return false;
+                       }
+
+                       @Override
+                       public boolean promptPassword(String s) {
+                               return true;
+                       }
+
+                       @Override
+                       public boolean promptYesNo(String s) {
+                               return false;
+                       }
+
+                       @Override
+                       public void showMessage(String s) {
+                               log.info(TAG, s);
+                       }
+               });
+       }
+
+       public void login() throws JSchException {
+               login(new UserInfo() {
+
+                       @Override
+                       public String getPassphrase() {
+                               return null;
+                       }
+
+                       @Override
+                       public String getPassword() {
+                               return null;
+                       }
+
+                       @Override
+                       public boolean promptPassphrase(String s) {
+                               return false;
+                       }
+
+                       @Override
+                       public boolean promptPassword(String s) {
+                               return false;
+                       }
+
+                       @Override
+                       public boolean promptYesNo(String s) {
+                               return false;
+                       }
+
+                       @Override
+                       public void showMessage(String s) {
+                               log.info(TAG, s);
+                       }
+               });
+       }
+
+       public void login(UserInfo userInfo) throws JSchException {
+               log.debug(TAG, "Creating session");
+               if (session == null) {
+                       session = jSch.getSession(username, host, port);
+               }
+               if (!session.isConnected()) {
+                       session.setUserInfo(userInfo);
+                       log.debug(TAG, "Connecting");
+                       session.connect();
+                       log.debug(TAG, "Opening channel");
+                       channel = session.openChannel("shell");
+                       channel.connect();
+               }
+       }
+
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/StderrLogging.java b/JavaTuer/src/de/hacksaar/javatuer/StderrLogging.java
new file mode 100644 (file)
index 0000000..326f37d
--- /dev/null
@@ -0,0 +1,20 @@
+package de.hacksaar.javatuer;
+
+public class StderrLogging extends TuerLogging {
+
+       @Override
+       public void debug(String tag, String msg) {
+               System.err.println("D/" + tag + ": " + msg);
+       }
+
+       @Override
+       public void exception(String tag, Throwable t) {
+               System.err.println("E/" + tag + ": " + t.getMessage());
+               t.printStackTrace(System.err);
+       }
+
+       @Override
+       public void info(String tag, String msg) {
+               System.err.println("I/" + tag + ": " + msg);
+       }
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/TuerLogging.java b/JavaTuer/src/de/hacksaar/javatuer/TuerLogging.java
new file mode 100644 (file)
index 0000000..6dbc199
--- /dev/null
@@ -0,0 +1,24 @@
+package de.hacksaar.javatuer;
+
+import com.jcraft.jsch.Logger;
+
+public abstract class TuerLogging implements Logger {
+
+       public abstract void debug(String tag, String msg);
+
+       public abstract void exception(String tag, Throwable t);
+
+       public abstract void info(String tag, String msg);
+
+       @Override
+       public boolean isEnabled(int i) {
+               return true;
+       }
+
+       @Override
+       public void log(int i, String s) {
+               if (i == 1) {
+                       info("SshConnection", s);
+               }
+       }
+}
diff --git a/JavaTuer/src/de/hacksaar/javatuer/TyshellClient.java b/JavaTuer/src/de/hacksaar/javatuer/TyshellClient.java
new file mode 100644 (file)
index 0000000..73d637d
--- /dev/null
@@ -0,0 +1,91 @@
+package de.hacksaar.javatuer;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.UserInfo;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+public class TyshellClient {
+       private static final String TAG = "TyshellClient";
+       public static final char END_OF_COMMAND = '\n';
+       private final String hostname;
+       private final int port;
+       private final TuerLogging log;
+       private SshClient client;
+       private InputStreamReader inputStream;
+       private OutputStreamWriter outputStream;
+
+       public TyshellClient(String hostname, int port) {
+               this(hostname, port, new DummyLogging());
+       }
+
+       public TyshellClient(String hostname, int port, TuerLogging log) {
+               this.log = log;
+               this.hostname = hostname;
+               this.port = port;
+       }
+
+       public void connect(String username, String password) {
+               try {
+                       client = new SshClient(hostname, username, port, log);
+                       client.login(password);
+                       inputStream = new InputStreamReader(client.getInputStream());
+                       outputStream = new OutputStreamWriter(client.getOutputStream());
+               } catch (JSchException | IOException e) {
+                       log.exception(TAG, e);
+               }
+       }
+
+       public void connect(String username, String keyFile, String passphrase) {
+               try {
+                       client = new SshClient(hostname, username, port, log);
+                       client.addPrivateKey(keyFile, passphrase);
+                       client.login();
+                       inputStream = new InputStreamReader(client.getInputStream());
+                       outputStream = new OutputStreamWriter(client.getOutputStream());
+               } catch (JSchException | IOException e) {
+                       log.exception(TAG, e);
+               }
+       }
+
+       public void connect(String username, String keyFile, UserInfo interactiveLogin) {
+               try {
+                       client = new SshClient(hostname, username, port, log);
+                       client.addPrivateKey(keyFile);
+                       client.login(interactiveLogin);
+                       inputStream = new InputStreamReader(client.getInputStream());
+                       outputStream = new OutputStreamWriter(client.getOutputStream());
+               } catch (JSchException | IOException e) {
+                       log.exception(TAG, e);
+               }
+       }
+
+       public void disconnect() {
+               try {
+                       inputStream.close();
+                       outputStream.close();
+               } catch (IOException e) {
+                       log.exception(TAG, e);
+               }
+               client.disconnect();
+       }
+
+       boolean isConnected() {
+               return (client.isConnected() && inputStream != null && outputStream != null);
+       }
+
+       public void sendCommand(String command) {
+               if (isConnected()) {
+                       try {
+                               assert outputStream != null;
+                               log.debug(TAG, "Sending: " + command);
+                               outputStream.write((command + END_OF_COMMAND).toCharArray());
+                               outputStream.flush();
+                       } catch (IOException e) {
+                               log.exception(TAG, e);
+                       }
+               }
+       }
+}