diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .idea/codeStyleSettings.xml | 28 | ||||
-rw-r--r-- | .travis.yml | 4 | ||||
-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 | 6 | ||||
-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/values/viewpager.xml | 23 | ||||
-rw-r--r-- | app/src/test/java/android/net/http/AndroidHttpClient.java | 25 | ||||
-rw-r--r-- | build.xml | 133 | ||||
-rw-r--r-- | config/jacoco.gradle | 8 | ||||
-rw-r--r-- | connectbot.iml | 2 | ||||
-rw-r--r-- | gradle.properties | 1 |
19 files changed, 446 insertions, 218 deletions
@@ -1,7 +1,3 @@ -# generated files -bin/ -gen/ - # translation artifacts launchpad-*.tar.gz diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 659f221..1ca6443 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -42,6 +42,34 @@ <AndroidXmlCodeStyleSettings> <option name="USE_CUSTOM_SETTINGS" value="true" /> </AndroidXmlCodeStyleSettings> + <Objective-C-extensions> + <option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" /> + <option name="RELEASE_STYLE" value="IVAR" /> + <option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" /> + <file> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" /> + </file> + <class> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" /> + <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" /> + </class> + <extensions> + <pair source="cpp" header="h" /> + <pair source="c" header="h" /> + </extensions> + </Objective-C-extensions> <XML> <option name="XML_ALIGN_ATTRIBUTES" value="false" /> <option name="XML_WHITE_SPACE_AROUND_CDATA" value="1" /> diff --git a/.travis.yml b/.travis.yml index 540cff3..b27f36f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,14 +47,14 @@ before_script: android: components: - build-tools-22.0.1 - - android-22 + - android-23 - extra-android-support - extra-android-m2repository license: - '.+' script: - - ./gradlew build check jacocoTestDebugReport + - ./gradlew build check jacocoUnitTestDebugReport - ./scripts/check-lint-count.sh app/build/outputs/lint-results.xml $HOME/.cache/lint/issue-count.txt $HOME/.cache/lint/issue-count.txt after_success: ./gradlew coveralls 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 b085973..4f8d301 100644 --- a/app/src/main/java/org/connectbot/ConsoleActivity.java +++ b/app/src/main/java/org/connectbot/ConsoleActivity.java @@ -1374,7 +1374,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. @@ -1399,7 +1399,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; @@ -1452,7 +1452,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/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 { +} diff --git a/build.xml b/build.xml deleted file mode 100644 index 376125b..0000000 --- a/build.xml +++ /dev/null @@ -1,133 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- vim: set ts=4 sw=4 et: --> -<project name="ConnectBot" default="help"> - -<!-- Begin custom ConnectBot stuff --> - - <!-- Output directory for .po files. --> - <property name="locale.dir" value="locale" /> - - <!-- Default args to pass to a2po for .po generation. --> - <property name="a2po.args" value="--groups strings --template fortune/fortune.pot --layout 'fortune/fortune-%(locale)s.po'"/> - - <!-- File names for launchpad translations. --> - <property name="launchpad.export.file" value="launchpad-export.tar.gz"/> - <property name="launchpad.import.file" value="launchpad-import.tar.gz"/> - - <target name="-pre-build" depends="update-version"/> - - <target name="help"> - <!-- displays starts at col 13 - |13 80| --> - <echo>Android Ant Build. Available targets:</echo> - <echo> help: Displays this help.</echo> - <echo> clean: Removes output files created by other targets.</echo> - <echo> compile: Compiles project's .java files into .class files.</echo> - <echo> debug: Builds the application and signs it with a debug key.</echo> - <echo> release: Builds the application. The generated apk file must be</echo> - <echo> signed before it is published.</echo> - <echo> install: Installs/reinstalls the debug package onto a running</echo> - <echo> emulator or device.</echo> - <echo> If the application was previously installed, the</echo> - <echo> signatures must match.</echo> - <echo> uninstall: Uninstalls the application from a running emulator or</echo> - <echo> device.</echo> - <echo> translations-import: Import translations from a Launchpad-style</echo> - <echo> Rosetta translation.</echo> - <echo> translations-export: Export translations to a Launchpad-style</echo> - <echo> Rosetta translation.</echo> - - </target> - - <target name="update-version" description="Updates the Version.java file with current Git revision"> - <echo>Updating resources with Git revision and build date...</echo> - - <tstamp> - <format property="build.date" pattern="yyyy.MM.dd" /> - </tstamp> - - <!-- Get the version name from the android manifest, will en up in property ${manifest.android:versionName} --> - <xpath input="AndroidManifest.xml" expression="/manifest/@android:versionName" - output="manifest.version.name" /> - - <!-- tell git to ignore changes to the version xml --> - <exec executable="git"> - <arg line="update-index --assume-unchanged res/values/version.xml"/> - </exec> - - <!-- find out the description of the current Git commit --> - <exec executable="git" outputproperty="git.revision"> - <arg line="describe --match 'v[0-9]*' --dirty"/> - </exec> - - <!-- write out to res/values/version.xml --> - <echo file="${resource.absolute.dir}/values/version.xml" encoding="utf8"><![CDATA[<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="msg_version" translatable="false">${ant.project.name} ${manifest.version.name} (${git.revision} ${build.date})</string> -</resources> -]]></echo> - - <echo>Updated "msg_version" to: ${ant.project.name} ${manifest.version.name} (${git.revision} ${build.date})</echo> - </target> - - <!-- Translations come from launchpad.net and are placed in the - locale/ subdirectory. --> - <target name="translations-import"> - <untar src="${launchpad.export.file}" dest="${locale.dir}" compression="gzip"/> - <exec executable="a2po" failonerror="true"> - <arg value="import"/> - <arg line="${a2po.args}"/> - </exec> - </target> - - <!-- Translations are to be uploaded to launchpad.net as a tar ball - created from the locale/ subdirectory. --> - <target name="translations-export"> - <exec executable="a2po" failonerror="true"> - <arg value="export"/> - <arg line="${a2po.args}"/> - </exec> - <tar destfile="${launchpad.import.file}" - compression="gzip" - basedir="${locale.dir}" - includes="**/*.pot **/*.po" /> - </target> - - <target name="-check-ndk"> - <fail - message="ndk.dir is missing. Make sure to put it in local.properties" - unless="ndk.dir" - /> - </target> - - <target name="native-build" depends="-check-ndk"> - <exec executable="${ndk.dir}/ndk-build" failonerror="true" /> - </target> - - <target name="native-clean" depends="-check-ndk"> - <exec executable="${ndk.dir}/ndk-build" failonerror="true"> - <arg value="clean" /> - </exec> - </target> - - <property name="lint" location="${android.tools.dir}/lint${bat}" /> - - <target name="lint-xml"> - <exec executable="${lint}"> - <arg value="--xml" /> - <arg value="lint-results.xml" /> - <arg path="${basedir}" /> - </exec> - </target> - - <target name="lint-html"> - <exec executable="${lint}"> - <arg value="--html" /> - <arg value="lint-results.html" /> - <arg path="${basedir}" /> - </exec> - </target> - -<!-- End custom ConnectBot stuff --> - -</project> diff --git a/config/jacoco.gradle b/config/jacoco.gradle index c603349..25ac311 100644 --- a/config/jacoco.gradle +++ b/config/jacoco.gradle @@ -16,9 +16,9 @@ tasks.withType(Test).whenTaskAdded { } // These tasks are for generating the coverage report after JUnit+robolectric tests have executed -tasks.create(name: "jacocoTestDebugReport", type: JacocoReport, dependsOn: "testDebug") { +tasks.create(name: "jacocoUnitTestDebugReport", type: JacocoReport, dependsOn: "testDebugUnitTest") { group = "Reporting" - description = "Generate Jacoco coverage reports" + description = "Generate Jacoco coverage reports for unit tests" classDirectories = fileTree( dir: "${project.buildDir}/intermediates/classes/debug/", @@ -30,7 +30,7 @@ tasks.create(name: "jacocoTestDebugReport", type: JacocoReport, dependsOn: "test ) sourceDirectories = files("src/main/java", "src/debug/java") - executionData = files("${project.buildDir}/jacoco/testDebug.exec") + executionData = files("${project.buildDir}/jacoco/testDebugUnitTest.exec") reports { xml.enabled = true @@ -38,4 +38,4 @@ tasks.create(name: "jacocoTestDebugReport", type: JacocoReport, dependsOn: "test } } -coveralls.jacocoReportPath = "${project.buildDir}/reports/jacoco/jacocoTestDebugReport/jacocoTestDebugReport.xml"
\ No newline at end of file +coveralls.jacocoReportPath = "${project.buildDir}/reports/jacoco/jacocoUnitTestDebugReport/jacocoUnitTestDebugReport.xml"
\ No newline at end of file diff --git a/connectbot.iml b/connectbot.iml index bb79c8b..4b158c6 100644 --- a/connectbot.iml +++ b/connectbot.iml @@ -8,7 +8,7 @@ </configuration> </facet> </component> - <component name="NewModuleRootManager" inherit-compiler-output="true"> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true"> <exclude-output /> <content url="file://$MODULE_DIR$"> <excludeFolder url="file://$MODULE_DIR$/.gradle" /> diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..dfbe478 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +android.useDeprecatedNdk=true |