Update group membership for a group call when it changes.

This commit is contained in:
Cody Henthorne 2020-12-05 20:55:52 -05:00 committed by GitHub
parent 234e4be924
commit 2dcc7d284f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 11 deletions

View file

@ -241,6 +241,7 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
viewModel.getCallParticipantsState().observe(this, callScreen::updateCallParticipants);
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
viewModel.getGroupMembers().observe(this, unused -> updateGroupMembersForGroupCall());
callScreen.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
@ -505,6 +506,10 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
}
}
private void updateGroupMembersForGroupCall() {
startService(new Intent(this, WebRtcCallService.class).setAction(WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS));
}
@Override
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();

View file

@ -17,6 +17,8 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -30,17 +32,19 @@ import java.util.List;
public class WebRtcCallViewModel extends ViewModel {
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
private final LiveData<Recipient> groupRecipient = LiveDataUtil.filter(Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData), Recipient::isActiveGroup);
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
private boolean canDisplayTooltipIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
@ -90,6 +94,10 @@ public class WebRtcCallViewModel extends ViewModel {
return safetyNumberChangeEvent;
}
public LiveData<List<GroupMemberEntry.FullMember>> getGroupMembers() {
return groupMembers;
}
public boolean canEnterPipMode() {
return canEnterPipMode;
}

View file

@ -129,6 +129,25 @@ public final class LiveDataUtil {
return mediatorLiveData;
}
/**
* Skip the first {@param skip} emissions before emitting everything else.
*/
public static @NonNull <T> LiveData<T> skip(@NonNull LiveData<T> source, int skip) {
return new MediatorLiveData<T>() {
int skipsRemaining = skip;
{
addSource(source, value -> {
if (skipsRemaining <= 0) {
setValue(value);
} else {
skipsRemaining--;
}
});
}
};
}
/**
* After {@param delay} ms after observation, emits a single Object, {@param value}.
*/

View file

@ -0,0 +1,120 @@
package org.thoughtcrime.securesms.util.livedata;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import static org.junit.Assert.assertEquals;
import static org.thoughtcrime.securesms.util.livedata.LiveDataTestUtil.assertNoValue;
import static org.thoughtcrime.securesms.util.livedata.LiveDataTestUtil.observeAndGetOneValue;
public final class LiveDataUtilTest_skip {
@Rule
public TestRule rule = new LiveDataRule();
@Test
public void skip_no_value() {
MutableLiveData<String> liveData = new MutableLiveData<>();
LiveData<String> skipped = LiveDataUtil.skip(liveData, 0);
assertNoValue(skipped);
}
@Test
public void skip_same_value_with_zero_skip() {
MutableLiveData<String> liveData = new MutableLiveData<>();
LiveData<String> skipped = LiveDataUtil.skip(liveData, 0);
liveData.setValue("A");
assertEquals("A", observeAndGetOneValue(skipped));
}
@Test
public void skip_second_value_with_skip_one() {
MutableLiveData<String> liveData = new MutableLiveData<>();
TestObserver<String> testObserver = new TestObserver<>();
LiveData<String> skipped = LiveDataUtil.skip(liveData, 1);
skipped.observeForever(testObserver);
liveData.setValue("A");
liveData.setValue("B");
skipped.removeObserver(testObserver);
Assertions.assertThat(testObserver.getValues())
.containsExactly("B");
}
@Test
public void skip_no_value_with_skip() {
MutableLiveData<String> liveData = new MutableLiveData<>();
LiveData<String> skipped = LiveDataUtil.skip(liveData, 1);
liveData.setValue("A");
assertNoValue(skipped);
}
@Test
public void skip_third_and_fourth_value_with_skip_two() {
MutableLiveData<String> liveData = new MutableLiveData<>();
TestObserver<String> testObserver = new TestObserver<>();
LiveData<String> skipped = LiveDataUtil.skip(liveData, 2);
skipped.observeForever(testObserver);
liveData.setValue("A");
liveData.setValue("B");
liveData.setValue("C");
liveData.setValue("D");
skipped.removeObserver(testObserver);
Assertions.assertThat(testObserver.getValues())
.containsExactly("C", "D");
}
@Test
public void skip_set_one_before_then_skip() {
MutableLiveData<String> liveData = new MutableLiveData<>();
TestObserver<String> testObserver = new TestObserver<>();
liveData.setValue("A");
LiveData<String> skipped = LiveDataUtil.skip(liveData, 2);
skipped.observeForever(testObserver);
liveData.setValue("B");
liveData.setValue("C");
liveData.setValue("D");
skipped.removeObserver(testObserver);
Assertions.assertThat(testObserver.getValues())
.containsExactly("C", "D");
}
@Test
public void skip_set_two_before_then_skip() {
MutableLiveData<String> liveData = new MutableLiveData<>();
TestObserver<String> testObserver = new TestObserver<>();
liveData.setValue("A");
liveData.setValue("B");
LiveData<String> skipped = LiveDataUtil.skip(liveData, 2);
skipped.observeForever(testObserver);
liveData.setValue("C");
liveData.setValue("D");
skipped.removeObserver(testObserver);
Assertions.assertThat(testObserver.getValues())
.containsExactly("D");
}
}

View file

@ -0,0 +1,22 @@
package org.thoughtcrime.securesms.util.livedata;
import androidx.lifecycle.Observer;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
public class TestObserver<T> implements Observer<T> {
private final Collection<T> values = new ConcurrentLinkedQueue<>();
@Override
public void onChanged(T t) {
values.add(t);
}
public @NonNull Collection<T> getValues() {
return values;
}
}