diff --git a/.gitignore b/.gitignore index b40390a31..21c3404ab 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,6 @@ local.properties !.idea/codeStyles .gradle build/ +captures *.iml projectFilesBackup/ \ No newline at end of file diff --git a/briar-android/.gitignore b/briar-android/.gitignore index 4d702fcd8..ab81bc92b 100644 --- a/briar-android/.gitignore +++ b/briar-android/.gitignore @@ -5,3 +5,9 @@ local.properties .settings src/main/assets/*.zip src/main/res/values-iw + +# Fastlane Screenshots +/fastlane/metadata/android/screenshots.html +/fastlane/metadata/android/*/images +/fastlane/report.xml +/fastlane/README.md \ No newline at end of file diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 77b2fcf6e..a39240a65 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -49,6 +49,16 @@ dependencies { testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.hamcrest:hamcrest-library:1.3" testImplementation "org.hamcrest:hamcrest-core:1.3" + + def espressoVersion = '3.0.2' + androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion" + androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion" + androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espressoVersion" + androidTestImplementation "tools.fastlane:screengrab:1.1.0" + androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:2.1.3" + androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.0.2" + androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' + androidTestImplementation 'junit:junit:4.12' } dependencyVerification { @@ -66,6 +76,14 @@ dependencyVerification { 'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05', 'com.android.support.constraint:constraint-layout-solver:1.1.0:constraint-layout-solver-1.1.0.jar:fcb4c7d705754ca3d69b1b2c3caf445a425599fda8caabbcf855d98ea0663e4e', 'com.android.support.constraint:constraint-layout:1.1.0:constraint-layout-1.1.0.aar:d490188709b7bb2f11609beadd7e5eb7538892f308828ec3ff261a74e6ecf47e', + 'com.android.support.test.espresso:espresso-contrib:3.0.2:espresso-contrib-3.0.2.aar:eacb4a10dde5597b8a6b8668804d4b63e3ae2d46a78192068532922fec0b4a66', + 'com.android.support.test.espresso:espresso-core:3.0.2:espresso-core-3.0.2.aar:f40bf62e26e6f95a9c376c4e318415a77053b7dbb7ec12688eb6fab93dffdf73', + 'com.android.support.test.espresso:espresso-idling-resource:3.0.2:espresso-idling-resource-3.0.2.aar:c6485150f9f4aea1ce9d138f3d60d82ebed3fe35b340a8b1dc975ff01f3b17b2', + 'com.android.support.test.espresso:espresso-intents:3.0.2:espresso-intents-3.0.2.aar:556f99e8c8723a9ef313ed816fb9074d65903c6767521a66b099720d2cc21f10', + 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3:uiautomator-v18-2.1.3.aar:15e6b3c7104859630bf844e31805aa7cb2eb4b385e6119ab34132c8258eee2c4', + 'com.android.support.test:monitor:1.0.2:monitor-1.0.2.aar:38ef4fa98a32dc55550ff49bb36a583e178b3a9b830fcb8dcc27bfc4254bc2bc', + 'com.android.support.test:rules:1.0.2:rules-1.0.2.aar:7ddad387d1a16d4dbdbefacee070d34574e565b008117c1a163edac8ae02a6aa', + 'com.android.support.test:runner:1.0.2:runner-1.0.2.aar:f04b9ae342975ba1cb3e4a06e13426e3e6b8a73faa45acba604493d83c9a4f00', 'com.android.support:animated-vector-drawable:27.1.1:animated-vector-drawable-27.1.1.aar:59670473f6e98fda792f7bef25dd7292b0a3106031c7a5e30eb020bf26f077bd', 'com.android.support:appcompat-v7:27.1.1:appcompat-v7-27.1.1.aar:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335', 'com.android.support:cardview-v7:27.1.1:cardview-v7-27.1.1.aar:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886', @@ -109,8 +127,10 @@ dependencyVerification { 'com.android.tools:sdk-common:26.1.3:sdk-common-26.1.3.jar:1948603ca9ff22c7ebb3178000bffa3a9dd2ca1cc5cb0c793cae08468b8fcfc1', 'com.android.tools:sdklib:26.1.3:sdklib-26.1.3.jar:4adcfaad9514607098d2c51503c39811112d3050f4d1e744c01c7f08f591032b', 'com.github.bumptech.glide:glide:3.8.0:glide-3.8.0.jar:750d9e7b940dc0ee48f8680623b55d46e14e8727acc922d7b156e57e7c549655', + 'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f', 'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd', 'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', + 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.gson:gson:2.7:gson-2.7.jar:2d43eb5ea9e133d2ee2405cc14f5ee08951b8361302fdd93494a3a997b508d32', 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', @@ -127,6 +147,7 @@ dependencyVerification { 'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439', 'com.ibm.icu:icu4j:53.1:icu4j-53.1.jar:e37a4467bac5cdeb02c5c4b8e5063d2f4e67b69e3c7df6d6b610f13185572bab', 'com.jpardogo.materialtabstrip:library:1.1.0:library-1.1.0.aar:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311', + 'com.squareup:javawriter:2.1.1:javawriter-2.1.1.jar:f699823d0081f69cbb676c1845ea222e0ada79bc88a53e5d22d8bd02d328f57e', 'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0', 'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce', 'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4', @@ -181,6 +202,7 @@ dependencyVerification { 'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70', 'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', + 'org.hamcrest:hamcrest-integration:1.3:hamcrest-integration-1.3.jar:70f418efbb506c5155da5f9a5a33262ea08a9e4d7fea186aa9015c41a7224ac2', 'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd', 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926', @@ -212,6 +234,7 @@ dependencyVerification { 'org.robolectric:shadows-framework:3.5.1:shadows-framework-3.5.1.jar:597b54cc1a494799d783921c6ac04352f33e94fca8e00f299d4ca192db79e3fc', 'org.robolectric:shadows-support-v4:3.0:shadows-support-v4-3.0.jar:66bcc3257b037d72998e860d67b1bc58215b7eeac8ad860fcc3e613332d88619', 'org.robolectric:utils:3.5.1:utils-3.5.1.jar:d7d77326867e6d903156ebb18c244819b26aebe3aa82a1c57081081a0b6c4f63', + 'tools.fastlane:screengrab:1.1.0:screengrab-1.1.0.aar:03ce3868ee8a0082d14e7a1de0999f91531c0cc794392688beb08ee9bc4495fd', 'uk.co.samuelwall:material-tap-target-prompt:2.8.0:material-tap-target-prompt-2.8.0.aar:ac70770c05bbc4675a1d5712c0e53d46ee4fa961b74947589fce50d8003065ec', 'xmlpull:xmlpull:1.1.3.1:xmlpull-1.1.3.1.jar:34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63', 'xpp3:xpp3_min:1.1.4c:xpp3_min-1.1.4c.jar:bfc90e9e32d0eab1f397fb974b5f150a815188382ac41f372a7149d5bc178008', @@ -241,24 +264,22 @@ android { versionCode 10013 versionName "1.0.13" applicationId "org.briarproject.briar.android" - resValue "string", "app_package", "org.briarproject.briar.android" - resValue "string", "app_name", "Briar" buildConfigField "String", "GitHash", "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" def now = (long) (System.currentTimeMillis() / 1000) buildConfigField "Long", "BuildTimestamp", "${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L" + testInstrumentationRunner 'org.briarproject.briar.android.test.BriarTestRunner' } buildTypes { debug { applicationIdSuffix ".debug" - resValue "string", "app_package", "org.briarproject.briar.android.debug" - resValue "string", "app_name", "Briar Debug" shrinkResources false minifyEnabled true crunchPngs false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-test.txt' } release { shrinkResources false @@ -268,6 +289,23 @@ android { } } + flavorDimensions "version" + productFlavors { + screenshot { + dimension "version" + minSdkVersion 18 + applicationIdSuffix ".screenshot" // = org.briarproject.briar.android.screenshot.debug + } + main { + dimension "version" + } + } + variantFilter { variant -> + if (variant.flavors*.name.contains("screenshot") && variant.buildType.name == "release") { + setIgnore(true) + } + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/briar-android/fastlane/Appfile b/briar-android/fastlane/Appfile new file mode 100644 index 000000000..05204c92c --- /dev/null +++ b/briar-android/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("org.briarproject.briar.android") diff --git a/briar-android/fastlane/Fastfile b/briar-android/fastlane/Fastfile new file mode 100644 index 000000000..01035f6ec --- /dev/null +++ b/briar-android/fastlane/Fastfile @@ -0,0 +1,30 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + desc "Takes screenshots for manual and Google Play" + lane :screenshots do + gradle(project_dir: "..", task: "assembleScreenshot assembleAndroidTest") + system './demo-mode-activate.sh' + capture_android_screenshots + system './demo-mode-deactivate.sh' + system './rename_screenshots.py' + end +end + + +# vi:syntax=ruby diff --git a/briar-android/fastlane/Screengrabfile b/briar-android/fastlane/Screengrabfile new file mode 100644 index 000000000..bacf6905b --- /dev/null +++ b/briar-android/fastlane/Screengrabfile @@ -0,0 +1,9 @@ +app_package_name "org.briarproject.briar.android.screenshot.debug" +locales ['en-US'] +use_tests_in_classes([ + 'org.briarproject.briar.android.login.SetupActivityScreenshotTest', + 'org.briarproject.briar.android.settings.SettingsActivityScreenshotTest', +]) +app_apk_path "build/outputs/apk/screenshot/debug/briar-android-screenshot-debug.apk" +tests_apk_path "build/outputs/apk/androidTest/screenshot/debug/briar-android-screenshot-debug-androidTest.apk" +test_instrumentation_runner "org.briarproject.briar.android.test.BriarTestRunner" \ No newline at end of file diff --git a/briar-android/fastlane/demo-mode-activate.sh b/briar-android/fastlane/demo-mode-activate.sh new file mode 100755 index 000000000..23001fb56 --- /dev/null +++ b/briar-android/fastlane/demo-mode-activate.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +adb shell settings put global sysui_demo_allowed 1 +adb shell am broadcast -a com.android.systemui.demo -e command enter +adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false +adb shell am broadcast -a com.android.systemui.demo -e command battery -e level 100 +adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show +adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1337 \ No newline at end of file diff --git a/briar-android/fastlane/demo-mode-deactivate.sh b/briar-android/fastlane/demo-mode-deactivate.sh new file mode 100755 index 000000000..c70cd5326 --- /dev/null +++ b/briar-android/fastlane/demo-mode-deactivate.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +adb shell am broadcast -a com.android.systemui.demo -e command exit \ No newline at end of file diff --git a/briar-android/fastlane/rename_screenshots.py b/briar-android/fastlane/rename_screenshots.py new file mode 100755 index 000000000..f1f0485fa --- /dev/null +++ b/briar-android/fastlane/rename_screenshots.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +# Author: Torsten Grote +# License: GPLv3 or later + +import os +import re +import glob + +METADATA_PATH = 'metadata/android' +GLOB = '/*/images/phoneScreenshots/*.png' + +REGEX = re.compile(r'(^\w+)_\d{13}\.png$') +REGEX_IN_FILE = re.compile(r'(\w+)_\d{13}\.png', re.MULTILINE) +PATH = os.path.dirname(os.path.realpath(__file__)) + + +def main(): + for path in glob.glob("%s%s" % (os.path.join(PATH, METADATA_PATH), GLOB)): + filename = os.path.basename(path) + match = REGEX.match(filename) + if match: + directory = os.path.dirname(path) + new_filename = "%s.png" % match.group(1) + new_path = os.path.join(directory, new_filename) + os.rename(path, new_path) + print("Renaming\n %s\nto\n %s\n" % (path, new_path)) + else: + print("Warning: Path did not match %s" % path) + + # rename fields also in screenshot overview file + overview = os.path.join(PATH, METADATA_PATH, 'screenshots.html') + with open(overview, 'r') as f: + file_data = f.read() + + file_data = REGEX_IN_FILE.sub(r'\1.png', file_data) + + with open(overview, 'w') as f: + f.write(file_data) + + +if __name__ == "__main__": + main() diff --git a/briar-android/proguard-test.txt b/briar-android/proguard-test.txt new file mode 100644 index 000000000..df4eb272e --- /dev/null +++ b/briar-android/proguard-test.txt @@ -0,0 +1,15 @@ +-dontwarn android.test.** +-dontwarn android.support.test.** +-dontnote android.support.test.** +-dontwarn com.googlecode.eyesfree.compat.CompatUtils + +-keep class org.xmlpull.v1.** { *; } +-dontwarn org.xmlpull.v1.** + +-keep class org.junit.** { *; } +-dontwarn org.junit.** + +-keep class junit.** { *; } +-dontwarn junit.** + +-dontwarn org.briarproject.briar.android.BriarTestComponentApplication \ No newline at end of file diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java new file mode 100644 index 000000000..156620b1d --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java @@ -0,0 +1,20 @@ +package org.briarproject.briar.android; + +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.briar.BriarCoreModule; + +public class BriarTestComponentApplication extends BriarApplicationImpl { + + @Override + protected AndroidComponent createApplicationComponent() { + AndroidComponent component = DaggerBriarUiTestComponent.builder() + .appModule(new AppModule(this)).build(); + // We need to load the eager singletons directly after making the + // dependency graphs + BrambleCoreModule.initEagerSingletons(component); + BriarCoreModule.initEagerSingletons(component); + AndroidEagerSingletons.initEagerSingletons(component); + return component; + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarUiTestComponent.java new file mode 100644 index 000000000..af1cba5f0 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -0,0 +1,31 @@ +package org.briarproject.briar.android; + +import org.briarproject.bramble.BrambleAndroidModule; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.account.BriarAccountModule; +import org.briarproject.briar.BriarCoreModule; +import org.briarproject.briar.android.login.PasswordActivityTest; +import org.briarproject.briar.android.login.SetupActivityScreenshotTest; +import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; +import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + AppModule.class, + BriarCoreModule.class, + BrambleAndroidModule.class, + BriarAccountModule.class, + BrambleCoreModule.class +}) +public interface BriarUiTestComponent extends AndroidComponent { + + void inject(SetupActivityScreenshotTest test); + void inject(PasswordActivityTest test); + void inject(NavDrawerActivityTest test); + void inject(SettingsActivityScreenshotTest test); + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/login/SetupActivityScreenshotTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/login/SetupActivityScreenshotTest.java new file mode 100644 index 000000000..475ae87a1 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/login/SetupActivityScreenshotTest.java @@ -0,0 +1,106 @@ +package org.briarproject.briar.android.login; + +import android.support.test.espresso.intent.rule.IntentsTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject; +import android.support.test.uiautomator.UiSelector; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.test.ScreenshotTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static android.support.test.InstrumentationRegistry.getTargetContext; +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.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.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static android.support.test.runner.lifecycle.Stage.PAUSED; +import static junit.framework.Assert.assertTrue; +import static org.briarproject.briar.android.test.ViewActions.waitForActivity; +import static org.briarproject.briar.android.test.ViewActions.waitUntilMatches; +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; + + +@RunWith(AndroidJUnit4.class) +public class SetupActivityScreenshotTest extends ScreenshotTest { + + @Rule + public IntentsTestRule testRule = + new IntentsTestRule(SetupActivity.class) { + @Override + protected void beforeActivityLaunched() { + super.beforeActivityLaunched(); + accountManager.deleteAccount(); + } + }; + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void createAccount() throws Exception { + // Enter username + onView(withText(R.string.setup_title)) + .check(matches(isDisplayed())); + onView(withId(R.id.nickname_entry)) + .check(matches(isDisplayed())) + .perform(typeText(USERNAME)); + onView(withId(R.id.nickname_entry)) + .perform(waitUntilMatches(withText(USERNAME))); + + screenshot("manual_create_account"); + + onView(withId(R.id.next)) + .check(matches(isDisplayed())) + .perform(click()); + + // Enter password + onView(withId(R.id.password_entry)) + .check(matches(isDisplayed())) + .perform(typeText(PASSWORD)); + onView(withId(R.id.password_confirm)) + .check(matches(isDisplayed())) + .perform(typeText(PASSWORD)); + onView(withId(R.id.next)) + .check(matches(isDisplayed())) + .perform(click()); + + // White-list Doze if needed + if (needsDozeWhitelisting(getTargetContext())) { + onView(withText(R.string.setup_doze_button)) + .check(matches(isDisplayed())) + .perform(click()); + UiDevice device = UiDevice.getInstance(getInstrumentation()); + UiObject allowButton = device.findObject( + new UiSelector().className("android.widget.Button") + .index(1)); + allowButton.click(); + onView(withId(R.id.next)) + .check(matches(isDisplayed())) + .perform(click()); + } + + // wait for OpenDatabaseActivity to show up + onView(withId(R.id.progress)) + .check(matches(isDisplayed())); + onView(isRoot()) + .perform(waitForActivity(testRule.getActivity(), PAUSED)); + intended(hasComponent(OpenDatabaseActivity.class.getName())); + + assertTrue(accountManager.hasDatabaseKey()); + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/navdrawer/NavDrawerActivityTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/navdrawer/NavDrawerActivityTest.java new file mode 100644 index 000000000..e92ae6693 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/navdrawer/NavDrawerActivityTest.java @@ -0,0 +1,48 @@ +package org.briarproject.briar.android.navdrawer; + +import android.support.test.espresso.contrib.DrawerActions; +import android.support.test.runner.AndroidJUnit4; +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.settings.SettingsActivity; +import org.briarproject.briar.android.test.ScreenshotTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.DrawerMatchers.isClosed; +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.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +@RunWith(AndroidJUnit4.class) +public class NavDrawerActivityTest extends ScreenshotTest { + + @Rule + public CleanAccountTestRule testRule = + new CleanAccountTestRule<>(NavDrawerActivity.class); + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void openSettings() { + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))) + .perform(DrawerActions.open()); + onView(withText(R.string.settings_button)) + .check(matches(isDisplayed())) + .perform(click()); + intended(hasComponent(SettingsActivity.class.getName())); + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java new file mode 100644 index 000000000..192680bd7 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java @@ -0,0 +1,70 @@ +package org.briarproject.briar.android.settings; + +import android.content.Intent; +import android.support.test.espresso.contrib.DrawerActions; +import android.support.test.runner.AndroidJUnit4; +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.test.ScreenshotTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.contrib.DrawerMatchers.isClosed; +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; + +@RunWith(AndroidJUnit4.class) +public class SettingsActivityScreenshotTest extends ScreenshotTest { + + @Rule + public CleanAccountTestRule testRule = + new CleanAccountTestRule<>(SettingsActivity.class); + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void changeTheme() { + onView(withText(R.string.settings_button)) + .check(matches(isDisplayed())); + + screenshot("manual_dark_theme_settings"); + + // switch to dark theme + onView(withText(R.string.pref_theme_title)) + .check(matches(isDisplayed())) + .perform(click()); + onView(withText(R.string.pref_theme_dark)) + .check(matches(isDisplayed())) + .perform(click()); + + // start main activity + Intent i = + new Intent(testRule.getActivity(), NavDrawerActivity.class); + testRule.getActivity().startActivity(i); + + // close expiry warning + onView(withId(R.id.expiryWarningClose)) + .check(matches(isDisplayed())); + onView(withId(R.id.expiryWarningClose)) + .perform(click()); + + // open navigation drawer + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))) + .perform(DrawerActions.open()); + + screenshot("manual_dark_theme_nav_drawer"); + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/test/BriarTestRunner.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/BriarTestRunner.java new file mode 100644 index 000000000..bf520102d --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/BriarTestRunner.java @@ -0,0 +1,20 @@ +package org.briarproject.briar.android.test; + +import android.app.Application; +import android.content.Context; +import android.support.test.runner.AndroidJUnitRunner; + +import org.briarproject.briar.android.BriarTestComponentApplication; + +public class BriarTestRunner extends AndroidJUnitRunner { + + @Override + public Application newApplication(ClassLoader cl, String className, + Context context) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + return super.newApplication(cl, BriarTestComponentApplication.class.getName(), + context); + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ScreenshotTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ScreenshotTest.java new file mode 100644 index 000000000..355dd7763 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ScreenshotTest.java @@ -0,0 +1,73 @@ +package org.briarproject.briar.android.test; + +import android.app.Activity; +import android.support.test.espresso.intent.rule.IntentsTestRule; +import android.util.Log; + +import org.briarproject.bramble.api.account.AccountManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.briar.android.BriarTestComponentApplication; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.junit.ClassRule; + +import javax.inject.Inject; + +import tools.fastlane.screengrab.Screengrab; +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy; +import tools.fastlane.screengrab.locale.LocaleTestRule; + +import static android.support.test.InstrumentationRegistry.getTargetContext; +import static tools.fastlane.screengrab.Screengrab.setDefaultScreenshotStrategy; + +public abstract class ScreenshotTest { + + @ClassRule + public static final LocaleTestRule localeTestRule = new LocaleTestRule(); + + protected static final String USERNAME = "Alice"; + protected static final String PASSWORD = "123456"; + + @Inject + protected AccountManager accountManager; + @Inject + protected LifecycleManager lifecycleManager; + + public ScreenshotTest() { + super(); + setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy()); + BriarTestComponentApplication app = + (BriarTestComponentApplication) getTargetContext() + .getApplicationContext(); + inject((BriarUiTestComponent) app.getApplicationComponent()); + } + + protected abstract void inject(BriarUiTestComponent component); + + protected void screenshot(String name) { + try { + Screengrab.screenshot(name); + } catch (RuntimeException e) { + if (!e.getMessage().equals("Unable to capture screenshot.")) + throw e; + // The tests should still pass when run from AndroidStudio + // without manually granting permissions like fastlane does. + Log.w("Screengrab", "Permission to write screenshot is missing."); + } + } + + protected class CleanAccountTestRule + extends IntentsTestRule { + + public CleanAccountTestRule(Class activityClass) { + super(activityClass); + } + + @Override + protected void beforeActivityLaunched() { + super.beforeActivityLaunched(); + accountManager.deleteAccount(); + accountManager.createAccount(USERNAME, PASSWORD); + } + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ViewActions.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ViewActions.java new file mode 100644 index 000000000..0d1aceba5 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/test/ViewActions.java @@ -0,0 +1,93 @@ +package org.briarproject.briar.android.test; + +import android.app.Activity; +import android.support.test.espresso.PerformException; +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.runner.lifecycle.ActivityLifecycleMonitor; +import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; +import android.support.test.runner.lifecycle.Stage; +import android.view.View; + +import org.hamcrest.Matcher; + +import java.util.concurrent.TimeoutException; + +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.util.HumanReadables.describe; +import static android.support.test.espresso.util.TreeIterables.breadthFirstViewTraversal; +import static java.lang.System.currentTimeMillis; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class ViewActions { + + private final static long TIMEOUT_MS = SECONDS.toMillis(10); + private final static long WAIT_MS = 50; + + public static ViewAction waitUntilMatches(Matcher viewMatcher) { + return waitUntilMatches(viewMatcher, TIMEOUT_MS); + } + + private static ViewAction waitUntilMatches(Matcher viewMatcher, + long timeout) { + return new CustomViewAction() { + @Override + protected boolean exitConditionTrue(View view) { + for (View child : breadthFirstViewTraversal(view)) { + if (viewMatcher.matches(child)) return true; + } + return false; + } + + @Override + public String getDescription() { + return "Wait for view matcher " + viewMatcher + + " to match within " + timeout + " milliseconds."; + } + }; + } + + public static ViewAction waitForActivity(Activity activity, Stage stage) { + return new CustomViewAction() { + @Override + protected boolean exitConditionTrue(View view) { + ActivityLifecycleMonitor lifecycleMonitor = + ActivityLifecycleMonitorRegistry.getInstance(); + return lifecycleMonitor.getLifecycleStageOf(activity) == stage; + } + + @Override + public String getDescription() { + return "Wait for activity " + activity.getClass().getName() + + " to resume within " + TIMEOUT_MS + " milliseconds."; + } + }; + } + + private static abstract class CustomViewAction implements ViewAction { + @Override + public Matcher getConstraints() { + return isDisplayed(); + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + long endTime = currentTimeMillis() + TIMEOUT_MS; + do { + if (exitConditionTrue(view)) return; + uiController.loopMainThreadForAtLeast(WAIT_MS); + } + while (currentTimeMillis() < endTime); + + throw new PerformException.Builder() + .withActionDescription(getDescription()) + .withViewDescription(describe(view)) + .withCause(new TimeoutException()) + .build(); + } + + protected abstract boolean exitConditionTrue(View view); + } + +} diff --git a/briar-android/src/debug/res/values/strings.xml b/briar-android/src/debug/res/values/strings.xml new file mode 100644 index 000000000..5cbb56beb --- /dev/null +++ b/briar-android/src/debug/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Briar Debug + org.briarproject.briar.android.debug + diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java index 08fc14b5d..e56adf30e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java @@ -109,15 +109,20 @@ public class BriarApplicationImpl extends Application LOG.info("Created"); - applicationComponent = DaggerAndroidComponent.builder() + applicationComponent = createApplicationComponent(); + } + + protected AndroidComponent createApplicationComponent() { + AndroidComponent androidComponent = DaggerAndroidComponent.builder() .appModule(new AppModule(this)) .build(); // We need to load the eager singletons directly after making the // dependency graphs - BrambleCoreModule.initEagerSingletons(applicationComponent); - BriarCoreModule.initEagerSingletons(applicationComponent); - AndroidEagerSingletons.initEagerSingletons(applicationComponent); + BrambleCoreModule.initEagerSingletons(androidComponent); + BriarCoreModule.initEagerSingletons(androidComponent); + AndroidEagerSingletons.initEagerSingletons(androidComponent); + return androidComponent; } @Override diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 086ff8c3f..8456ceda1 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -1,6 +1,9 @@ + Briar + org.briarproject.briar.android + Welcome to Briar Your nickname will be shown next to any content you post. You can\'t change it after creating your account. diff --git a/briar-android/src/screenshotDebug/AndroidManifest.xml b/briar-android/src/screenshotDebug/AndroidManifest.xml new file mode 100644 index 000000000..a35a0a5e5 --- /dev/null +++ b/briar-android/src/screenshotDebug/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/briar-android/src/screenshotDebug/res/values/strings.xml b/briar-android/src/screenshotDebug/res/values/strings.xml new file mode 100644 index 000000000..6a7574e56 --- /dev/null +++ b/briar-android/src/screenshotDebug/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Briar + org.briarproject.briar.android.screenshot.debug +