diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 6d6a46b88..0f7507e73 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -126,10 +126,11 @@ dependencies { testImplementation 'androidx.test:runner:1.3.0' testImplementation 'androidx.test.ext:junit:1.1.2' testImplementation 'androidx.fragment:fragment-testing:1.2.5' + testImplementation "androidx.arch.core:core-testing:2.1.0" testImplementation "androidx.test.espresso:espresso-core:$espressoVersion" testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'org.mockito:mockito-core:3.1.0' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.1' testImplementation "org.jmock:jmock:$jmockVersion" testImplementation "org.jmock:jmock-junit4:$jmockVersion" testImplementation "org.jmock:jmock-legacy:$jmockVersion" diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java index e5152dd0a..b3a7cb122 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveResult; @@ -54,8 +55,9 @@ public class AddContactViewModel extends DbViewModel { ContactManager contactManager, @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, - TransactionManager db) { - super(application, dbExecutor, lifecycleManager, db); + TransactionManager db, + AndroidExecutor androidExecutor) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.contactManager = contactManager; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/PendingContactListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/PendingContactListViewModel.java index b1e2feb48..8317214c1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/PendingContactListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/PendingContactListViewModel.java @@ -19,6 +19,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.rendezvous.RendezvousPoller; import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.viewmodel.DbViewModel; import java.util.ArrayList; @@ -58,10 +59,11 @@ public class PendingContactListViewModel extends DbViewModel @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, ContactManager contactManager, RendezvousPoller rendezvousPoller, EventBus eventBus) { - super(application, dbExecutor, lifecycleManager, db); + super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.contactManager = contactManager; this.rendezvousPoller = rendezvousPoller; this.eventBus = eventBus; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 232983139..7ff5d50e4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -22,6 +22,7 @@ import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.attachment.AttachmentCreator; import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentResult; @@ -106,6 +107,7 @@ public class ConversationViewModel extends DbViewModel @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, EventBus eventBus, MessagingManager messagingManager, ContactManager contactManager, @@ -113,7 +115,7 @@ public class ConversationViewModel extends DbViewModel PrivateMessageFactory privateMessageFactory, AttachmentRetriever attachmentRetriever, AttachmentCreator attachmentCreator) { - super(application, dbExecutor, lifecycleManager, db); + super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.db = db; this.eventBus = eventBus; this.messagingManager = messagingManager; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java index 63dddcd1d..eeba81f3b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveEvent; @@ -80,8 +81,9 @@ public class ImageViewModel extends DbViewModel implements EventListener { @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, @IoExecutor Executor ioExecutor) { - super(application, dbExecutor, lifecycleManager, db); + super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.messagingManager = messagingManager; this.eventBus = eventBus; this.ioExecutor = ioExecutor; 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 99a494bf2..132cc0b9f 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 @@ -9,6 +9,7 @@ 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.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.viewmodel.DbViewModel; import java.util.concurrent.Executor; @@ -53,8 +54,9 @@ public class NavDrawerViewModel extends DbViewModel { @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, SettingsManager settingsManager) { - super(app, dbExecutor, lifecycleManager, db); + super(app, dbExecutor, lifecycleManager, db, androidExecutor); this.settingsManager = settingsManager; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java index 3f19c3f7d..1de04c570 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java @@ -29,6 +29,7 @@ import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.viewmodel.DbViewModel; import java.util.concurrent.Executor; @@ -87,9 +88,10 @@ public class PluginViewModel extends DbViewModel implements EventListener { @Inject PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, - SettingsManager settingsManager, PluginManager pluginManager, - EventBus eventBus, NetworkManager networkManager) { - super(app, dbExecutor, lifecycleManager, db); + AndroidExecutor androidExecutor, SettingsManager settingsManager, + PluginManager pluginManager, EventBus eventBus, + NetworkManager networkManager) { + super(app, dbExecutor, lifecycleManager, db, androidExecutor); this.app = app; this.settingsManager = settingsManager; this.pluginManager = pluginManager; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListViewModel.java index 1266fd47f..5d03f34e7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/list/GroupListViewModel.java @@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.event.GroupAddedEvent; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -78,11 +79,12 @@ class GroupListViewModel extends DbViewModel implements EventListener { @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, TransactionManager db, + AndroidExecutor androidExecutor, PrivateGroupManager groupManager, GroupInvitationManager groupInvitationManager, ContactManager contactManager, AndroidNotificationManager notificationManager, EventBus eventBus) { - super(application, dbExecutor, lifecycleManager, db); + super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.groupManager = groupManager; this.groupInvitationManager = groupInvitationManager; this.contactManager = contactManager; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java index c928894df..d14260aae 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.viewmodel; import android.app.Application; -import android.os.Handler; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbCallable; @@ -10,6 +9,7 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidExecutor; import java.util.ArrayList; import java.util.List; @@ -27,7 +27,6 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.RecyclerView; -import static android.os.Looper.getMainLooper; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logException; @@ -42,16 +41,19 @@ public abstract class DbViewModel extends AndroidViewModel { private final Executor dbExecutor; private final LifecycleManager lifecycleManager; private final TransactionManager db; + private final AndroidExecutor androidExecutor; public DbViewModel( @NonNull Application application, @DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, - TransactionManager db) { + TransactionManager db, + AndroidExecutor androidExecutor) { super(application); this.dbExecutor = dbExecutor; this.lifecycleManager = lifecycleManager; this.db = db; + this.androidExecutor = androidExecutor; } /** @@ -103,8 +105,8 @@ public abstract class DbViewModel extends AndroidViewModel { Thread.currentThread().interrupt(); } catch (DbException e) { logException(LOG, WARNING, e); - new Handler(getMainLooper()) - .post(() -> uiUpdate.call(new LiveResult<>(e))); + androidExecutor.runOnUiThread( + () -> uiUpdate.call(new LiveResult<>(e))); } }); } diff --git a/briar-android/src/test/java/org/briarproject/briar/android/AndroidExecutorTestImpl.java b/briar-android/src/test/java/org/briarproject/briar/android/AndroidExecutorTestImpl.java new file mode 100644 index 000000000..5c02d8718 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/AndroidExecutorTestImpl.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.android; + +import org.briarproject.bramble.api.system.AndroidExecutor; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; + +public class AndroidExecutorTestImpl implements AndroidExecutor { + + private final Executor executor; + + public AndroidExecutorTestImpl(Executor executor) { + this.executor = executor; + } + + @Override + public Future runOnBackgroundThread(Callable c) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void runOnBackgroundThread(Runnable r) { + executor.execute(r); + } + + @Override + public Future runOnUiThread(Callable c) { + throw new IllegalStateException("not implemented"); + } + + @Override + public void runOnUiThread(Runnable r) { + executor.execute(r); + } +} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/privategroup/list/GroupListViewModelTest.java b/briar-android/src/test/java/org/briarproject/briar/android/privategroup/list/GroupListViewModelTest.java new file mode 100644 index 000000000..8030995b0 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/privategroup/list/GroupListViewModelTest.java @@ -0,0 +1,212 @@ +package org.briarproject.briar.android.privategroup.list; + +import android.app.Application; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.AuthorInfo; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.DbExpectations; +import org.briarproject.bramble.test.ImmediateExecutor; +import org.briarproject.briar.android.AndroidExecutorTestImpl; +import org.briarproject.briar.android.viewmodel.LiveResult; +import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.privategroup.PrivateGroup; +import org.briarproject.briar.api.privategroup.PrivateGroupManager; +import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent; +import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; +import org.jmock.Expectations; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Rule; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule; + +import static edu.emory.mathcs.backport.java.util.Collections.emptyList; +import static edu.emory.mathcs.backport.java.util.Collections.singletonList; +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getContact; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.briar.android.viewmodel.LiveDataTestUtil.getOrAwaitValue; +import static org.briarproject.briar.api.client.MessageTracker.GroupCount; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class GroupListViewModelTest extends BrambleMockTestCase { + + @Rule + public final InstantTaskExecutorRule testRule = + new InstantTaskExecutorRule(); + + private final LifecycleManager lifecycleManager = + context.mock(LifecycleManager.class); + private final TransactionManager db = + context.mock(TransactionManager.class); + private final PrivateGroupManager groupManager = + context.mock(PrivateGroupManager.class); + private final GroupInvitationManager groupInvitationManager = + context.mock(GroupInvitationManager.class); + private final ContactManager contactManager = + context.mock(ContactManager.class); + private final AndroidNotificationManager notificationManager = + context.mock(AndroidNotificationManager.class); + private final EventBus eventBus = context.mock(EventBus.class); + + private final GroupListViewModel viewModel; + + + private final Group g1 = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group g2 = getGroup(CLIENT_ID, MAJOR_VERSION); + private final PrivateGroup privateGroup1 = + new PrivateGroup(g1, "foo", getAuthor(), getRandomBytes(2)); + private final PrivateGroup privateGroup2 = + new PrivateGroup(g2, "bar", getAuthor(), getRandomBytes(2)); + private final AuthorInfo authorInfo1 = + new AuthorInfo(AuthorInfo.Status.UNVERIFIED); + private final AuthorInfo authorInfo2 = + new AuthorInfo(AuthorInfo.Status.VERIFIED); + + private final GroupCount groupCount1 = new GroupCount(2, 1, 23L); + private final GroupCount groupCount2 = new GroupCount(5, 3, 42L); + private final GroupItem item1 = + new GroupItem(privateGroup1, authorInfo1, groupCount1, false); + private final GroupItem item2 = + new GroupItem(privateGroup2, authorInfo2, groupCount2, false); + + public GroupListViewModelTest() { + context.setImposteriser(ClassImposteriser.INSTANCE); + Application app = context.mock(Application.class); + context.checking(new Expectations() {{ + oneOf(eventBus).addListener(with(any(EventListener.class))); + }}); + Executor dbExecutor = new ImmediateExecutor(); + AndroidExecutor androidExecutor = + new AndroidExecutorTestImpl(dbExecutor); + viewModel = new GroupListViewModel(app, dbExecutor, lifecycleManager, + db, androidExecutor, groupManager, groupInvitationManager, + contactManager, notificationManager, eventBus); + } + + @Test + public void testLoadGroupsException() throws Exception { + DbException dbException = new DbException(); + + Transaction txn = new Transaction(null, true); + context.checking(new DbExpectations() {{ + oneOf(lifecycleManager).waitForDatabase(); + oneOf(db).transaction(with(true), withDbRunnable(txn)); + oneOf(groupManager).getPrivateGroups(txn); + will(throwException(dbException)); + }}); + + viewModel.loadGroups(); + + LiveResult> result = + getOrAwaitValue(viewModel.getGroupItems()); + assertTrue(result.hasError()); + assertEquals(dbException, result.getException()); + assertNull(result.getResultOrNull()); + } + + @Test + public void testLoadGroups() throws Exception { + Transaction txn = new Transaction(null, true); + context.checking(new DbExpectations() {{ + oneOf(lifecycleManager).waitForDatabase(); + oneOf(db).transaction(with(true), withDbRunnable(txn)); + oneOf(groupManager).getPrivateGroups(txn); + will(returnValue(Arrays.asList(privateGroup1, privateGroup2))); + }}); + expectLoadGroup(txn, privateGroup1, authorInfo1, groupCount1, false); + expectLoadGroup(txn, privateGroup2, authorInfo2, groupCount2, false); + + viewModel.loadGroups(); + + // unpack updated live data + LiveResult> result = + getOrAwaitValue(viewModel.getGroupItems()); + assertFalse(result.hasError()); + List liveList = result.getResultOrNull(); + assertNotNull(liveList); + // list is sorted by last message timestamp + assertEquals(Arrays.asList(item2, item1), liveList); + + // group 1 gets dissolved by creator + Event dissolvedEvent = new GroupDissolvedEvent(privateGroup1.getId()); + viewModel.eventOccurred(dissolvedEvent); + result = getOrAwaitValue(viewModel.getGroupItems()); + liveList = result.getResultOrNull(); + assertNotNull(liveList); + assertEquals(2, liveList.size()); + // assert that list update includes dissolved group item + for (GroupItem item : liveList) { + if (item.getId().equals(privateGroup1.getId())) { + assertTrue(item.isDissolved()); + } else if (item.getId().equals(privateGroup2.getId())) { + assertFalse(item.isDissolved()); + } else fail(); + } + } + + @Test + public void testLoadNumInvitations() throws Exception { + context.checking(new Expectations() {{ + oneOf(lifecycleManager).waitForDatabase(); + oneOf(groupInvitationManager).getInvitations(); + will(returnValue(emptyList())); + }}); + viewModel.loadNumInvitations(); + + int num = getOrAwaitValue(viewModel.getNumInvitations()); + assertEquals(0, num); + + PrivateGroup pg = context.mock(PrivateGroup.class); + Contact c = getContact(); + GroupInvitationItem item = new GroupInvitationItem(pg, c); + context.checking(new Expectations() {{ + oneOf(lifecycleManager).waitForDatabase(); + oneOf(groupInvitationManager).getInvitations(); + will(returnValue(singletonList(item))); + }}); + viewModel.loadNumInvitations(); + + num = getOrAwaitValue(viewModel.getNumInvitations()); + assertEquals(1, num); + } + + private void expectLoadGroup(Transaction txn, PrivateGroup privateGroup, + AuthorInfo authorInfo, GroupCount groupCount, boolean dissolved) + throws DbException { + context.checking(new DbExpectations() {{ + oneOf(contactManager) + .getAuthorInfo(txn, privateGroup.getCreator().getId()); + will(returnValue(authorInfo)); + oneOf(groupManager).getGroupCount(txn, privateGroup.getId()); + will(returnValue(groupCount)); + oneOf(groupManager).isDissolved(txn, privateGroup.getId()); + will(returnValue(dissolved)); + }}); + } + +} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveDataTestUtil.java b/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveDataTestUtil.java new file mode 100644 index 000000000..0c26fba5d --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/viewmodel/LiveDataTestUtil.java @@ -0,0 +1,36 @@ +/* Copyright 2019 Google LLC. + SPDX-License-Identifier: Apache-2.0 + https://gist.github.com/JoseAlcerreca/1e9ee05dcdd6a6a6fa1cbfc125559bba + */ + +package org.briarproject.briar.android.viewmodel; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; + +public class LiveDataTestUtil { + public static T getOrAwaitValue(final LiveData liveData) + throws InterruptedException { + final Object[] data = new Object[1]; + final CountDownLatch latch = new CountDownLatch(1); + Observer observer = new Observer() { + @Override + public void onChanged(@Nullable T o) { + data[0] = o; + latch.countDown(); + liveData.removeObserver(this); + } + }; + liveData.observeForever(observer); + // Don't wait indefinitely if the LiveData is not set. + if (!latch.await(2, TimeUnit.SECONDS)) { + throw new RuntimeException("LiveData value was never set."); + } + //noinspection unchecked + return (T) data[0]; + } +}