diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76ecc8a15..9fd196c5a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,12 +34,16 @@ android test: # start emulator first, so it can fail early - start-emulator.sh # run normal and screenshot tests together (exclude Large tests) - - ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest + - ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.package=org.briarproject.briar.android -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest + after_script: + - adb pull /sdcard/Pictures/screenshots artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - kernel.log - logcat.txt + - briar-android/build/reports/androidTests/connected/flavors/* + - screenshots expire_in: 3 days when: on_failure when: manual diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 3c495c15d..050186276 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -80,6 +80,7 @@ android { } testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' unitTests { includeAndroidResources = true } @@ -148,6 +149,8 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestUtil 'androidx.test:orchestrator:1.3.0' androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24" androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestImplementation 'junit:junit:4.13.1' 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 index 239cd05ee..c60e61cb7 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java @@ -19,4 +19,9 @@ public class BriarTestComponentApplication extends BriarApplicationImpl { return component; } + @Override + public boolean isInstrumentationTest() { + return true; + } + } diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java new file mode 100644 index 000000000..87b8b7833 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java @@ -0,0 +1,65 @@ +package org.briarproject.briar.android; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.IOException; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicBoolean; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.FailureHandler; +import androidx.test.espresso.base.DefaultFailureHandler; +import androidx.test.runner.screenshot.BasicScreenCaptureProcessor; +import androidx.test.runner.screenshot.ScreenCapture; +import androidx.test.runner.screenshot.ScreenCaptureProcessor; +import androidx.test.runner.screenshot.Screenshot; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +@NotNullByDefault +public class ScreenshotOnFailureRule implements TestRule { + + FailureHandler defaultFailureHandler = + new DefaultFailureHandler(getApplicationContext()); + + @Override + public Statement apply(Statement base, Description description) { + HashSet processors = new HashSet<>(1); + processors.add(new BasicScreenCaptureProcessor()); + Screenshot.addScreenCaptureProcessors(processors); + return new Statement() { + @Override + public void evaluate() throws Throwable { + AtomicBoolean errorHandled = new AtomicBoolean(false); + Espresso.setFailureHandler((throwable, matcher) -> { + takeScreenshot(description); + errorHandled.set(true); + defaultFailureHandler.handle(throwable, matcher); + }); + try { + base.evaluate(); + } catch (Throwable t) { + if (!errorHandled.get()) { + takeScreenshot(description); + } + throw t; + } + } + }; + } + + private void takeScreenshot(Description description) { + String name = description.getTestClass().getSimpleName(); + ScreenCapture capture = Screenshot.capture(); + capture.setName(name); + try { + capture.process(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java index af75f3bbf..5c78c3043 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java @@ -4,25 +4,27 @@ import android.app.Activity; import android.content.Intent; import org.briarproject.bramble.api.account.AccountManager; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.R; +import org.junit.ClassRule; import javax.inject.Inject; import androidx.test.espresso.intent.rule.IntentsTestRule; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; -import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; @SuppressWarnings("WeakerAccess") public abstract class UiTest { + @ClassRule + public static final ScreenshotOnFailureRule screenshotOnFailureRule = + new ScreenshotOnFailureRule(); + protected final String USERNAME = getApplicationContext().getString(R.string.screenshot_alice); protected static final String PASSWORD = "123456"; @@ -41,6 +43,12 @@ public abstract class UiTest { protected abstract void inject(BriarUiTestComponent component); + protected void startActivity(Class clazz) { + Intent i = new Intent(getApplicationContext(), clazz); + i.addFlags(FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(i); + } + @NotNullByDefault protected class CleanAccountTestRule extends IntentsTestRule { @@ -59,11 +67,7 @@ public abstract class UiTest { getApplicationContext().startService(serviceIntent); try { lifecycleManager.waitForStartup(); - // do not show doze white-listing dialog - Settings settings = new Settings(); - settings.putBoolean(DOZE_ASK_AGAIN, false); - settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); - } catch (InterruptedException | DbException e) { + } catch (InterruptedException e) { throw new AssertionError(e); } } diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java index dc71aded2..50c2dd8be 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android; import android.app.Activity; +import android.util.Log; import android.view.View; import org.hamcrest.Matcher; @@ -14,9 +15,13 @@ import androidx.test.runner.lifecycle.ActivityLifecycleMonitor; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.util.HumanReadables.describe; import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal; +import static androidx.test.runner.lifecycle.Stage.RESUMED; import static java.lang.System.currentTimeMillis; import static java.util.concurrent.TimeUnit.SECONDS; @@ -25,13 +30,26 @@ public class ViewActions { private final static long TIMEOUT_MS = SECONDS.toMillis(10); private final static long WAIT_MS = 50; + public static void waitFor(final Matcher viewMatcher) { + onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher))); + } + + public static void waitFor(final Class clazz) { + onView(isRoot()).perform(waitForActivity(clazz, RESUMED, TIMEOUT_MS)); + } + + public static void waitFor(final Class clazz, + long timeout) { + onView(isRoot()).perform(waitForActivity(clazz, RESUMED, timeout)); + } + public static ViewAction waitUntilMatches(Matcher viewMatcher) { return waitUntilMatches(viewMatcher, TIMEOUT_MS); } private static ViewAction waitUntilMatches(Matcher viewMatcher, long timeout) { - return new CustomViewAction() { + return new CustomViewAction(timeout) { @Override protected boolean exitConditionTrue(View view) { for (View child : breadthFirstViewTraversal(view)) { @@ -48,24 +66,62 @@ public class ViewActions { }; } - public static ViewAction waitForActivity(Activity activity, Stage stage) { - return new CustomViewAction() { + public static ViewAction waitForActivity(Class clazz, + Stage stage, long timeout) { + return new CustomViewAction(timeout) { @Override protected boolean exitConditionTrue(View view) { + boolean found = false; ActivityLifecycleMonitor lifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance(); - return lifecycleMonitor.getLifecycleStageOf(activity) == stage; + log(lifecycleMonitor); + for (Activity a : lifecycleMonitor + .getActivitiesInStage(stage)) { + if (a.getClass().equals(clazz)) found = true; + } + return found; + } + + private void log(ActivityLifecycleMonitor lifecycleMonitor) { + log(lifecycleMonitor, Stage.PRE_ON_CREATE); + log(lifecycleMonitor, Stage.CREATED); + log(lifecycleMonitor, Stage.STARTED); + log(lifecycleMonitor, Stage.RESUMED); + log(lifecycleMonitor, Stage.PAUSED); + log(lifecycleMonitor, Stage.STOPPED); + log(lifecycleMonitor, Stage.RESTARTED); + log(lifecycleMonitor, Stage.DESTROYED); + } + + private void log(ActivityLifecycleMonitor lifecycleMonitor, + Stage stage) { + for (Activity a : lifecycleMonitor + .getActivitiesInStage(stage)) { + Log.e("TEST", a.getClass().getSimpleName() + + " is in state " + stage); + } } @Override public String getDescription() { - return "Wait for activity " + activity.getClass().getName() + - " to resume within " + TIMEOUT_MS + " milliseconds."; + return "Wait for activity " + clazz.getName() + " in stage " + + stage.name() + " within " + timeout + + " milliseconds."; } }; } private static abstract class CustomViewAction implements ViewAction { + private final long timeout; + + public CustomViewAction() { + this(TIMEOUT_MS); + } + + public CustomViewAction(long timeout) { + this.timeout = timeout; + } + @Override public Matcher getConstraints() { return isDisplayed(); @@ -74,7 +130,7 @@ public class ViewActions { @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); - long endTime = currentTimeMillis() + TIMEOUT_MS; + long endTime = currentTimeMillis() + timeout; do { if (exitConditionTrue(view)) return; uiController.loopMainThreadForAtLeast(WAIT_MS); diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java index e8c81393f..f4b7a6dee 100644 --- a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -4,6 +4,8 @@ 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.account.SignInTestCreateAccount; +import org.briarproject.briar.android.account.SignInTestSignIn; import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; @@ -26,4 +28,8 @@ public interface BriarUiTestComponent extends AndroidComponent { void inject(NavDrawerActivityTest test); + void inject(SignInTestCreateAccount test); + + void inject(SignInTestSignIn test); + } diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java new file mode 100644 index 000000000..a66583428 --- /dev/null +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java @@ -0,0 +1,65 @@ +package org.briarproject.briar.android.account; + +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.UiTest; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.splash.SplashScreenActivity; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.espresso.contrib.DrawerActions; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.DrawerMatchers.isClosed; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withClassName; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.briarproject.briar.android.ViewActions.waitUntilMatches; +import static org.hamcrest.Matchers.endsWith; + +@RunWith(AndroidJUnit4.class) +public class SignInTestCreateAccount extends UiTest { + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void createAccount() throws Exception { + accountManager.deleteAccount(); + accountManager.createAccount(USERNAME, PASSWORD); + + startActivity(SplashScreenActivity.class); + lifecycleManager.waitForStartup(); + waitFor(NavDrawerActivity.class); + + // open nav drawer + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))) + .perform(DrawerActions.open()); + + // click onboarding away (once shown) + onView(isRoot()).perform(waitUntilMatches(hasDescendant( + withClassName(endsWith("PromptView"))))); + onView(withClassName(endsWith("PromptView"))) + .perform(click()); + + // sign-out manually + onView(withText(R.string.sign_out_button)) + .check(matches(isDisplayed())) + .perform(click()); + lifecycleManager.waitForShutdown(); + } + +} diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java new file mode 100644 index 000000000..30bb9aaad --- /dev/null +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java @@ -0,0 +1,59 @@ +package org.briarproject.briar.android.account; + +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.UiTest; +import org.briarproject.briar.android.login.StartupActivity; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.splash.SplashScreenActivity; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.DrawerMatchers.isClosed; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.hamcrest.CoreMatchers.allOf; + +/** + * This relies on class sorting to run after {@link SignInTestCreateAccount}. + */ +@RunWith(AndroidJUnit4.class) +public class SignInTestSignIn extends UiTest { + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void signIn() throws Exception { + startActivity(SplashScreenActivity.class); + + waitFor(StartupActivity.class); + + // enter password + onView(withId(R.id.edit_password)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + onView(withId(R.id.btn_sign_in)) + .check(matches(allOf(isDisplayed(), isEnabled()))) + .perform(click()); + + lifecycleManager.waitForStartup(); + waitFor(NavDrawerActivity.class); + + // ensure nav drawer is visible + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))); + } +} diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java index 9670dd8bc..af6d2ce02 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -31,4 +31,6 @@ public interface BriarUiTestComponent extends AndroidComponent { void inject(SettingsActivityScreenshotTest test); + void inject(PromoVideoTest test); + } diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java new file mode 100644 index 000000000..424150f92 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java @@ -0,0 +1,41 @@ +package org.briarproject.briar.android; + +import android.view.View; + +import org.hamcrest.Matcher; + +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; + +import static androidx.test.espresso.action.GeneralLocation.VISIBLE_CENTER; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; + +public class OverlayTapViewAction implements ViewAction { + + public static ViewAction visualClick(OverlayView overlayView) { + return new OverlayTapViewAction(overlayView); + } + + private final OverlayView overlayView; + + public OverlayTapViewAction(OverlayView overlayView) { + this.overlayView = overlayView; + } + + @Override + public Matcher getConstraints() { + return isDisplayingAtLeast(90); + } + + @Override + public String getDescription() { + return null; + } + + @Override + public void perform(UiController uiController, View view) { + float[] coordinates = VISIBLE_CENTER.calculateCoordinates(view); + overlayView.tap(coordinates); + } +} + diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java new file mode 100644 index 000000000..776cdd6b0 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java @@ -0,0 +1,86 @@ +package org.briarproject.briar.android; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.view.View; +import android.view.WindowManager; + +import java.util.Random; + +import javax.annotation.Nullable; + +import static android.content.Context.WINDOW_SERVICE; +import static android.provider.Settings.canDrawOverlays; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; +import static org.junit.Assert.assertTrue; + +/** + * A full-screen overlay used to make taps visible in instrumentation tests. + */ +public class OverlayView extends View { + + public static OverlayView attach(Context ctx) throws Throwable { + assertTrue(canDrawOverlays(ctx)); + OverlayView view = new OverlayView(getApplicationContext()); + runOnUiThread(() -> attachInternal(ctx, view)); + return view; + } + + private static void attachInternal(Context ctx, OverlayView view) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + WindowManager wm = (WindowManager) ctx.getSystemService(WINDOW_SERVICE); + wm.addView(view, params); + } + + private final Random random = new Random(); + private final Paint paint; + private final int yOffset; + @Nullable + private float[] coordinates; + + public OverlayView(Context ctx) { + super(ctx); + int resourceId = getResources() + .getIdentifier("status_bar_height", "dimen", "android"); + yOffset = getResources().getDimensionPixelSize(resourceId); + paint = new Paint(); + paint.setAntiAlias(true); + paint.setARGB(175, 255, 0, 0); + setWillNotDraw(false); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + } + + void tap(float[] coordinates) { + this.coordinates = coordinates; + invalidate(); + new Handler().postDelayed(this::untap, 750); + } + + private void untap() { + this.coordinates = null; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (coordinates == null) return; + float x = coordinates[0] + random.nextInt(42); + float y = coordinates[1] - yOffset + random.nextInt(13); + canvas.drawCircle(x, y, 42, paint); + } +} diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java new file mode 100644 index 000000000..7e9d6f430 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java @@ -0,0 +1,285 @@ +package org.briarproject.briar.android; + +import android.view.View; + +import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactState; +import org.briarproject.briar.R; +import org.briarproject.briar.android.account.SetupActivity; +import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.splash.SplashScreenActivity; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.inject.Inject; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiSelector; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static java.lang.Thread.sleep; +import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; +import static org.briarproject.briar.android.OverlayTapViewAction.visualClick; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.briarproject.briar.android.ViewActions.waitUntilMatches; +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; +import static org.hamcrest.CoreMatchers.allOf; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class PromoVideoTest extends ScreenshotTest { + + // we can leave isFilming to false (to speed up CI) + // and only set it to true when doing recordings + private static final boolean isFilming = false; + + private static final int DELAY_SMALL = isFilming ? 4_000 : 0; + private static final int DELAY_MEDIUM = isFilming ? 7_500 : 0; + private static final int DELAY_LONG = isFilming ? 10_000 : 0; + + @Rule + public ActivityScenarioRule testRule = + new ActivityScenarioRule<>(SplashScreenActivity.class); + + @Inject + protected ContactManager contactManager; + + private OverlayView overlayView; + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + accountManager.deleteAccount(); + } + + @Test + public void createAccountAddContact() throws Throwable { + if (isFilming) { + // Using this breaks emulator CI tests for some reason. + // Only use it for filming for now until we have time to debug this. + overlayView = OverlayView.attach(getApplicationContext()); + } + + // Splash screen shows logo + onView(withId(R.id.logoView)) + .perform(waitUntilMatches(isDisplayed())); + + // It takes a long time for SetupActivity to start after the splash, + // (because it is shown longer for videos), so increase timeout. + if (!isFilming) waitFor(SetupActivity.class, 20_000); + + // Note: We use waiting code only when not filming, + // to make the test reliable for CI. Otherwise, we used fixed + // delays to deterministically align with subtitles. + + sleep(DELAY_LONG); + + // Enter username + onView(withText(R.string.setup_title)) + .perform(waitUntilMatches(isDisplayed())); + sleep(DELAY_SMALL); + onView(withId(R.id.nickname_entry)) + .check(matches(isDisplayed())) + .perform(replaceText(USERNAME)); + closeKeyboard(withId(R.id.nickname_entry)); + + sleep(DELAY_SMALL); + + doClick(withId(R.id.next)); + + sleep(DELAY_MEDIUM); + + // Enter password + doClick(withId(R.id.password_entry), 1000); + onView(withId(R.id.password_entry)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + sleep(DELAY_SMALL); + doClick(withId(R.id.password_confirm), 1000); + onView(withId(R.id.password_confirm)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + + sleep(DELAY_SMALL); + // click next or create account + doClick(withId(R.id.next)); + + sleep(DELAY_SMALL); + + // White-list Doze if needed + if (needsDozeWhitelisting(getApplicationContext())) { + doClick(withText(R.string.setup_doze_button)); + UiDevice device = UiDevice.getInstance(getInstrumentation()); + UiObject allowButton = device.findObject( + new UiSelector().className("android.widget.Button") + .index(1)); + allowButton.click(); + doClick(withId(R.id.next)); + } + + lifecycleManager.waitForStartup(); + assertTrue(accountManager.hasDatabaseKey()); + + sleep(DELAY_SMALL); + + // wait for contact list to be shown + if (!isFilming) waitFor(NavDrawerActivity.class); + + // clicking the FAB doesn't work, so we click its inner FAB as well + onView(withId(R.id.speedDial)) + .check(matches(isDisplayed())) + .perform(click()); + doClick(withId(R.id.fab_main)); // this is inside R.id.speedDial + sleep(DELAY_MEDIUM); + + // click adding contact at a distance menu item + doClick(withText(R.string.add_contact_remotely_title)); + sleep(DELAY_LONG); + + // enter briar:// link + String link = + "briar://ab54fpik6sjyetzjhlwto2fv7tspibx2uhpdnei4tdidkvjpbphvy"; + doClick(withId(R.id.pasteButton)); + onView(withId(R.id.linkInput)) + .perform(waitUntilMatches(isDisplayed())) + .perform(replaceText(link)); + sleep(DELAY_MEDIUM); + + doClick(withId(R.id.addButton)); + sleep(DELAY_MEDIUM); + + // enter contact alias + String contactName = getApplicationContext() + .getString(R.string.screenshot_bob); + doClick(withId(R.id.contactNameInput), 1000); + onView(withId(R.id.contactNameInput)) + .perform(waitUntilMatches(isDisplayed())) + .perform(replaceText(contactName)); + sleep(DELAY_SMALL); + closeKeyboard(withId(R.id.contactNameInput)); + sleep(DELAY_SMALL); + + // add pending contact + onView(withId(R.id.addButton)).perform(scrollTo()); + doClick(withId(R.id.addButton)); + sleep(DELAY_LONG); + + // wait for pending contact list activity to be shown + if (!isFilming) { + waitFor(PendingContactListActivity.class); + waitFor(allOf(withText(R.string.pending_contact_requests), + isDisplayed())); + } + + // remove pending contact + for (Pair p : contactManager + .getPendingContacts()) { + contactManager.removePendingContact(p.getFirst().getId()); + } + // add contact and make them appear online + Contact bob = testDataCreator.addContact(contactName, false, true); + sleep(DELAY_SMALL); + connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> { + }); + + sleep(DELAY_LONG); + + // wait for contact list to be shown + if (!isFilming) { + waitFor(NavDrawerActivity.class); + waitFor(allOf(withText(R.string.contact_list_button), + isDisplayed())); + waitFor(allOf(withId(R.id.recyclerView), isDisplayed())); + } + + // click on new contact + doItemClick(withId(R.id.recyclerView), 0); + + sleep(DELAY_MEDIUM); + + // bring up keyboard + doClick(withId(R.id.input_text), DELAY_SMALL); + + String msg1 = getApplicationContext() + .getString(R.string.screenshot_message_1); + onView(withId(R.id.input_text)) + .perform(waitUntilMatches(isEnabled())) + .perform(replaceText(msg1)); + + sleep(DELAY_SMALL); + + doClick(withId(R.id.compositeSendButton)); + + sleep(DELAY_SMALL); + + // send emoji + doClick(withId(R.id.emoji_toggle), DELAY_SMALL); + onView(withId(R.id.input_text)) + .perform(replaceText("\uD83D\uDE0E")); + sleep(DELAY_SMALL); + doClick(withId(R.id.compositeSendButton)); + + // close keyboard + closeKeyboard(withId(R.id.compositeSendButton)); + + sleep(DELAY_LONG); + } + + private void doClick(final Matcher viewMatcher, long sleepMs) + throws InterruptedException { + doClick(viewMatcher); + if (isFilming) sleep(sleepMs); + } + + private void doClick(final Matcher viewMatcher) + throws InterruptedException { + if (isFilming) { + onView(viewMatcher) + .perform(waitUntilMatches(isDisplayed())) + .perform(visualClick(overlayView)); + sleep(500); + } + onView(viewMatcher) + .perform(waitUntilMatches(allOf(isDisplayed(), isEnabled()))) + .perform(click()); + } + + private void doItemClick(final Matcher viewMatcher, int pos) + throws InterruptedException { + if (isFilming) { + onView(viewMatcher).perform( + actionOnItemAtPosition(pos, visualClick(overlayView))); + sleep(500); + } + onView(viewMatcher).perform( + actionOnItemAtPosition(pos, click())); + } + + private void closeKeyboard(final Matcher viewMatcher) + throws InterruptedException { + if (isFilming) sleep(750); + onView(viewMatcher).perform(closeSoftKeyboard()); + } + +} diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java index 84fef39b2..61c0a5bbd 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java @@ -10,6 +10,7 @@ import org.junit.ClassRule; import javax.inject.Inject; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import tools.fastlane.screengrab.FalconScreenshotStrategy; import tools.fastlane.screengrab.Screengrab; import tools.fastlane.screengrab.locale.LocaleTestRule; @@ -26,6 +27,10 @@ public abstract class ScreenshotTest extends UiTest { @Inject protected Clock clock; + protected void screenshot(String name, ActivityScenarioRule rule) { + rule.getScenario().onActivity(activity -> screenshot(name, activity)); + } + protected void screenshot(String name, Activity activity) { try { Screengrab.screenshot(name, new FalconScreenshotStrategy(activity)); diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java index f40c70aef..b0d3f6a4d 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java @@ -10,7 +10,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject; @@ -22,30 +22,27 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.typeText; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.briar.android.ViewActions.waitUntilMatches; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; +import static org.hamcrest.Matchers.allOf; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class SetupDataTest extends ScreenshotTest { @Rule - public IntentsTestRule testRule = - new IntentsTestRule(SetupActivity.class) { - @Override - protected void beforeActivityLaunched() { - super.beforeActivityLaunched(); - accountManager.deleteAccount(); - } - }; + public ActivityScenarioRule testRule = + new ActivityScenarioRule<>(SetupActivity.class); @Override protected void inject(BriarUiTestComponent component) { component.inject(this); + accountManager.deleteAccount(); } @Test @@ -59,7 +56,7 @@ public class SetupDataTest extends ScreenshotTest { onView(withId(R.id.nickname_entry)) .perform(waitUntilMatches(withText(USERNAME))); - screenshot("manual_create_account", testRule.getActivity()); + screenshot("manual_create_account", testRule); onView(withId(R.id.next)) .check(matches(isDisplayed())) @@ -73,7 +70,7 @@ public class SetupDataTest extends ScreenshotTest { .check(matches(isDisplayed())) .perform(typeText(PASSWORD)); onView(withId(R.id.next)) - .check(matches(isDisplayed())) + .check(matches(allOf(isDisplayed(), isEnabled()))) .perform(click()); // White-list Doze if needed @@ -94,14 +91,6 @@ public class SetupDataTest extends ScreenshotTest { lifecycleManager.waitForStartup(); assertTrue(accountManager.hasDatabaseKey()); createTestData(); - - // close expiry warning - onView(withId(R.id.expiryWarning)) - .perform(waitUntilMatches(isDisplayed())); - onView(withId(R.id.expiryWarningClose)) - .check(matches(isDisplayed())); - onView(withId(R.id.expiryWarningClose)) - .perform(click()); } private void createTestData() { @@ -116,7 +105,7 @@ public class SetupDataTest extends ScreenshotTest { throws DbException { Context ctx = getApplicationContext(); String bobName = ctx.getString(R.string.screenshot_bob); - Contact bob = testDataCreator.addContact(bobName, true); + Contact bob = testDataCreator.addContact(bobName, false, true); // TODO add messages diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java index c99250ad8..c396a835a 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java @@ -14,7 +14,6 @@ import org.junit.runner.RunWith; import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.contrib.DrawerActions; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.rule.ActivityTestRule; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.espresso.Espresso.onView; @@ -38,8 +37,8 @@ import static org.junit.Assume.assumeTrue; public class SettingsActivityScreenshotTest extends ScreenshotTest { @Rule - public ActivityTestRule testRule = - new ActivityTestRule<>(SettingsActivity.class); + public CleanAccountTestRule testRule = + new CleanAccountTestRule<>(SettingsActivity.class); @Override protected void inject(BriarUiTestComponent component) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java index b113a195a..d0d863b65 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java @@ -19,4 +19,6 @@ public interface BriarApplication extends BrambleApplication { SharedPreferences getDefaultSharedPreferences(); boolean isRunningInBackground(); + + boolean isInstrumentationTest(); } 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 927827913..3026d8302 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 @@ -151,4 +151,9 @@ public class BriarApplicationImpl extends Application ActivityManager.getMyMemoryState(info); return (info.importance != IMPORTANCE_FOREGROUND); } + + @Override + public boolean isInstrumentationTest() { + return false; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index 6e36b1390..4014a73db 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -301,7 +301,9 @@ public class BriarService extends Service { LOG.info("Interrupted while waiting for shutdown"); } LOG.info("Exiting"); - System.exit(0); + if (!app.isInstrumentationTest()) { + System.exit(0); + } }, "BackgroundShutdown"); }, "BackgroundShutdown"); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java index 4f7b0fbde..da993afed 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java @@ -193,6 +193,9 @@ public abstract class BaseActivity extends AppCompatActivity } private boolean showScreenFilterWarning() { + if (((BriarApplication) getApplication()).isInstrumentationTest()) { + return false; + } // If the dialog is already visible, filter the tap ScreenFilterDialogFragment f = findDialogFragment(); if (f != null && f.isVisible()) return false; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index baf49a711..eae436236 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.account.UnlockActivity; import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.DbController; @@ -236,7 +237,8 @@ public abstract class BriarActivity extends BaseActivity { if (SDK_INT >= 21) finishAndRemoveTask(); else supportFinishAfterTransition(); LOG.info("Exiting"); - System.exit(0); + BriarApplication app = (BriarApplication) getApplication(); + if (!app.isInstrumentationTest()) System.exit(0); } @Deprecated diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java index 65c3efec4..e0c166bf0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.AndroidWakeLockManager; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarService; import org.briarproject.briar.android.BriarService.BriarServiceConnection; import org.briarproject.briar.android.controller.handler.ResultHandler; @@ -104,7 +105,8 @@ public class BriarControllerImpl implements BriarController { @Override public void hasDozed(ResultHandler handler) { - if (!dozeWatchdog.getAndResetDozeFlag() + BriarApplication app = (BriarApplication) activity.getApplication(); + if (app.isInstrumentationTest() || !dozeWatchdog.getAndResetDozeFlag() || !needsDozeWhitelisting(activity)) { handler.onResult(false); return; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index c16fcd4e1..d9865871d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -27,6 +27,7 @@ import org.briarproject.bramble.api.plugin.Plugin.State; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.blog.FeedFragment; @@ -134,7 +135,8 @@ public class NavDrawerActivity extends BriarActivity implements navDrawerViewModel = provider.get(NavDrawerViewModel.class); pluginViewModel = provider.get(PluginViewModel.class); - if (IS_DEBUG_BUILD) { + BriarApplication app = (BriarApplication) getApplication(); + if (IS_DEBUG_BUILD && !app.isInstrumentationTest()) { navDrawerViewModel.showExpiryWarning() .observe(this, this::showExpiryWarning); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java index 132cc0b9f..783922c60 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.viewmodel.DbViewModel; import java.util.concurrent.Executor; @@ -120,7 +121,9 @@ public class NavDrawerViewModel extends DbViewModel { @UiThread void checkDozeWhitelisting() { // check this first, to hit the DbThread only when really necessary - if (!needsDozeWhitelisting(getApplication())) { + BriarApplication app = (BriarApplication) getApplication(); + if (app.isInstrumentationTest() || + !needsDozeWhitelisting(getApplication())) { shouldAskForDozeWhitelisting.setValue(false); return; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java index 85aa5bb42..79e537d29 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java @@ -61,6 +61,8 @@ public class SplashScreenActivity extends BaseActivity { startNextActivity(ENTRY_ACTIVITY); finish(); } else { + int duration = + getResources().getInteger(R.integer.splashScreenDuration); new Handler().postDelayed(() -> { if (currentTimeMillis() >= EXPIRY_DATE) { LOG.info("Expired"); @@ -69,7 +71,7 @@ public class SplashScreenActivity extends BaseActivity { startNextActivity(ENTRY_ACTIVITY); } supportFinishAfterTransition(); - }, 500); + }, duration); } } diff --git a/briar-android/src/main/res/layout/splash.xml b/briar-android/src/main/res/layout/splash.xml index 0cd22a43c..d78083132 100644 --- a/briar-android/src/main/res/layout/splash.xml +++ b/briar-android/src/main/res/layout/splash.xml @@ -5,6 +5,7 @@ android:layout_height="match_parent"> @android:integer/config_mediumAnimTime + 500 + diff --git a/briar-android/src/screenshotDebug/AndroidManifest.xml b/briar-android/src/screenshotDebug/AndroidManifest.xml index d3e565327..376a33b94 100644 --- a/briar-android/src/screenshotDebug/AndroidManifest.xml +++ b/briar-android/src/screenshotDebug/AndroidManifest.xml @@ -16,4 +16,7 @@ + + + diff --git a/briar-android/src/screenshotDebug/res/values/attrs.xml b/briar-android/src/screenshotDebug/res/values/attrs.xml new file mode 100644 index 000000000..e39053ba1 --- /dev/null +++ b/briar-android/src/screenshotDebug/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + 7500 + + diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle index e3be69cbc..6b11c5cdc 100644 --- a/briar-android/witness.gradle +++ b/briar-android/witness.gradle @@ -51,9 +51,11 @@ dependencyVerification { 'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6', 'androidx.test.espresso:espresso-intents:3.3.0:espresso-intents-3.3.0.aar:5b6cd6aadce78edc705d93c1e81ace3b59be97128aca0e88fd9c5c176aa9bf10', 'androidx.test.ext:junit:1.1.2:junit-1.1.2.aar:6c6ab120c640bf16fcaae69cb83c144d0ed6b6298562be0ac35e37ed969c0409', + 'androidx.test.services:test-services:1.3.0:test-services-1.3.0.apk:1b88faab6864baf25c5d0b92a610c283c159a566e7a56c03307117fa1b542993', 'androidx.test.uiautomator:uiautomator:2.2.0:uiautomator-2.2.0.aar:2838e9d961dbffefbbd229a2bd4f6f82ac4fb2462975862a9e75e9ed325a3197', 'androidx.test:core:1.3.0:core-1.3.0.aar:86549cae8c5b848f817e2c716e174c7dab61caf0b4df9848680eeb753089a337', 'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e', + 'androidx.test:orchestrator:1.3.0:orchestrator-1.3.0.apk:676f808d08a3d05050eae30c3b7d92ce5cef1e00a54d68355bb7e7d4b72366fe', 'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a', 'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b', 'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612', diff --git a/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java b/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java index 0f8ebc2ff..b96100e0f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java @@ -24,5 +24,6 @@ public interface TestDataCreator { int numBlogPosts, int numForums, int numForumPosts); @IoExecutor - Contact addContact(String name, boolean avatar) throws DbException; + Contact addContact(String name, boolean alias, boolean avatar) + throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java index e773d3195..987e4cc28 100644 --- a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java @@ -158,15 +158,15 @@ public class TestDataCreatorImpl implements TestDataCreator { LocalAuthor localAuthor = identityManager.getLocalAuthor(); for (int i = 0; i < numContacts; i++) { LocalAuthor remote = getRandomAuthor(); - Contact contact = - addContact(localAuthor.getId(), remote, avatarPercent); + Contact contact = addContact(localAuthor.getId(), remote, + random.nextBoolean(), avatarPercent); contacts.add(contact); } return contacts; } private Contact addContact(AuthorId localAuthorId, LocalAuthor remote, - int avatarPercent) throws DbException { + boolean alias, int avatarPercent) throws DbException { // prepare to add contact SecretKey secretKey = getSecretKey(); long timestamp = clock.currentTimeMillis(); @@ -179,7 +179,7 @@ public class TestDataCreatorImpl implements TestDataCreator { Contact contact = db.transactionWithResult(false, txn -> { ContactId contactId = contactManager.addContact(txn, remote, localAuthorId, secretKey, timestamp, true, verified, true); - if (random.nextBoolean()) { + if (alias) { contactManager.setContactAlias(txn, contactId, getRandomAuthorName()); } @@ -197,11 +197,12 @@ public class TestDataCreatorImpl implements TestDataCreator { } @Override - public Contact addContact(String name, boolean avatar) throws DbException { + public Contact addContact(String name, boolean alias, boolean avatar) + throws DbException { LocalAuthor localAuthor = identityManager.getLocalAuthor(); LocalAuthor remote = authorFactory.createLocalAuthor(name); int avatarPercent = avatar ? 100 : 0; - return addContact(localAuthor.getId(), remote, avatarPercent); + return addContact(localAuthor.getId(), remote, alias, avatarPercent); } private String getRandomAuthorName() { diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt index ee88dfce1..dca4e336d 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt @@ -25,7 +25,7 @@ class ContactControllerIntegrationTest: IntegrationTest() { // add one test contact val testContactName= "testContactName" - testDataCreator.addContact(testContactName, false) + testDataCreator.addContact(testContactName, true, false) // retrieve list with one test contact response = get("$url/contacts")