--- /dev/null
+<?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>
--- /dev/null
+# This file is automatically generated by IntelliJ IDEA
+# Project target.
+target=android-7
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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);
+}
--- /dev/null
+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();
+ }
+ }
+
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}