diff --git a/briar-android/fastlane/Screengrabfile b/briar-android/fastlane/Screengrabfile index bacf6905b..a74c46ea4 100644 --- a/briar-android/fastlane/Screengrabfile +++ b/briar-android/fastlane/Screengrabfile @@ -3,6 +3,7 @@ locales ['en-US'] use_tests_in_classes([ 'org.briarproject.briar.android.login.SetupActivityScreenshotTest', 'org.briarproject.briar.android.settings.SettingsActivityScreenshotTest', + 'org.briarproject.briar.android.contact.ConversationActivityScreenshotTest', ]) 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" diff --git a/briar-android/fastlane/demo-mode-activate.sh b/briar-android/fastlane/demo-mode-activate.sh index 23001fb56..41dbe273e 100755 --- a/briar-android/fastlane/demo-mode-activate.sh +++ b/briar-android/fastlane/demo-mode-activate.sh @@ -4,4 +4,8 @@ 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 +adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1337 + +# workaround for Android Pie hidden API Espresso bug +adb shell settings put global hidden_api_policy_pre_p_apps 1 +adb shell settings put global hidden_api_policy_p_apps 1 diff --git a/briar-android/proguard-test.txt b/briar-android/proguard-test.txt index df4eb272e..91b89ae58 100644 --- a/briar-android/proguard-test.txt +++ b/briar-android/proguard-test.txt @@ -12,4 +12,4 @@ -keep class junit.** { *; } -dontwarn junit.** --dontwarn org.briarproject.briar.android.BriarTestComponentApplication \ No newline at end of file +-dontwarn org.briarproject.briar.android.** 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 index f00200ac5..da6b49622 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -4,6 +4,7 @@ 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.contact.ConversationActivityScreenshotTest; import org.briarproject.briar.android.login.SetupActivityScreenshotTest; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest; @@ -22,6 +23,7 @@ import dagger.Component; }) public interface BriarUiTestComponent extends AndroidComponent { + void inject(ConversationActivityScreenshotTest test); void inject(SetupActivityScreenshotTest test); void inject(NavDrawerActivityTest test); void inject(SettingsActivityScreenshotTest test); diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/contact/ConversationActivityScreenshotTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/contact/ConversationActivityScreenshotTest.java new file mode 100644 index 000000000..ac5aa89e2 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/contact/ConversationActivityScreenshotTest.java @@ -0,0 +1,86 @@ +package org.briarproject.briar.android.contact; + +import android.content.Context; +import android.content.Intent; +import android.support.test.runner.AndroidJUnit4; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.db.DbException; +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.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; +import static org.briarproject.briar.android.contact.ConversationActivity.CONTACT_ID; +import static org.briarproject.briar.android.test.ViewActions.waitUntilMatches; +import static org.hamcrest.Matchers.allOf; + +@RunWith(AndroidJUnit4.class) +public class ConversationActivityScreenshotTest extends ScreenshotTest { + + @Rule + public CleanAccountTestRule testRule = + new CleanAccountTestRule<>(ConversationActivity.class, + this::createTestData); + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void messaging() { + Context targetContext = getInstrumentation().getTargetContext(); + Intent intent = new Intent(targetContext, ConversationActivity.class); + intent.putExtra(CONTACT_ID, 1); + testRule.launchActivity(intent); + + onView(withId(R.id.conversationView)).perform(waitUntilMatches( + allOf(withText(R.string.screenshot_message_3), isDisplayed()))); + + screenshot("manual_messaging"); + } + + private void createTestData() { + try { + createTestDataExceptions(); + } catch (DbException | FormatException e) { + throw new AssertionError(e); + } + } + + private void createTestDataExceptions() + throws DbException, FormatException { + String bobName = + getTargetContext().getString(R.string.screenshot_bob); + Contact bob = testDataCreator.addContact(bobName); + + String bobHi = getTargetContext() + .getString(R.string.screenshot_message_1); + long bobTime = getMinutesAgo(2); + testDataCreator.addPrivateMessage(bob, bobHi, bobTime, true); + + String aliceHi = getTargetContext() + .getString(R.string.screenshot_message_2); + long aliceTime = getMinutesAgo(1); + testDataCreator.addPrivateMessage(bob, aliceHi, aliceTime, false); + + String bobHi2 = getTargetContext() + .getString(R.string.screenshot_message_3); + long bobTime2 = getMinutesAgo(0); + testDataCreator.addPrivateMessage(bob, bobHi2, bobTime2, true); + + connectionRegistry.registerConnection(bob.getId(), ID, true); + } + +} 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 index 355dd7763..2d3b1ced8 100644 --- 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 @@ -1,15 +1,22 @@ package org.briarproject.briar.android.test; import android.app.Activity; +import android.content.Intent; 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.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.ConnectionRegistry; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.briar.android.BriarService; import org.briarproject.briar.android.BriarTestComponentApplication; import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.api.test.TestDataCreator; import org.junit.ClassRule; +import javax.annotation.Nullable; import javax.inject.Inject; import tools.fastlane.screengrab.Screengrab; @@ -31,6 +38,12 @@ public abstract class ScreenshotTest { protected AccountManager accountManager; @Inject protected LifecycleManager lifecycleManager; + @Inject + protected TestDataCreator testDataCreator; + @Inject + protected ConnectionRegistry connectionRegistry; + @Inject + protected Clock clock; public ScreenshotTest() { super(); @@ -55,11 +68,30 @@ public abstract class ScreenshotTest { } } + protected long getMinutesAgo(int minutes) { + return clock.currentTimeMillis() - minutes * 60 * 1000; + } + + @NotNullByDefault protected class CleanAccountTestRule extends IntentsTestRule { + @Nullable + private final Runnable runnable; + public CleanAccountTestRule(Class activityClass) { super(activityClass); + this.runnable = null; + } + + /** + * Use this if you need to run code before launching the activity. + * Note: You need to use {@link #launchActivity(Intent)} yourself + * to start the activity. + */ + public CleanAccountTestRule(Class activityClass, Runnable runnable) { + super(activityClass, false, false); + this.runnable = runnable; } @Override @@ -67,6 +99,17 @@ public abstract class ScreenshotTest { super.beforeActivityLaunched(); accountManager.deleteAccount(); accountManager.createAccount(USERNAME, PASSWORD); + if (runnable != null) { + Intent serviceIntent = + new Intent(getTargetContext(), BriarService.class); + getTargetContext().startService(serviceIntent); + try { + lifecycleManager.waitForStartup(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + runnable.run(); + } } } diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 722a2f50d..9bd9cc224 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -480,4 +480,20 @@ Briar is locked Tap to unlock + + + + Alice + + Bob + + Carol + + Hi Bob! + + Hi Alice! Thanks for telling me about Briar! + + No problem, hope you like it 😀 + + 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 fc210e583..fe1178307 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 @@ -1,12 +1,16 @@ package org.briarproject.briar.api.test; +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @NotNullByDefault public interface TestDataCreator { /** - * Create fake test data on the DatabaseExecutor + * Create fake test data on the IoExecutor * * @param numContacts Number of contacts to create. Must be >= 1 * @param numPrivateMsgs Number of private messages to create for each @@ -18,4 +22,11 @@ public interface TestDataCreator { void createTestData(int numContacts, int numPrivateMsgs, int numBlogPosts, int numForums, int numForumPosts); + @IoExecutor + Contact addContact(String name) throws DbException; + + @IoExecutor + void addPrivateMessage(Contact contact, String body, long time, + boolean local) throws DbException, FormatException; + } 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 811f3a351..15fe35eec 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 @@ -2,6 +2,7 @@ package org.briarproject.briar.test; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; @@ -13,15 +14,18 @@ import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; +import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; @@ -30,10 +34,12 @@ import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPost; import org.briarproject.briar.api.blog.BlogPostFactory; +import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumPost; +import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessageFactory; @@ -59,6 +65,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.test.TestData.AUTHOR_NAMES; import static org.briarproject.briar.test.TestData.GROUP_NAMES; +@NotNullByDefault public class TestDataCreatorImpl implements TestDataCreator { private final Logger LOG = @@ -75,6 +82,7 @@ public class TestDataCreatorImpl implements TestDataCreator { private final DatabaseComponent db; private final IdentityManager identityManager; private final ContactManager contactManager; + private final ContactGroupFactory contactGroupFactory; private final TransportPropertyManager transportPropertyManager; private final MessagingManager messagingManager; private final BlogManager blogManager; @@ -93,6 +101,7 @@ public class TestDataCreatorImpl implements TestDataCreator { BlogPostFactory blogPostFactory, CryptoComponent cryptoComponent, DatabaseComponent db, IdentityManager identityManager, ContactManager contactManager, + ContactGroupFactory contactGroupFactory, TransportPropertyManager transportPropertyManager, MessagingManager messagingManager, BlogManager blogManager, ForumManager forumManager, @IoExecutor Executor ioExecutor) { @@ -106,6 +115,7 @@ public class TestDataCreatorImpl implements TestDataCreator { this.db = db; this.identityManager = identityManager; this.contactManager = contactManager; + this.contactGroupFactory = contactGroupFactory; this.transportPropertyManager = transportPropertyManager; this.messagingManager = messagingManager; this.blogManager = blogManager; @@ -147,17 +157,17 @@ public class TestDataCreatorImpl implements TestDataCreator { List contacts = new ArrayList<>(numContacts); LocalAuthor localAuthor = identityManager.getLocalAuthor(); for (int i = 0; i < numContacts; i++) { - Contact contact = addRandomContact(localAuthor); + LocalAuthor author = getRandomAuthor(); + Contact contact = addContact(localAuthor.getId(), author); contacts.add(contact); } return contacts; } - private Contact addRandomContact(LocalAuthor localAuthor) + private Contact addContact(AuthorId localAuthorId, LocalAuthor author) throws DbException { // prepare to add contact - LocalAuthor author = getRandomAuthor(); SecretKey secretKey = getSecretKey(); long timestamp = clock.currentTimeMillis(); boolean verified = random.nextBoolean(); @@ -170,10 +180,11 @@ public class TestDataCreatorImpl implements TestDataCreator { Transaction txn = db.startTransaction(false); try { ContactId contactId = contactManager - .addContact(txn, author, localAuthor.getId(), secretKey, + .addContact(txn, author, localAuthorId, secretKey, timestamp, true, verified, true); transportPropertyManager.addRemoteProperties(txn, contactId, props); contact = db.getContact(txn, contactId); + fakeClientVisibilities(txn, contact); db.commitTransaction(txn); } finally { db.endTransaction(txn); @@ -187,14 +198,27 @@ public class TestDataCreatorImpl implements TestDataCreator { return contact; } - private LocalAuthor getRandomAuthor() { - int i = random.nextInt(AUTHOR_NAMES.length); - String authorName = AUTHOR_NAMES[i]; + @Override + public Contact addContact(String name) throws DbException { + // load localAuthor from DB, because cache doesn't get refreshed + // when Espresso tests run within one lifecycle + Transaction txn = db.startTransaction(false); + LocalAuthor localAuthor = db.getLocalAuthors(txn).iterator().next(); + db.endTransaction(txn); + return addContact(localAuthor.getId(), getAuthor(name)); + } + + private LocalAuthor getAuthor(String name) { KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] privateKey = keyPair.getPrivate().getEncoded(); - return authorFactory.createLocalAuthor(authorName, publicKey, - privateKey); + return authorFactory.createLocalAuthor(name, publicKey, privateKey); + } + + private LocalAuthor getRandomAuthor() { + int i = random.nextInt(AUTHOR_NAMES.length); + String authorName = AUTHOR_NAMES[i]; + return getAuthor(authorName); } private SecretKey getSecretKey() { @@ -286,7 +310,8 @@ public class TestDataCreatorImpl implements TestDataCreator { Group group = messagingManager.getContactGroup(contact); for (int i = 0; i < numPrivateMsgs; i++) { try { - createPrivateMessage(group.getId(), i); + createRandomPrivateMessage(contact.getId(), group.getId(), + i); } catch (FormatException e) { throw new RuntimeException(e); } @@ -298,23 +323,30 @@ public class TestDataCreatorImpl implements TestDataCreator { } } - private void createPrivateMessage(GroupId groupId, int num) - throws DbException, FormatException { + private void createRandomPrivateMessage(ContactId contactId, + GroupId groupId, int num) throws DbException, FormatException { long timestamp = clock.currentTimeMillis() - num * 60 * 1000; String body = getRandomText(); + boolean local = random.nextBoolean(); + createPrivateMessage(contactId, groupId, body, timestamp, local); + } + + private void createPrivateMessage(ContactId contactId, GroupId groupId, + String body, long timestamp, boolean local) + throws DbException, FormatException { PrivateMessage m = privateMessageFactory .createPrivateMessage(groupId, timestamp, body); - - boolean local = random.nextBoolean(); BdfDictionary meta = new BdfDictionary(); meta.put("timestamp", timestamp); meta.put("local", local); meta.put("read", local); // all local messages are read - Transaction txn = db.startTransaction(false); try { clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); - if (local) messageTracker.trackOutgoingMessage(txn, m.getMessage()); + if (local) { + messageTracker.trackOutgoingMessage(txn, m.getMessage()); + db.receiveMessage(txn, contactId, m.getMessage()); + } else messageTracker.trackIncomingMessage(txn, m.getMessage()); db.commitTransaction(txn); } finally { @@ -322,6 +354,14 @@ public class TestDataCreatorImpl implements TestDataCreator { } } + @Override + public void addPrivateMessage(Contact contact, String body, long time, + boolean local) throws DbException, FormatException { + Group group = messagingManager.getContactGroup(contact); + createPrivateMessage(contact.getId(), group.getId(), body, time, + local); + } + private void createBlogPosts(List contacts, int numBlogPosts) throws DbException { for (int i = 0; i < numBlogPosts; i++) { @@ -400,6 +440,23 @@ public class TestDataCreatorImpl implements TestDataCreator { } } + private void fakeClientVisibilities(Transaction txn, Contact contact) + throws DbException { + fakeClientVisibilities(txn, contact, MessagingManager.CLIENT_ID, + MessagingManager.MAJOR_VERSION); + fakeClientVisibilities(txn, contact, ForumSharingManager.CLIENT_ID, + ForumSharingManager.MAJOR_VERSION); + fakeClientVisibilities(txn, contact, BlogSharingManager.CLIENT_ID, + BlogSharingManager.MAJOR_VERSION); + } + + private void fakeClientVisibilities(Transaction txn, Contact contact, + ClientId clientId, int majorVersion) throws DbException { + Group group = contactGroupFactory + .createContactGroup(clientId, majorVersion, contact); + db.setGroupVisibility(txn, contact.getId(), group.getId(), SHARED); + } + private String getRandomText() { int minLength = 3 + random.nextInt(500); int maxWordLength = 15;