diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/app.iml | 32 | ||||
-rw-r--r-- | app/build.gradle | 73 | ||||
-rw-r--r-- | app/src/androidTest/java/org/connectbot/StartupTest.java | 162 | ||||
-rw-r--r-- | app/src/main/AndroidManifest.xml | 4 | ||||
-rw-r--r-- | app/src/main/java/org/connectbot/ConsoleActivity.java | 306 | ||||
-rw-r--r-- | app/src/main/java/org/connectbot/HostListActivity.java | 80 | ||||
-rw-r--r-- | app/src/main/java/org/connectbot/service/OnHostStatusChangedListener.java | 26 | ||||
-rw-r--r-- | app/src/main/java/org/connectbot/service/TerminalManager.java | 30 | ||||
-rw-r--r-- | app/src/main/java/org/connectbot/util/HostDatabase.java | 16 | ||||
-rw-r--r-- | app/src/main/jni/com_google_ase_Exec.cpp | 7 | ||||
-rw-r--r-- | app/src/main/res/layout-large/act_console.xml | 300 | ||||
-rw-r--r-- | app/src/main/res/layout/act_console.xml | 300 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 33 | ||||
-rw-r--r-- | app/src/main/res/values/styles.xml | 41 | ||||
-rw-r--r-- | app/src/main/res/values/viewpager.xml | 23 | ||||
-rw-r--r-- | app/src/test/java/android/net/http/AndroidHttpClient.java | 25 |
16 files changed, 1088 insertions, 370 deletions
diff --git a/app/app.iml b/app/app.iml index 827213f..e6fba85 100644 --- a/app/app.iml +++ b/app/app.iml @@ -12,9 +12,13 @@ <option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> - <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" /> <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" /> + <afterSyncTasks> + <task>generateDebugSources</task> + <task>mockableAndroidJar</task> + <task>prepareDebugUnitTestDependencies</task> + </afterSyncTasks> <option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> @@ -23,7 +27,7 @@ </configuration> </facet> </component> - <component name="NewModuleRootManager" inherit-compiler-output="false"> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" /> <exclude-output /> @@ -33,7 +37,7 @@ <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> - <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> @@ -69,7 +73,9 @@ <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.1/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.0/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.0/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.0/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> @@ -82,38 +88,42 @@ <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/build/jacoco" /> <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + <excludeFolder url="file://$MODULE_DIR$/build/reports" /> + <excludeFolder url="file://$MODULE_DIR$/build/test-results" /> <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> </content> - <orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" /> + <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" exported="" scope="TEST" name="mockable-android-22" level="project" /> <orderEntry type="library" exported="" name="jzlib-1.1.3" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="maven-ant-tasks-2.1.3" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="ant-1.8.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="assertj-core-1.7.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="accessibility-test-framework-1.0" level="project" /> - <orderEntry type="library" exported="" name="support-annotations-22.2.1" level="project" /> - <orderEntry type="library" exported="" scope="TEST" name="ant-launcher-1.8.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="asm-commons-5.0.1" level="project" /> + <orderEntry type="library" exported="" scope="TEST" name="ant-launcher-1.8.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="asm-5.0.1" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="shadows-core-3.0" level="project" /> + <orderEntry type="library" exported="" name="appcompat-v7-23.0.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="asm-util-5.0.1" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="bcprov-jdk16-1.46" level="project" /> + <orderEntry type="library" exported="" name="support-v4-23.0.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.10.19" level="project" /> <orderEntry type="library" exported="" name="jsocks-1.0.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="objenesis-2.1" level="project" /> + <orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="icu4j-53.1" level="project" /> - <orderEntry type="library" exported="" name="support-v4-22.2.1" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="robolectric-resources-3.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="robolectric-annotations-3.0" level="project" /> + <orderEntry type="library" exported="" name="design-23.0.0" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="sqlite4java-0.282" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="asm-tree-5.0.1" level="project" /> - <orderEntry type="library" exported="" scope="TEST" name="asm-analysis-5.0.1" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="robolectric-utils-3.0" level="project" /> + <orderEntry type="library" exported="" scope="TEST" name="asm-analysis-5.0.1" level="project" /> <orderEntry type="library" exported="" name="sshlib-2.2.0" level="project" /> - <orderEntry type="library" exported="" scope="TEST" name="vtd-xml-2.11" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" /> + <orderEntry type="library" exported="" scope="TEST" name="vtd-xml-2.11" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" /> <orderEntry type="library" exported="" scope="TEST" name="robolectric-3.0" level="project" /> </component> diff --git a/app/build.gradle b/app/build.gradle index d451fd4..e02e5bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,17 +12,11 @@ buildscript { dependencies { classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1x' - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } -repositories { - maven { - url 'https://dl.bintray.com/connectbot/maven' - } -} - dependencies { compile 'org.connectbot:sshlib:2.2.0' @@ -36,36 +30,51 @@ dependencies { } android { - compileSdkVersion 22 + compileSdkVersion 23 buildToolsVersion "22.0.1" defaultConfig { applicationId "org.connectbot" minSdkVersion 8 - targetSdkVersion 22 + targetSdkVersion 23 ndk { moduleName "com_google_ase_Exec" ldLibs "log" } - lintOptions { - abortOnError false - lintConfig file('lint.xml') - } - testApplicationId "org.connectbot.tests" - testInstrumentationRunner "android.test.InstrumentationTestRunner" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } signingConfigs { - release + if (project.hasProperty('keystorePassword')) { + release { + storeFile file(property('keystoreFile')) + storePassword property('keystorePassword') + keyAlias property('keystoreAlias') + keyPassword property('keystorePassword') + } + } } dependencies { - compile "com.android.support:support-v4:22.2.1" - compile "com.android.support:appcompat-v7:22.2.1" - compile "com.android.support:design:22.2.1" + compile "com.android.support:support-v4:23.0.0" + compile "com.android.support:appcompat-v7:23.0.0" + compile "com.android.support:design:23.0.0" + + androidTestCompile('com.android.support.test:runner:0.3') { + exclude module: "support-annotations" + } + androidTestCompile('com.android.support.test:rules:0.3') { + exclude module: "support-annotations" + } + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2') { + exclude module: "support-annotations" + } + androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2') { + exclude module: "support-annotations" + } } buildTypes { @@ -74,8 +83,11 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg' if (project.hasProperty('keystorePassword')) { + //noinspection GroovyAssignabilityCheck signingConfig signingConfigs.release } + + return true // this silences Android Studio's groovy inspector } debug { @@ -83,13 +95,23 @@ android { testCoverageEnabled true } } + + lintOptions { + abortOnError false + lintConfig file('lint.xml') + } + + packagingOptions { + exclude 'META-INF/LICENSE.txt' + exclude 'LICENSE.txt' + } } def getGitDescription = { -> try { def stdout = new ByteArrayOutputStream() exec { - commandLine 'git', 'describe', '--tags', '--dirty' + commandLine 'git', 'describe', '--dirty' standardOutput = stdout } return stdout.toString().trim() @@ -104,17 +126,10 @@ android.applicationVariants.all { variant -> ext.env = System.getenv() def buildNumber = getGitDescription() if (buildNumber != null) { - File valuesFile = file("${buildDir}/intermediates/res/${variant.dirName}/values/values.xml") + File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml") String content = valuesFile.getText('UTF-8') content = content.replaceAll(/\(working copy\)/, buildNumber) valuesFile.write(content, 'UTF-8') } } -} - -if (project.hasProperty('keystorePassword')) { - android.signingConfigs.release.storeFile file(keystoreFile) - android.signingConfigs.release.storePassword keystorePassword - android.signingConfigs.release.keyAlias keystoreAlias - android.signingConfigs.release.keyPassword keystorePassword -} +}
\ No newline at end of file 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 { +} |