aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/androidTest/java/org/connectbot/StartupTest.java162
-rw-r--r--app/src/main/AndroidManifest.xml4
-rw-r--r--app/src/main/java/org/connectbot/ConsoleActivity.java306
-rw-r--r--app/src/main/java/org/connectbot/HostListActivity.java80
-rw-r--r--app/src/main/java/org/connectbot/service/OnHostStatusChangedListener.java26
-rw-r--r--app/src/main/java/org/connectbot/service/TerminalManager.java30
-rw-r--r--app/src/main/java/org/connectbot/util/HostDatabase.java16
-rw-r--r--app/src/main/jni/com_google_ase_Exec.cpp7
-rw-r--r--app/src/main/res/layout-large/act_console.xml300
-rw-r--r--app/src/main/res/layout/act_console.xml300
-rw-r--r--app/src/main/res/values/strings.xml33
-rw-r--r--app/src/main/res/values/styles.xml41
-rw-r--r--app/src/main/res/values/viewpager.xml23
-rw-r--r--app/src/test/java/android/net/http/AndroidHttpClient.java25
14 files changed, 1023 insertions, 330 deletions
diff --git a/app/src/androidTest/java/org/connectbot/StartupTest.java b/app/src/androidTest/java/org/connectbot/StartupTest.java
new file mode 100644
index 0000000..0eb9c11
--- /dev/null
+++ b/app/src/androidTest/java/org/connectbot/StartupTest.java
@@ -0,0 +1,162 @@
+package org.connectbot;
+
+import org.connectbot.bean.HostBean;
+import org.connectbot.util.HostDatabase;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.intent.Intents;
+import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.ImageView;
+
+import static android.support.test.espresso.Espresso.onData;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
+import static android.support.test.espresso.action.ViewActions.longClick;
+import static android.support.test.espresso.action.ViewActions.pressBack;
+import static android.support.test.espresso.action.ViewActions.pressImeActionButton;
+import static android.support.test.espresso.action.ViewActions.pressMenuKey;
+import static android.support.test.espresso.action.ViewActions.typeText;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.intent.Intents.intended;
+import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+
+@RunWith(AndroidJUnit4.class)
+public class StartupTest {
+ @Rule
+ public final ActivityTestRule<HostListActivity> mActivityRule = new ActivityTestRule<>(
+ HostListActivity.class, false, false);
+
+ @Before
+ public void makeDatabasePristine() {
+ HostDatabase db = new HostDatabase(InstrumentationRegistry.getTargetContext());
+ db.resetDatabase();
+
+ mActivityRule.launchActivity(new Intent());
+ }
+
+ @Test
+ public void localConnectionDisconnectFromHostList() {
+ startNewLocalConnection();
+
+ onView(withId(R.id.console_flip)).perform(closeSoftKeyboard(), pressBack());
+
+ // Make sure we're still connected.
+ onData(withHostNickname("Local")).inAdapterView(withId(android.R.id.list))
+ .check(matches(hostConnected()))
+ .perform(longClick());
+
+ // Click on the disconnect context menu item.
+ onView(withText(R.string.list_host_disconnect)).check(matches(isDisplayed())).perform(click());
+
+ // Now make sure we're disconnected.
+ onData(withHostNickname("Local")).inAdapterView(withId(android.R.id.list))
+ .check(matches(hostDisconnected()));
+ }
+
+ @Test
+ public void localConnectionDisconnectConsoleActivity() {
+ startNewLocalConnection();
+
+ onView(withId(R.id.console_flip)).perform(pressMenuKey());
+
+ // Click on the disconnect context menu item.
+ onView(withText(R.string.list_host_disconnect)).check(matches(isDisplayed())).perform(click());
+
+ // Now make sure we're disconnected.
+ onData(withHostNickname("Local")).inAdapterView(withId(android.R.id.list))
+ .check(matches(hostDisconnected()));
+ }
+
+ private void startNewLocalConnection() {
+ onView(withId(R.id.transport_selection)).perform(click());
+ onData(allOf(is(instanceOf(String.class)), is("local"))).perform(click());
+ onView(withId(R.id.front_quickconnect)).perform(typeText("Local"));
+
+ Intents.init();
+ try {
+ onView(withId(R.id.front_quickconnect)).perform(pressImeActionButton());
+ intended(hasComponent(ConsoleActivity.class.getName()));
+ } finally {
+ Intents.release();
+ }
+
+ onView(withId(R.id.console_flip)).check(matches(
+ hasDescendant(allOf(isDisplayed(), withId(R.id.terminal_view)))));
+ }
+
+ /**
+ * Matches the nickname of a {@link HostBean}.
+ */
+ public static Matcher<Object> withHostNickname(final String content) {
+ return new BoundedMatcher<Object, HostBean>(HostBean.class) {
+ @Override
+ public boolean matchesSafely(HostBean host) {
+ return host.getNickname().matches(content);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("with host nickname '" + content + "'");
+ }
+ };
+ }
+
+ /**
+ * Matches the drawable state on an ImageView that is set with setImageState.
+ */
+ public static Matcher<View> withDrawableState(final int expectedState) {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof ImageView)) {
+ return false;
+ }
+
+ int[] states = view.getDrawableState();
+ for (int state : states) {
+ if (state == expectedState) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("with drawable state '" + expectedState + "'");
+ }
+ };
+ }
+
+ @NonNull
+ private Matcher<View> hostDisconnected() {
+ return hasDescendant(allOf(withId(android.R.id.icon),
+ withDrawableState(android.R.attr.state_expanded)));
+ }
+
+ @NonNull
+ private Matcher<View> hostConnected() {
+ return hasDescendant(allOf(withId(android.R.id.icon),
+ withDrawableState(android.R.attr.state_checked)));
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f4a0c34..f43a428 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,8 +17,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.connectbot"
- android:versionName="1.8.5"
- android:versionCode="375"
+ android:versionName="1.8.6"
+ android:versionCode="376"
android:installLocation="auto">
<uses-sdk />
diff --git a/app/src/main/java/org/connectbot/ConsoleActivity.java b/app/src/main/java/org/connectbot/ConsoleActivity.java
index 72fd2fd..4051a45 100644
--- a/app/src/main/java/org/connectbot/ConsoleActivity.java
+++ b/app/src/main/java/org/connectbot/ConsoleActivity.java
@@ -85,6 +85,7 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@@ -100,7 +101,10 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
private static final int CLICK_TIME = 400;
private static final float MAX_CLICK_DISTANCE = 25f;
- private static final int KEYBOARD_DISPLAY_TIME = 1500;
+ private static final int KEYBOARD_DISPLAY_TIME = 3000;
+ private static final int KEYBOARD_REPEAT_INITIAL = 500;
+ private static final int KEYBOARD_REPEAT = 100;
+ private static final String STATE_SELECTED_URI = "selectedUri";
protected ViewPager pager = null;
protected TabLayout tabs = null;
@@ -217,38 +221,143 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
};
+ protected Handler keyRepeatHandler = new Handler();
+
+
+ /**
+ * Handle repeatable virtual keys and touch events
+ */
+ public class KeyRepeater implements Runnable, OnTouchListener {
+ private View mView;
+ private Handler mHandler;
+ private boolean mDown;
+
+ public KeyRepeater(Handler handler, View view) {
+ mView = view;
+ mHandler = handler;
+ mView.setOnTouchListener(this);
+ mDown = false;
+ }
+
+ @Override
+ public void run() {
+ mDown = true;
+ mHandler.removeCallbacks(this);
+ mHandler.postDelayed(this, KEYBOARD_REPEAT);
+ onEmulatedKeyClicked(mView);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "KeyRepeater.onTouch(" + v.getId() + ", " +
+ event.getAction() + ", " +
+ event.getActionIndex() + ", " +
+ event.getActionMasked() + ");");
+ }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDown = false;
+ mHandler.postDelayed(this, KEYBOARD_REPEAT_INITIAL);
+ return (true);
+
+ case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(this);
+ return (true);
+
+ case MotionEvent.ACTION_UP:
+ mHandler.removeCallbacks(this);
+ if (!mDown) {
+ onEmulatedKeyClicked(mView);
+ }
+ return (true);
+ }
+ return false;
+ }
+ }
+
private void onEmulatedKeyClicked(View v) {
TerminalView terminal = adapter.getCurrentTerminalView();
if (terminal == null) return;
TerminalKeyListener handler = terminal.bridge.getKeyHandler();
- boolean hideKeys = true;
+ boolean hideKeys = false;
- switch (v.getId()) {
+ switch (v.getId()) {
case R.id.button_ctrl:
handler.metaPress(TerminalKeyListener.OUR_CTRL_ON, true);
+ hideKeys = true;
break;
case R.id.button_esc:
handler.sendEscape();
+ hideKeys = true;
break;
case R.id.button_tab:
handler.sendTab();
+ hideKeys = true;
break;
+
case R.id.button_up:
handler.sendPressedKey(vt320.KEY_UP);
- hideKeys = false;
break;
case R.id.button_down:
handler.sendPressedKey(vt320.KEY_DOWN);
- hideKeys = false;
break;
case R.id.button_left:
handler.sendPressedKey(vt320.KEY_LEFT);
- hideKeys = false;
break;
case R.id.button_right:
handler.sendPressedKey(vt320.KEY_RIGHT);
- hideKeys = false;
+ break;
+
+ case R.id.button_home:
+ handler.sendPressedKey(vt320.KEY_HOME);
+ break;
+ case R.id.button_end:
+ handler.sendPressedKey(vt320.KEY_END);
+ break;
+ case R.id.button_pgup:
+ handler.sendPressedKey(vt320.KEY_PAGE_UP);
+ break;
+ case R.id.button_pgdn:
+ handler.sendPressedKey(vt320.KEY_PAGE_DOWN);
+ break;
+
+ case R.id.button_f1:
+ handler.sendPressedKey(vt320.KEY_F1);
+ break;
+ case R.id.button_f2:
+ handler.sendPressedKey(vt320.KEY_F2);
+ break;
+ case R.id.button_f3:
+ handler.sendPressedKey(vt320.KEY_F3);
+ break;
+ case R.id.button_f4:
+ handler.sendPressedKey(vt320.KEY_F4);
+ break;
+ case R.id.button_f5:
+ handler.sendPressedKey(vt320.KEY_F5);
+ break;
+ case R.id.button_f6:
+ handler.sendPressedKey(vt320.KEY_F6);
+ break;
+ case R.id.button_f7:
+ handler.sendPressedKey(vt320.KEY_F7);
+ break;
+ case R.id.button_f8:
+ handler.sendPressedKey(vt320.KEY_F8);
+ break;
+ case R.id.button_f9:
+ handler.sendPressedKey(vt320.KEY_F9);
+ break;
+ case R.id.button_f10:
+ handler.sendPressedKey(vt320.KEY_F10);
+ break;
+ case R.id.button_f11:
+ handler.sendPressedKey(vt320.KEY_F11);
+ break;
+ case R.id.button_f12:
+ handler.sendPressedKey(vt320.KEY_F12);
break;
}
@@ -258,6 +367,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
autoHideEmulatedKeys();
terminal.bridge.tryKeyVibrate();
+ if (titleBarHide) {
+ actionBar.hide();
+ }
}
/**
@@ -292,10 +404,12 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
booleanPromptGroup.setVisibility(View.GONE);
}
- private void showEmulatedKeys() {
+ private void showEmulatedKeys(boolean showActionBar) {
keyboardGroup.startAnimation(keyboard_fade_in);
keyboardGroup.setVisibility(View.VISIBLE);
- actionBar.show();
+ if (showActionBar) {
+ actionBar.show();
+ }
autoHideEmulatedKeys();
}
@@ -358,7 +472,11 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
setVolumeControlStream(AudioManager.STREAM_MUSIC);
// handle requested console from incoming intent
- requested = getIntent().getData();
+ if (icicle == null) {
+ requested = getIntent().getData();
+ } else {
+ requested = Uri.parse(icicle.getString(STATE_SELECTED_URI));
+ }
inflater = LayoutInflater.from(this);
@@ -448,10 +566,29 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
findViewById(R.id.button_ctrl).setOnClickListener(emulatedKeysListener);
findViewById(R.id.button_esc).setOnClickListener(emulatedKeysListener);
findViewById(R.id.button_tab).setOnClickListener(emulatedKeysListener);
- findViewById(R.id.button_up).setOnClickListener(emulatedKeysListener);
- findViewById(R.id.button_down).setOnClickListener(emulatedKeysListener);
- findViewById(R.id.button_left).setOnClickListener(emulatedKeysListener);
- findViewById(R.id.button_right).setOnClickListener(emulatedKeysListener);
+
+ new KeyRepeater(keyRepeatHandler, findViewById(R.id.button_up));
+ new KeyRepeater(keyRepeatHandler, findViewById(R.id.button_down));
+ new KeyRepeater(keyRepeatHandler, findViewById(R.id.button_left));
+ new KeyRepeater(keyRepeatHandler, findViewById(R.id.button_right));
+
+ findViewById(R.id.button_home).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_end).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_pgup).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_pgdn).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f1).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f2).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f3).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f4).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f5).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f6).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f7).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f8).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f9).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f10).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f11).setOnClickListener(emulatedKeysListener);
+ findViewById(R.id.button_f12).setOnClickListener(emulatedKeysListener);
+
actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
@@ -467,9 +604,50 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
});
+ final HorizontalScrollView keyboardScroll = (HorizontalScrollView) findViewById(R.id.keyboard_hscroll);
+ if (!hardKeyboard) {
+ // Show virtual keyboard and scroll back and forth
+ showEmulatedKeys(false);
+ keyboardScroll.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final int xscroll = findViewById(R.id.button_f12).getRight();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "smoothScrollBy(toEnd[" + xscroll + "])");
+ }
+ keyboardScroll.smoothScrollBy(xscroll, 0);
+ keyboardScroll.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "smoothScrollBy(toStart[" + (-xscroll) + "])");
+ }
+ keyboardScroll.smoothScrollBy(-xscroll, 0);
+ }
+ }, 500);
+ }
+ }, 500);
+ }
+
+ // Reset keyboard auto-hide timer when scrolling
+ keyboardScroll.setOnTouchListener(
+ new OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ autoHideEmulatedKeys();
+ break;
+ case MotionEvent.ACTION_UP:
+ v.performClick();
+ return (true);
+ }
+ return (false);
+ }
+ });
+
tabs = (TabLayout) findViewById(R.id.tabs);
if (tabs != null)
- tabs.setupWithViewPager(pager);
+ setupTabLayoutWithViewPager();
// detect fling gestures to switch between terminals
final GestureDetector detect = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@@ -634,7 +812,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
&& event.getEventTime() - event.getDownTime() < CLICK_TIME
&& Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE
&& Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) {
- showEmulatedKeys();
+ showEmulatedKeys(true);
}
// pass any touch events back to detector
@@ -645,6 +823,32 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
/**
+ * Ties the {@link TabLayout} to the {@link ViewPager}.
+ *
+ * <p>This method will:
+ * <ul>
+ * <li>Add a {@link ViewPager.OnPageChangeListener} that will forward events to
+ * this TabLayout.</li>
+ * <li>Populate the TabLayout's tabs from the ViewPager's {@link PagerAdapter}.</li>
+ * <li>Set our {@link TabLayout.OnTabSelectedListener} which will forward
+ * selected events to the ViewPager</li>
+ * </ul>
+ * </p>
+ */
+ public void setupTabLayoutWithViewPager() {
+ tabs.setTabsFromPagerAdapter(adapter);
+ pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabs));
+ tabs.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(pager));
+
+ if (adapter.getCount() > 0) {
+ final int curItem = pager.getCurrentItem();
+ if (tabs.getSelectedTabPosition() != curItem) {
+ tabs.getTabAt(curItem).select();
+ }
+ }
+ }
+
+ /**
*
*/
private void configureOrientation() {
@@ -790,26 +994,26 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
new AlertDialog.Builder(ConsoleActivity.this)
- .setView(resizeView)
- .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- int width, height;
- try {
- width = Integer.parseInt(((EditText) resizeView
- .findViewById(R.id.width))
- .getText().toString());
- height = Integer.parseInt(((EditText) resizeView
- .findViewById(R.id.height))
- .getText().toString());
- } catch (NumberFormatException nfe) {
- // TODO change this to a real dialog where we can
- // make the input boxes turn red to indicate an error.
- return;
+ .setView(resizeView)
+ .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ int width, height;
+ try {
+ width = Integer.parseInt(((EditText) resizeView
+ .findViewById(R.id.width))
+ .getText().toString());
+ height = Integer.parseInt(((EditText) resizeView
+ .findViewById(R.id.height))
+ .getText().toString());
+ } catch (NumberFormatException nfe) {
+ // TODO change this to a real dialog where we can
+ // make the input boxes turn red to indicate an error.
+ return;
+ }
+
+ terminalView.forceSize(width, height);
}
-
- terminalView.forceSize(width, height);
- }
- }).setNegativeButton(android.R.string.cancel, null).create().show();
+ }).setNegativeButton(android.R.string.cancel, null).create().show();
return true;
}
@@ -854,13 +1058,13 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, HostListActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ case android.R.id.home:
+ Intent intent = new Intent(this, HostListActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
}
}
@@ -994,6 +1198,18 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
unbindService(connection);
}
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ // Maintain selected host if connected.
+ if (adapter.getCurrentTerminalView() != null
+ && !adapter.getCurrentTerminalView().bridge.isDisconnected()) {
+ Uri uri = adapter.getCurrentTerminalView().bridge.host.getUri();
+ savedInstanceState.putString(STATE_SELECTED_URI, uri.toString());
+ }
+
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
private void startCopyMode() {
// mark as copying and reset any previous bounds
TerminalView terminalView = (TerminalView) adapter.getCurrentTerminalView();
@@ -1179,7 +1395,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
// and add our terminal view control, using index to place behind overlay
final TerminalView terminal = new TerminalView(container.getContext(), bridge);
- terminal.setId(R.id.console_flip);
+ terminal.setId(R.id.terminal_view);
view.addView(terminal, 0);
// Tag the view with its bridge so it can be retrieved later.
@@ -1204,7 +1420,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
View view = (View) object;
- TerminalView terminal = (TerminalView) view.findViewById(R.id.console_flip);
+ TerminalView terminal = (TerminalView) view.findViewById(R.id.terminal_view);
HostBean host = terminal.bridge.host;
int itemIndex = POSITION_NONE;
@@ -1257,7 +1473,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
public TerminalView getCurrentTerminalView() {
View currentView = pager.findViewWithTag(getBridgeAtPosition(pager.getCurrentItem()));
if (currentView == null) return null;
- return (TerminalView) currentView.findViewById(R.id.console_flip);
+ return (TerminalView) currentView.findViewById(R.id.terminal_view);
}
}
}
diff --git a/app/src/main/java/org/connectbot/HostListActivity.java b/app/src/main/java/org/connectbot/HostListActivity.java
index 2d8e882..1fe634e 100644
--- a/app/src/main/java/org/connectbot/HostListActivity.java
+++ b/app/src/main/java/org/connectbot/HostListActivity.java
@@ -20,6 +20,7 @@ package org.connectbot;
import java.util.List;
import org.connectbot.bean.HostBean;
+import org.connectbot.service.OnHostStatusChangedListener;
import org.connectbot.service.TerminalBridge;
import org.connectbot.service.TerminalManager;
import org.connectbot.transport.TransportFactory;
@@ -32,17 +33,15 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.Intent.ShortcutIconResource;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
-import android.content.Intent.ShortcutIconResource;
import android.content.SharedPreferences.Editor;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
import android.util.Log;
@@ -51,19 +50,20 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-public class HostListActivity extends ListActivity {
+public class HostListActivity extends ListActivity implements OnHostStatusChangedListener {
public final static String TAG = "CB.HostListActivity";
public static final String DISCONNECT_ACTION = "org.connectbot.action.DISCONNECT";
@@ -96,13 +96,6 @@ public class HostListActivity extends ListActivity {
*/
private boolean closeOnDisconnectAll = true;
- protected Handler updateHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- HostListActivity.this.updateList();
- }
- };
-
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
bound = ((TerminalManager.TerminalBinder) service).getService();
@@ -110,12 +103,16 @@ public class HostListActivity extends ListActivity {
// update our listview binder to find the service
HostListActivity.this.updateList();
+ bound.registerOnHostStatusChangedListener(HostListActivity.this);
+
if (waitingForDisconnectAll) {
disconnectAll();
}
}
public void onServiceDisconnected(ComponentName className) {
+ bound.unregisterOnHostStatusChangedListener(HostListActivity.this);
+
bound = null;
HostListActivity.this.updateList();
}
@@ -161,9 +158,6 @@ public class HostListActivity extends ListActivity {
closeOnDisconnectAll = waitingForDisconnectAll && closeOnDisconnectAll;
}
- /* (non-Javadoc)
- * @see android.app.Activity#onNewIntent(android.content.Intent)
- */
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -370,7 +364,6 @@ public class HostListActivity extends ListActivity {
connect.setOnMenuItemClickListener(new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
bridge.dispatchDisconnect(true);
- updateHandler.sendEmptyMessage(-1);
return true;
}
});
@@ -410,7 +403,7 @@ public class HostListActivity extends ListActivity {
bridge.dispatchDisconnect(true);
hostdb.deleteHost(host);
- updateHandler.sendEmptyMessage(-1);
+ updateList();
}
})
.setNegativeButton(R.string.delete_neg, null).create().show();
@@ -433,7 +426,6 @@ public class HostListActivity extends ListActivity {
.setPositiveButton(R.string.disconnect_all_pos, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
bound.disconnectAll(true, false);
- updateHandler.sendEmptyMessage(-1);
waitingForDisconnectAll = false;
// Clear the intent so that the activity can be relaunched without closing.
@@ -511,8 +503,14 @@ public class HostListActivity extends ListActivity {
this.setListAdapter(adapter);
}
- class HostAdapter extends ArrayAdapter<HostBean> {
- private List<HostBean> hosts;
+ @Override
+ public void onHostStatusChanged() {
+ updateList();
+ }
+
+ static class HostAdapter extends BaseAdapter {
+ private final LayoutInflater inflater;
+ private final List<HostBean> hosts;
private final TerminalManager manager;
private final ColorStateList red, green, blue;
@@ -525,8 +523,7 @@ public class HostListActivity extends ListActivity {
}
public HostAdapter(Context context, List<HostBean> hosts, TerminalManager manager) {
- super(context, R.layout.item_host, hosts);
-
+ this.inflater = LayoutInflater.from(context);
this.hosts = hosts;
this.manager = manager;
@@ -539,7 +536,7 @@ public class HostListActivity extends ListActivity {
* Check if we're connected to a terminal with the given host.
*/
private int getConnectedState(HostBean host) {
- // always disconnected if we dont have backend service
+ // always disconnected if we don't have backend service
if (this.manager == null)
return STATE_UNKNOWN;
@@ -553,6 +550,32 @@ public class HostListActivity extends ListActivity {
}
@Override
+ public int getCount() {
+ return hosts.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return hosts.get(position);
+ }
+
+ /**
+ * Use the database's IDs for the host.
+ */
+ @Override
+ public long getItemId(int position) {
+ return hosts.get(position).getId();
+ }
+
+ /**
+ * Since we're using the database's IDs, they're unchanging.
+ */
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
@@ -566,8 +589,9 @@ public class HostListActivity extends ListActivity {
holder.icon = (ImageView) convertView.findViewById(android.R.id.icon);
convertView.setTag(holder);
- } else
+ } else {
holder = (ViewHolder) convertView.getTag();
+ }
HostBean host = hosts.get(position);
if (host == null) {
@@ -609,8 +633,8 @@ public class HostListActivity extends ListActivity {
holder.caption.setTextColor(chosen);
} else {
// selected, so revert back to default black text
- holder.nickname.setTextAppearance(context, android.R.attr.textAppearanceLarge);
- holder.caption.setTextAppearance(context, android.R.attr.textAppearanceSmall);
+ holder.nickname.setTextAppearance(context, android.R.style.TextAppearance_Large);
+ holder.caption.setTextAppearance(context, android.R.style.TextAppearance_Small);
}
CharSequence nice = context.getString(R.string.bind_never);
diff --git a/app/src/main/java/org/connectbot/service/OnHostStatusChangedListener.java b/app/src/main/java/org/connectbot/service/OnHostStatusChangedListener.java
new file mode 100644
index 0000000..0d6ced7
--- /dev/null
+++ b/app/src/main/java/org/connectbot/service/OnHostStatusChangedListener.java
@@ -0,0 +1,26 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2015 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.connectbot.service;
+
+/**
+ * Used to notify interested parties when a {@link TerminalBridge} has changed materially
+ * changed status (e.g., connected, disconnected, name changed, etc).
+ */
+public interface OnHostStatusChangedListener {
+ public void onHostStatusChanged();
+}
diff --git a/app/src/main/java/org/connectbot/service/TerminalManager.java b/app/src/main/java/org/connectbot/service/TerminalManager.java
index a15dff0..5416ddb 100644
--- a/app/src/main/java/org/connectbot/service/TerminalManager.java
+++ b/app/src/main/java/org/connectbot/service/TerminalManager.java
@@ -81,6 +81,8 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen
public BridgeDisconnectedListener disconnectListener = null;
+ private final ArrayList<OnHostStatusChangedListener> hostStatusChangedListeners = new ArrayList<>();
+
public Map<String, KeyHolder> loadedKeypairs = new HashMap<String, KeyHolder>();
public Resources res;
@@ -252,6 +254,8 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen
// also update database with new connected time
touchHost(host);
+ notifyHostStatusChanged();
+
return bridge;
}
@@ -354,6 +358,8 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen
disconnected.add(bridge.host);
}
+ notifyHostStatusChanged();
+
if (shouldHideRunningNotification) {
ConnectionNotifier.getInstance().hideRunningNotification(this);
}
@@ -719,4 +725,28 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen
mPendingReconnect.clear();
}
}
+
+ /**
+ * Register a {@code listener} that wants to know when a host's status materially changes.
+ * @see #hostStatusChangedListeners
+ */
+ public void registerOnHostStatusChangedListener(OnHostStatusChangedListener listener) {
+ if (!hostStatusChangedListeners.contains(listener)) {
+ hostStatusChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a {@code listener} that wants to know when a host's status materially changes.
+ * @see #hostStatusChangedListeners
+ */
+ public void unregisterOnHostStatusChangedListener(OnHostStatusChangedListener listener) {
+ hostStatusChangedListeners.remove(listener);
+ }
+
+ private void notifyHostStatusChanged() {
+ for (OnHostStatusChangedListener listener : hostStatusChangedListeners) {
+ listener.onHostStatusChanged();
+ }
+ }
}
diff --git a/app/src/main/java/org/connectbot/util/HostDatabase.java b/app/src/main/java/org/connectbot/util/HostDatabase.java
index d8cdf09..f721aeb 100644
--- a/app/src/main/java/org/connectbot/util/HostDatabase.java
+++ b/app/src/main/java/org/connectbot/util/HostDatabase.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
+import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.trilead.ssh2.KnownHosts;
@@ -155,6 +156,10 @@ public class HostDatabase extends RobustSQLiteOpenHelper {
public void onCreate(SQLiteDatabase db) {
super.onCreate(db);
+ createTables(db);
+ }
+
+ private void createTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_HOSTS
+ " (_id INTEGER PRIMARY KEY, "
+ FIELD_HOST_NICKNAME + " TEXT, "
@@ -203,6 +208,17 @@ public class HostDatabase extends RobustSQLiteOpenHelper {
db.execSQL(CREATE_TABLE_COLOR_DEFAULTS_INDEX);
}
+ @VisibleForTesting
+ public void resetDatabase() {
+ SQLiteDatabase db = getWritableDatabase();
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_PORTFORWARDS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_COLORS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_COLOR_DEFAULTS);
+ createTables(db);
+ db.close();
+ }
+
@Override
public void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException {
// Versions of the database before the Android Market release will be
diff --git a/app/src/main/jni/com_google_ase_Exec.cpp b/app/src/main/jni/com_google_ase_Exec.cpp
index b2fa39f..a9d1aba 100644
--- a/app/src/main/jni/com_google_ase_Exec.cpp
+++ b/app/src/main/jni/com_google_ase_Exec.cpp
@@ -90,7 +90,12 @@ static int create_subprocess(
}
fcntl(ptm, F_SETFD, FD_CLOEXEC);
- if(grantpt(ptm) || unlockpt(ptm) ||
+ if(
+#if !defined(ANDROID)
+ /* this actually doesn't do anything on Android */
+ grantpt(ptm) ||
+#endif
+ unlockpt(ptm) ||
ptsname_r(ptm, devname, sizeof(devname))){
LOG("[ trouble with /dev/ptmx - %s ]\n", strerror(errno));
return -1;
diff --git a/app/src/main/res/layout-large/act_console.xml b/app/src/main/res/layout-large/act_console.xml
index 44d15af..66f3d83 100644
--- a/app/src/main/res/layout-large/act_console.xml
+++ b/app/src/main/res/layout-large/act_console.xml
@@ -37,6 +37,8 @@
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium"
+ app:tabMode="scrollable"
+ app:tabContentStart="60dp"
app:tabIndicatorColor="@android:color/white"/>
<TextView
@@ -142,155 +144,199 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#55000000"
+ android:orientation="horizontal"
android:padding="0dip"
android:visibility="gone">
- <Button
- android:id="@+id/button_ctrl"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
+ <HorizontalScrollView
+ android:id="@+id/keyboard_hscroll"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_toggle_control_character"
android:padding="0dp"
- android:text="@string/button_key_ctrl"
- android:textSize="10dip"
- />
+ android:scrollbars="none">
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:padding="0dp">
- <Button
- android:id="@+id/button_esc"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_send_escape_character"
- android:padding="0dp"
- android:text="@string/button_key_esc"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_ctrl"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_toggle_control_character"
+ android:text="@string/button_key_ctrl"/>
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_esc"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_send_escape_character"
+ android:text="@string/button_key_esc"/>
- <Button
- android:id="@+id/button_tab"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_send_tab_character"
- android:padding="0dp"
- android:text="@string/button_key_tab"
- android:textSize="10dip"
- />
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_tab"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_send_tab_character"
+ android:text="@string/button_key_tab"/>
- <Button
- android:id="@+id/button_up"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_up"
- android:padding="0dp"
- android:text="@string/button_key_up"
- android:textSize="10dip"
- />
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_up"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_up"
+ android:text="@string/button_key_up"/>
- <Button
- android:id="@+id/button_down"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_down"
- android:padding="0dp"
- android:text="@string/button_key_down"
- android:textSize="10dip"
- />
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_down"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_down"
+ android:text="@string/button_key_down"/>
- <Button
- android:id="@+id/button_left"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_left"
- android:padding="0dp"
- android:text="@string/button_key_left"
- android:textSize="10dip"
- />
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_left"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_left"
+ android:text="@string/button_key_left"/>
- <Button
- android:id="@+id/button_right"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_right"
- android:padding="0dp"
- android:text="@string/button_key_right"
- android:textSize="10dip"
- />
+ <View style="@style/KeyboardSeparator"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <Button
+ android:id="@+id/button_right"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_right"
+ android:text="@string/button_key_right"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_home"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_home"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_end"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_end"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_pgup"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_pgup"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_pgdn"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_pgdn"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f1"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f1"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f2"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f2"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f3"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f3"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f4"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f4"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f5"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f5"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f6"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f6"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f7"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f7"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f8"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f8"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f9"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f9"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f10"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f10"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f11"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f11"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f12"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f12"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <View style="@style/KeyboardSeparator"/>
<ImageView
android:id="@+id/button_keyboard"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
+ style="@style/KeyboardKey"
+ android:background="#55b0b0f0"
android:contentDescription="@string/image_description_show_keyboard"
- android:padding="0dp"
- android:src="@drawable/button_keyboard"
- android:textSize="10dip"
- />
-
+ android:src="@drawable/button_keyboard"/>
</LinearLayout>
</RelativeLayout>
diff --git a/app/src/main/res/layout/act_console.xml b/app/src/main/res/layout/act_console.xml
index 8787265..4bc5ccb 100644
--- a/app/src/main/res/layout/act_console.xml
+++ b/app/src/main/res/layout/act_console.xml
@@ -126,155 +126,201 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#55000000"
+ android:orientation="horizontal"
android:padding="0dip"
android:visibility="gone">
- <Button
- android:id="@+id/button_ctrl"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
+ <HorizontalScrollView
+ android:id="@+id/keyboard_hscroll"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_toggle_control_character"
android:padding="0dp"
- android:text="@string/button_key_ctrl"
- android:textSize="10dip"
- />
+ android:scrollbars="none">
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:padding="0dp">
- <Button
- android:id="@+id/button_esc"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_send_escape_character"
- android:padding="0dp"
- android:text="@string/button_key_esc"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_ctrl"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_toggle_control_character"
+ android:text="@string/button_key_ctrl"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
- <Button
- android:id="@+id/button_tab"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_send_tab_character"
- android:padding="0dp"
- android:text="@string/button_key_tab"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_esc"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_send_escape_character"
+ android:text="@string/button_key_esc"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
- <Button
- android:id="@+id/button_up"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_up"
- android:padding="0dp"
- android:text="@string/button_key_up"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_tab"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_send_tab_character"
+ android:text="@string/button_key_tab"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
- <Button
- android:id="@+id/button_down"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_down"
- android:padding="0dp"
- android:text="@string/button_key_down"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_up"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_up"
+ android:text="@string/button_key_up"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
- <Button
- android:id="@+id/button_left"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_left"
- android:padding="0dp"
- android:text="@string/button_key_left"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_down"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_down"
+ android:text="@string/button_key_down"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
- <Button
- android:id="@+id/button_right"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
- android:contentDescription="@string/image_description_right"
- android:padding="0dp"
- android:text="@string/button_key_right"
- android:textSize="10dip"
- />
+ <Button
+ android:id="@+id/button_left"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_left"
+ android:text="@string/button_key_left"/>
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#90000000"
- />
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_right"
+ style="@style/KeyboardButton"
+ android:contentDescription="@string/image_description_right"
+ android:text="@string/button_key_right"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_home"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_home"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_end"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_end"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_pgup"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_pgup"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_pgdn"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_pgdn"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f1"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f1"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f2"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f2"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f3"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f3"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f4"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f4"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f5"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f5"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f6"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f6"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f7"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f7"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f8"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f8"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f9"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f9"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f10"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f10"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f11"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f11"/>
+
+ <View style="@style/KeyboardSeparator"/>
+
+ <Button
+ android:id="@+id/button_f12"
+ style="@style/KeyboardButton"
+ android:text="@string/button_key_f12"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <View style="@style/KeyboardSeparator"/>
<ImageView
android:id="@+id/button_keyboard"
- android:layout_width="0px"
- android:layout_height="30dip"
- android:layout_margin="0dp"
- android:layout_weight="1"
- android:background="#55f0f0f0"
+ style="@style/KeyboardKey"
+ android:background="#55b0b0f0"
android:contentDescription="@string/image_description_show_keyboard"
- android:padding="0dp"
- android:src="@drawable/button_keyboard"
- android:textSize="10dip"
- />
-
+ android:src="@drawable/button_keyboard"/>
</LinearLayout>
+
</RelativeLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ecbe757..c6086da 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -555,4 +555,37 @@
<string name="button_key_ctrl">Ctrl</string>
<!-- Text for the "Tab" button in virtual keyboard. -->
<string name="button_key_tab">Tab</string>
+
+ <!-- Text for the "Home" button in virtual keyboard. -->
+ <string name="button_key_home">Home</string>
+ <!-- Text for the "End" button in virtual keyboard. -->
+ <string name="button_key_end">End</string>
+ <!-- Text for the "Page Up" button in virtual keyboard. -->
+ <string name="button_key_pgup">PgUp</string>
+ <!-- Text for the "Page Down" button in virtual keyboard. -->
+ <string name="button_key_pgdn">PgDn</string>
+ <!-- Text for the "F1" button in virtual keyboard. -->
+ <string name="button_key_f1">F1</string>
+ <!-- Text for the "F2" button in virtual keyboard. -->
+ <string name="button_key_f2">F2</string>
+ <!-- Text for the "F3" button in virtual keyboard. -->
+ <string name="button_key_f3">F3</string>
+ <!-- Text for the "F4" button in virtual keyboard. -->
+ <string name="button_key_f4">F4</string>
+ <!-- Text for the "F5" button in virtual keyboard. -->
+ <string name="button_key_f5">F5</string>
+ <!-- Text for the "F6" button in virtual keyboard. -->
+ <string name="button_key_f6">F6</string>
+ <!-- Text for the "F7" button in virtual keyboard. -->
+ <string name="button_key_f7">F7</string>
+ <!-- Text for the "F8" button in virtual keyboard. -->
+ <string name="button_key_f8">F8</string>
+ <!-- Text for the "F9" button in virtual keyboard. -->
+ <string name="button_key_f9">F9</string>
+ <!-- Text for the "F10" button in virtual keyboard. -->
+ <string name="button_key_f10">F10</string>
+ <!-- Text for the "F11" button in virtual keyboard. -->
+ <string name="button_key_f11">F11</string>
+ <!-- Text for the "F12" button in virtual keyboard. -->
+ <string name="button_key_f12">F12</string>
</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3ad5fc0
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+
+ <style name="KeyboardKey">
+ <item name="android:layout_width">45dip</item>
+ <item name="android:layout_height">30dip</item>
+ <item name="android:layout_margin">0dp</item>
+ <item name="android:padding">0dp</item>
+ <item name="android:textSize">10sp</item>
+ </style>
+
+
+ <style name="KeyboardButton" parent="KeyboardKey">
+ <item name="android:background">#55f0f0f0</item>
+ </style>
+
+
+ <style name="KeyboardSeparator">
+ <item name="android:layout_width">1dip</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:background">#90000000</item>
+ </style>
+</resources>
diff --git a/app/src/main/res/values/viewpager.xml b/app/src/main/res/values/viewpager.xml
new file mode 100644
index 0000000..cc73c50
--- /dev/null
+++ b/app/src/main/res/values/viewpager.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ ConnectBot: simple, powerful, open-source SSH client for Android
+ ~ Copyright 2015 Kenny Root, Jeffrey Sharkey
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- This is so that ConsoleActivity has something to refer to when
+ setting the ID of the TerminalView instances in the ViewPager. -->
+ <item name="terminal_view" type="id"/>
+</resources>
diff --git a/app/src/test/java/android/net/http/AndroidHttpClient.java b/app/src/test/java/android/net/http/AndroidHttpClient.java
new file mode 100644
index 0000000..701510b
--- /dev/null
+++ b/app/src/test/java/android/net/http/AndroidHttpClient.java
@@ -0,0 +1,25 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2015 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+/**
+ * This is a work-around until Robolectric 3.1 comes out with the fix for this.
+ * https://github.com/robolectric/robolectric/issues/1862
+ */
+public class AndroidHttpClient {
+}