Distinguish between primary and secondary devices in participants list.
This commit is contained in:
parent
2d20ceea01
commit
0ccc7e3c06
10 changed files with 132 additions and 104 deletions
|
@ -1,6 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
@ -21,19 +22,19 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public final class CallParticipantListUpdate {
|
public final class CallParticipantListUpdate {
|
||||||
|
|
||||||
private final Set<Holder> added;
|
private final Set<Wrapper> added;
|
||||||
private final Set<Holder> removed;
|
private final Set<Wrapper> removed;
|
||||||
|
|
||||||
CallParticipantListUpdate(@NonNull Set<Holder> added, @NonNull Set<Holder> removed) {
|
CallParticipantListUpdate(@NonNull Set<Wrapper> added, @NonNull Set<Wrapper> removed) {
|
||||||
this.added = added;
|
this.added = added;
|
||||||
this.removed = removed;
|
this.removed = removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Set<Holder> getAdded() {
|
public @NonNull Set<Wrapper> getAdded() {
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Set<Holder> getRemoved() {
|
public @NonNull Set<Wrapper> getRemoved() {
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,66 +69,47 @@ public final class CallParticipantListUpdate {
|
||||||
public static @NonNull CallParticipantListUpdate computeDeltaUpdate(@NonNull List<CallParticipant> oldList,
|
public static @NonNull CallParticipantListUpdate computeDeltaUpdate(@NonNull List<CallParticipant> oldList,
|
||||||
@NonNull List<CallParticipant> newList)
|
@NonNull List<CallParticipant> newList)
|
||||||
{
|
{
|
||||||
Set<CallParticipantId> primaries = getPrimaries(oldList, newList);
|
Set<CallParticipantListUpdate.Wrapper> oldParticipants = Stream.of(oldList)
|
||||||
Set<CallParticipantListUpdate.Holder> oldParticipants = Stream.of(oldList)
|
|
||||||
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
||||||
.map(p -> createHolder(p, primaries.contains(p.getCallParticipantId())))
|
.map(CallParticipantListUpdate::createWrapper)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
Set<CallParticipantListUpdate.Holder> newParticipants = Stream.of(newList)
|
Set<CallParticipantListUpdate.Wrapper> newParticipants = Stream.of(newList)
|
||||||
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
||||||
.map(p -> createHolder(p, primaries.contains(p.getCallParticipantId())))
|
.map(CallParticipantListUpdate::createWrapper)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
Set<CallParticipantListUpdate.Holder> added = SetUtil.difference(newParticipants, oldParticipants);
|
Set<CallParticipantListUpdate.Wrapper> added = SetUtil.difference(newParticipants, oldParticipants);
|
||||||
Set<CallParticipantListUpdate.Holder> removed = SetUtil.difference(oldParticipants, newParticipants);
|
Set<CallParticipantListUpdate.Wrapper> removed = SetUtil.difference(oldParticipants, newParticipants);
|
||||||
|
|
||||||
return new CallParticipantListUpdate(added, removed);
|
return new CallParticipantListUpdate(added, removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Holder createHolder(@NonNull CallParticipant callParticipant, boolean isPrimary) {
|
@VisibleForTesting
|
||||||
return new Holder(callParticipant.getCallParticipantId(), callParticipant.getRecipient(), isPrimary);
|
static Wrapper createWrapper(@NonNull CallParticipant callParticipant) {
|
||||||
|
return new Wrapper(callParticipant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull Set<CallParticipantId> getPrimaries(@NonNull List<CallParticipant> oldList, @NonNull List<CallParticipant> newList) {
|
static final class Wrapper {
|
||||||
return Stream.concat(Stream.of(oldList), Stream.of(newList))
|
private final CallParticipant callParticipant;
|
||||||
.map(CallParticipant::getCallParticipantId)
|
|
||||||
.distinctBy(CallParticipantId::getRecipientId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class Holder {
|
private Wrapper(@NonNull CallParticipant callParticipant) {
|
||||||
private final CallParticipantId callParticipantId;
|
this.callParticipant = callParticipant;
|
||||||
private final Recipient recipient;
|
|
||||||
private final boolean isPrimary;
|
|
||||||
|
|
||||||
private Holder(@NonNull CallParticipantId callParticipantId, @NonNull Recipient recipient, boolean isPrimary) {
|
|
||||||
this.callParticipantId = callParticipantId;
|
|
||||||
this.recipient = recipient;
|
|
||||||
this.isPrimary = isPrimary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Recipient getRecipient() {
|
public @NonNull CallParticipant getCallParticipant() {
|
||||||
return recipient;
|
return callParticipant;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Denotes whether this was the first detected instance of this recipient when generating an update. See
|
|
||||||
* {@link CallParticipantListUpdate#computeDeltaUpdate(List, List)}
|
|
||||||
*/
|
|
||||||
public boolean isPrimary() {
|
|
||||||
return isPrimary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
Holder holder = (Holder) o;
|
Wrapper wrapper = (Wrapper) o;
|
||||||
return callParticipantId.equals(holder.callParticipantId);
|
return callParticipant.getCallParticipantId().equals(wrapper.callParticipant.getCallParticipantId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(callParticipantId);
|
return Objects.hash(callParticipant.getCallParticipantId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||||
private final AvatarImageView avatarImageView;
|
private final AvatarImageView avatarImageView;
|
||||||
private final TextView descriptionTextView;
|
private final TextView descriptionTextView;
|
||||||
|
|
||||||
private final Set<CallParticipantListUpdate.Holder> pendingAdditions = new HashSet<>();
|
private final Set<CallParticipantListUpdate.Wrapper> pendingAdditions = new HashSet<>();
|
||||||
private final Set<CallParticipantListUpdate.Holder> pendingRemovals = new HashSet<>();
|
private final Set<CallParticipantListUpdate.Wrapper> pendingRemovals = new HashSet<>();
|
||||||
|
|
||||||
private boolean isEnabled = true;
|
private boolean isEnabled = true;
|
||||||
|
|
||||||
|
@ -112,18 +112,18 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||||
avatarImageView.setVisibility(recipient == null ? View.GONE : View.VISIBLE);
|
avatarImageView.setVisibility(recipient == null ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDescription(@NonNull Set<CallParticipantListUpdate.Holder> holders, boolean isAdded) {
|
private void setDescription(@NonNull Set<CallParticipantListUpdate.Wrapper> wrappers, boolean isAdded) {
|
||||||
if (holders.isEmpty()) {
|
if (wrappers.isEmpty()) {
|
||||||
descriptionTextView.setText("");
|
descriptionTextView.setText("");
|
||||||
} else {
|
} else {
|
||||||
setDescriptionForRecipients(holders, isAdded);
|
setDescriptionForRecipients(wrappers, isAdded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDescriptionForRecipients(@NonNull Set<CallParticipantListUpdate.Holder> recipients, boolean isAdded) {
|
private void setDescriptionForRecipients(@NonNull Set<CallParticipantListUpdate.Wrapper> recipients, boolean isAdded) {
|
||||||
Iterator<CallParticipantListUpdate.Holder> iterator = recipients.iterator();
|
Iterator<CallParticipantListUpdate.Wrapper> iterator = recipients.iterator();
|
||||||
Context context = getContentView().getContext();
|
Context context = getContentView().getContext();
|
||||||
String description;
|
String description;
|
||||||
|
|
||||||
switch (recipients.size()) {
|
switch (recipients.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -144,22 +144,14 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||||
descriptionTextView.setText(description);
|
descriptionTextView.setText(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull Recipient getNextRecipient(@NonNull Iterator<CallParticipantListUpdate.Holder> holderIterator) {
|
private @NonNull Recipient getNextRecipient(@NonNull Iterator<CallParticipantListUpdate.Wrapper> wrapperIterator) {
|
||||||
return holderIterator.next().getRecipient();
|
return wrapperIterator.next().getCallParticipant().getRecipient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull String getNextDisplayName(@NonNull Iterator<CallParticipantListUpdate.Holder> holderIterator) {
|
private @NonNull String getNextDisplayName(@NonNull Iterator<CallParticipantListUpdate.Wrapper> wrapperIterator) {
|
||||||
CallParticipantListUpdate.Holder holder = holderIterator.next();
|
CallParticipantListUpdate.Wrapper wrapper = wrapperIterator.next();
|
||||||
Recipient recipient = holder.getRecipient();
|
|
||||||
|
|
||||||
if (recipient.isSelf()) {
|
return wrapper.getCallParticipant().getRecipientDisplayName(getContentView().getContext());
|
||||||
return getContentView().getContext().getString(R.string.CallParticipantsListUpdatePopupWindow__you_on_another_device);
|
|
||||||
} else if (holder.isPrimary()) {
|
|
||||||
return recipient.getDisplayName(getContentView().getContext());
|
|
||||||
} else {
|
|
||||||
return getContentView().getContext().getString(R.string.CallParticipantsListUpdatePopupWindow__s_on_another_device,
|
|
||||||
recipient.getDisplayName(getContentView().getContext()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @StringRes int getOneMemberDescriptionResourceId(boolean isAdded) {
|
private static @StringRes int getOneMemberDescriptionResourceId(boolean isAdded) {
|
||||||
|
|
|
@ -25,8 +25,7 @@ public final class CallParticipantViewState extends RecipientMappingModel<CallPa
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull String getName(@NonNull Context context) {
|
public @NonNull String getName(@NonNull Context context) {
|
||||||
return callParticipant.getRecipient().isSelf() ? context.getString(R.string.GroupMembersDialog_you)
|
return callParticipant.getRecipientDisplayName(context);
|
||||||
: super.getName(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVideoMutedVisibility() {
|
public int getVideoMutedVisibility() {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package org.thoughtcrime.securesms.events;
|
package org.thoughtcrime.securesms.events;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||||
|
@ -12,7 +15,7 @@ import java.util.Objects;
|
||||||
|
|
||||||
public final class CallParticipant {
|
public final class CallParticipant {
|
||||||
|
|
||||||
public static final CallParticipant EMPTY = createRemote(new CallParticipantId(Recipient.UNKNOWN), Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0, true, 0);
|
public static final CallParticipant EMPTY = createRemote(new CallParticipantId(Recipient.UNKNOWN), Recipient.UNKNOWN, null, new BroadcastVideoSink(null), false, false, 0, true, 0, DeviceOrdinal.PRIMARY);
|
||||||
|
|
||||||
private final @NonNull CallParticipantId callParticipantId;
|
private final @NonNull CallParticipantId callParticipantId;
|
||||||
private final @NonNull CameraState cameraState;
|
private final @NonNull CameraState cameraState;
|
||||||
|
@ -24,6 +27,7 @@ public final class CallParticipant {
|
||||||
private final long lastSpoke;
|
private final long lastSpoke;
|
||||||
private final boolean mediaKeysReceived;
|
private final boolean mediaKeysReceived;
|
||||||
private final long addedToCallTime;
|
private final long addedToCallTime;
|
||||||
|
private final @NonNull DeviceOrdinal deviceOrdinal;
|
||||||
|
|
||||||
public static @NonNull CallParticipant createLocal(@NonNull CameraState cameraState,
|
public static @NonNull CallParticipant createLocal(@NonNull CameraState cameraState,
|
||||||
@NonNull BroadcastVideoSink renderer,
|
@NonNull BroadcastVideoSink renderer,
|
||||||
|
@ -38,7 +42,8 @@ public final class CallParticipant {
|
||||||
microphoneEnabled,
|
microphoneEnabled,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
0);
|
0,
|
||||||
|
DeviceOrdinal.PRIMARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull CallParticipant createRemote(@NonNull CallParticipantId callParticipantId,
|
public static @NonNull CallParticipant createRemote(@NonNull CallParticipantId callParticipantId,
|
||||||
|
@ -49,9 +54,10 @@ public final class CallParticipant {
|
||||||
boolean videoEnabled,
|
boolean videoEnabled,
|
||||||
long lastSpoke,
|
long lastSpoke,
|
||||||
boolean mediaKeysReceived,
|
boolean mediaKeysReceived,
|
||||||
long addedToCallTime)
|
long addedToCallTime,
|
||||||
|
@NonNull DeviceOrdinal deviceOrdinal)
|
||||||
{
|
{
|
||||||
return new CallParticipant(callParticipantId, recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
|
return new CallParticipant(callParticipantId, recipient, identityKey, renderer, CameraState.UNKNOWN, videoEnabled, audioEnabled, lastSpoke, mediaKeysReceived, addedToCallTime, deviceOrdinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallParticipant(@NonNull CallParticipantId callParticipantId,
|
private CallParticipant(@NonNull CallParticipantId callParticipantId,
|
||||||
|
@ -63,7 +69,8 @@ public final class CallParticipant {
|
||||||
boolean microphoneEnabled,
|
boolean microphoneEnabled,
|
||||||
long lastSpoke,
|
long lastSpoke,
|
||||||
boolean mediaKeysReceived,
|
boolean mediaKeysReceived,
|
||||||
long addedToCallTime)
|
long addedToCallTime,
|
||||||
|
@NonNull DeviceOrdinal deviceOrdinal)
|
||||||
{
|
{
|
||||||
this.callParticipantId = callParticipantId;
|
this.callParticipantId = callParticipantId;
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
|
@ -75,14 +82,15 @@ public final class CallParticipant {
|
||||||
this.lastSpoke = lastSpoke;
|
this.lastSpoke = lastSpoke;
|
||||||
this.mediaKeysReceived = mediaKeysReceived;
|
this.mediaKeysReceived = mediaKeysReceived;
|
||||||
this.addedToCallTime = addedToCallTime;
|
this.addedToCallTime = addedToCallTime;
|
||||||
|
this.deviceOrdinal = deviceOrdinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull CallParticipant withIdentityKey(@NonNull IdentityKey identityKey) {
|
public @NonNull CallParticipant withIdentityKey(@NonNull IdentityKey identityKey) {
|
||||||
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
|
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime, deviceOrdinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull CallParticipant withVideoEnabled(boolean videoEnabled) {
|
public @NonNull CallParticipant withVideoEnabled(boolean videoEnabled) {
|
||||||
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime);
|
return new CallParticipant(callParticipantId, recipient, identityKey, videoSink, cameraState, videoEnabled, microphoneEnabled, lastSpoke, mediaKeysReceived, addedToCallTime, deviceOrdinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull CallParticipantId getCallParticipantId() {
|
public @NonNull CallParticipantId getCallParticipantId() {
|
||||||
|
@ -93,6 +101,18 @@ public final class CallParticipant {
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull String getRecipientDisplayName(@NonNull Context context) {
|
||||||
|
if (recipient.isSelf() && isPrimary()) {
|
||||||
|
return context.getString(R.string.CallParticipant__you);
|
||||||
|
} else if (recipient.isSelf()) {
|
||||||
|
return context.getString(R.string.CallParticipant__you_on_another_device);
|
||||||
|
} else if (isPrimary()) {
|
||||||
|
return recipient.getDisplayName(context);
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.CallParticipant__s_on_another_device, recipient.getDisplayName(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable IdentityKey getIdentityKey() {
|
public @Nullable IdentityKey getIdentityKey() {
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
@ -136,6 +156,10 @@ public final class CallParticipant {
|
||||||
return addedToCallTime;
|
return addedToCallTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPrimary() {
|
||||||
|
return deviceOrdinal == DeviceOrdinal.PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -172,4 +196,9 @@ public final class CallParticipant {
|
||||||
", addedToCallTime=" + addedToCallTime +
|
", addedToCallTime=" + addedToCallTime +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DeviceOrdinal {
|
||||||
|
PRIMARY,
|
||||||
|
SECONDARY
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
0
|
0,
|
||||||
|
CallParticipant.DeviceOrdinal.PRIMARY
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -93,7 +94,8 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
0
|
0,
|
||||||
|
CallParticipant.DeviceOrdinal.PRIMARY
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,11 @@ import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base group call action processor that handles general callbacks around call members
|
* Base group call action processor that handles general callbacks around call members
|
||||||
|
@ -74,8 +77,16 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||||
.changeCallInfoState()
|
.changeCallInfoState()
|
||||||
.clearParticipantMap();
|
.clearParticipantMap();
|
||||||
|
|
||||||
|
List<GroupCall.RemoteDeviceState> remoteDeviceStates = new ArrayList<>(remoteDevices.size());
|
||||||
for (int i = 0; i < remoteDevices.size(); i++) {
|
for (int i = 0; i < remoteDevices.size(); i++) {
|
||||||
GroupCall.RemoteDeviceState device = remoteDevices.get(remoteDevices.keyAt(i));
|
remoteDeviceStates.add(remoteDevices.get(remoteDevices.keyAt(i)));
|
||||||
|
}
|
||||||
|
Collections.sort(remoteDeviceStates, (a, b) -> Long.compare(a.getAddedTime(), b.getAddedTime()));
|
||||||
|
|
||||||
|
Set<Recipient> seen = new HashSet<>();
|
||||||
|
seen.add(Recipient.self());
|
||||||
|
|
||||||
|
for (GroupCall.RemoteDeviceState device : remoteDeviceStates) {
|
||||||
Recipient recipient = Recipient.externalPush(context, device.getUserId(), null, false);
|
Recipient recipient = Recipient.externalPush(context, device.getUserId(), null, false);
|
||||||
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
|
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
|
||||||
CallParticipant callParticipant = participants.get(callParticipantId);
|
CallParticipant callParticipant = participants.get(callParticipantId);
|
||||||
|
@ -99,7 +110,11 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||||
Boolean.FALSE.equals(device.getVideoMuted()),
|
Boolean.FALSE.equals(device.getVideoMuted()),
|
||||||
device.getSpeakerTime(),
|
device.getSpeakerTime(),
|
||||||
device.getMediaKeysReceived(),
|
device.getMediaKeysReceived(),
|
||||||
device.getAddedTime()));
|
device.getAddedTime(),
|
||||||
|
seen.contains(recipient) ? CallParticipant.DeviceOrdinal.SECONDARY
|
||||||
|
: CallParticipant.DeviceOrdinal.PRIMARY));
|
||||||
|
|
||||||
|
seen.add(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.remoteDevicesCount(remoteDevices.size());
|
builder.remoteDevicesCount(remoteDevices.size());
|
||||||
|
|
|
@ -122,7 +122,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
|
||||||
.clearParticipantMap();
|
.clearParticipantMap();
|
||||||
|
|
||||||
for (Recipient recipient : callParticipants) {
|
for (Recipient recipient : callParticipants) {
|
||||||
builder.putParticipant(recipient, CallParticipant.createRemote(new CallParticipantId(recipient), recipient, null, new BroadcastVideoSink(null), true, true, 0, false, 0));
|
builder.putParticipant(recipient, CallParticipant.createRemote(new CallParticipantId(recipient), recipient, null, new BroadcastVideoSink(null), true, true, 0, false, 0, CallParticipant.DeviceOrdinal.PRIMARY));
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
|
|
@ -3040,8 +3040,10 @@
|
||||||
<string name="CallParticipantsListUpdatePopupWindow__s_and_s_left">%1$s and %2$s left</string>
|
<string name="CallParticipantsListUpdatePopupWindow__s_and_s_left">%1$s and %2$s left</string>
|
||||||
<string name="CallParticipantsListUpdatePopupWindow__s_s_and_s_left">%1$s, %2$s and %3$s left</string>
|
<string name="CallParticipantsListUpdatePopupWindow__s_s_and_s_left">%1$s, %2$s and %3$s left</string>
|
||||||
<string name="CallParticipantsListUpdatePopupWindow__s_s_and_d_others_left">%1$s, %2$s and %3$d others left</string>
|
<string name="CallParticipantsListUpdatePopupWindow__s_s_and_d_others_left">%1$s, %2$s and %3$d others left</string>
|
||||||
<string name="CallParticipantsListUpdatePopupWindow__you_on_another_device">You (on another device)</string>
|
|
||||||
<string name="CallParticipantsListUpdatePopupWindow__s_on_another_device">%1$s (on another device)</string>
|
<string name="CallParticipant__you">You</string>
|
||||||
|
<string name="CallParticipant__you_on_another_device">You (on another device)</string>
|
||||||
|
<string name="CallParticipant__s_on_another_device">%1$s (on another device)</string>
|
||||||
|
|
||||||
<!-- DeleteAccountFragment -->
|
<!-- DeleteAccountFragment -->
|
||||||
<string name="DeleteAccountFragment__deleting_your_account_will">Deleting your account will:</string>
|
<string name="DeleteAccountFragment__deleting_your_account_will">Deleting your account will:</string>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
@ -27,9 +29,9 @@ public class CallParticipantListUpdateTest {
|
||||||
@Test
|
@Test
|
||||||
public void givenEmptySets_thenExpectNoChanges() {
|
public void givenEmptySets_thenExpectNoChanges() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
Set<CallParticipantListUpdate.Holder> added = Collections.emptySet();
|
Set<CallParticipantListUpdate.Wrapper> added = Collections.emptySet();
|
||||||
Set<CallParticipantListUpdate.Holder> removed = Collections.emptySet();
|
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertTrue(update.hasNoChanges());
|
assertTrue(update.hasNoChanges());
|
||||||
|
@ -39,9 +41,9 @@ public class CallParticipantListUpdateTest {
|
||||||
@Test
|
@Test
|
||||||
public void givenOneEmptySet_thenExpectMultipleChanges() {
|
public void givenOneEmptySet_thenExpectMultipleChanges() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
Set<CallParticipantListUpdate.Holder> added = new HashSet<>(Arrays.asList(createHolders(1, 2, 3)));
|
Set<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3)));
|
||||||
Set<CallParticipantListUpdate.Holder> removed = Collections.emptySet();
|
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(update.hasNoChanges());
|
assertFalse(update.hasNoChanges());
|
||||||
|
@ -51,9 +53,9 @@ public class CallParticipantListUpdateTest {
|
||||||
@Test
|
@Test
|
||||||
public void givenNoEmptySets_thenExpectMultipleChanges() {
|
public void givenNoEmptySets_thenExpectMultipleChanges() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
Set<CallParticipantListUpdate.Holder> added = new HashSet<>(Arrays.asList(createHolders(1, 2, 3)));
|
Set<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3)));
|
||||||
Set<CallParticipantListUpdate.Holder> removed = new HashSet<>(Arrays.asList(createHolders(4, 5, 6)));
|
Set<CallParticipantListUpdate.Wrapper> removed = new HashSet<>(Arrays.asList(createWrappers(4, 5, 6)));
|
||||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(update.hasNoChanges());
|
assertFalse(update.hasNoChanges());
|
||||||
|
@ -63,9 +65,9 @@ public class CallParticipantListUpdateTest {
|
||||||
@Test
|
@Test
|
||||||
public void givenOneSetWithSingleItemAndAnEmptySet_thenExpectSingleChange() {
|
public void givenOneSetWithSingleItemAndAnEmptySet_thenExpectSingleChange() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
Set<CallParticipantListUpdate.Holder> added = new HashSet<>(Arrays.asList(createHolders(1)));
|
Set<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1)));
|
||||||
Set<CallParticipantListUpdate.Holder> removed = Collections.emptySet();
|
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(update.hasNoChanges());
|
assertFalse(update.hasNoChanges());
|
||||||
|
@ -83,7 +85,7 @@ public class CallParticipantListUpdateTest {
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(update.hasNoChanges());
|
assertFalse(update.hasNoChanges());
|
||||||
assertTrue(update.getRemoved().isEmpty());
|
assertTrue(update.getRemoved().isEmpty());
|
||||||
assertThat(update.getAdded(), Matchers.containsInAnyOrder(createHolders(1, 2, 3, 4, 5)));
|
assertThat(update.getAdded(), Matchers.containsInAnyOrder(createWrappers(1, 2, 3, 4, 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -121,8 +123,8 @@ public class CallParticipantListUpdateTest {
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertFalse(update.hasNoChanges());
|
assertFalse(update.hasNoChanges());
|
||||||
assertThat(update.getAdded(), Matchers.containsInAnyOrder(createHolders(6)));
|
assertThat(update.getAdded(), Matchers.containsInAnyOrder(createWrappers(6)));
|
||||||
assertThat(update.getRemoved(), Matchers.containsInAnyOrder(createHolders(1)));
|
assertThat(update.getRemoved(), Matchers.containsInAnyOrder(createWrappers(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -134,17 +136,18 @@ public class CallParticipantListUpdateTest {
|
||||||
CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), list);
|
CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), list);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
List<Boolean> isPrimaryList = Stream.of(update.getAdded()).map(CallParticipantListUpdate.Holder::isPrimary).toList();
|
List<Boolean> isPrimaryList = Stream.of(update.getAdded()).map(wrapper -> wrapper.getCallParticipant().isPrimary()).toList();
|
||||||
assertThat(isPrimaryList, Matchers.containsInAnyOrder(true, false, false));
|
assertThat(isPrimaryList, Matchers.containsInAnyOrder(true, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
static CallParticipantListUpdate.Holder[] createHolders(long ... recipientIds) {
|
static CallParticipantListUpdate.Wrapper[] createWrappers(long ... recipientIds) {
|
||||||
CallParticipantListUpdate.Holder[] ids = new CallParticipantListUpdate.Holder[recipientIds.length];
|
CallParticipantListUpdate.Wrapper[] ids = new CallParticipantListUpdate.Wrapper[recipientIds.length];
|
||||||
|
Set<Long> primaries = new HashSet<>();
|
||||||
|
|
||||||
for (int i = 0; i < recipientIds.length; i++) {
|
for (int i = 0; i < recipientIds.length; i++) {
|
||||||
CallParticipant participant = createParticipant(recipientIds[i], recipientIds[i]);
|
CallParticipant participant = createParticipant(recipientIds[i], recipientIds[i], primaries.contains(recipientIds[i]) ? CallParticipant.DeviceOrdinal.SECONDARY : CallParticipant.DeviceOrdinal.PRIMARY);
|
||||||
|
|
||||||
ids[i] = CallParticipantListUpdate.createHolder(participant, true);
|
ids[i] = CallParticipantListUpdate.createWrapper(participant);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids;
|
return ids;
|
||||||
|
@ -162,17 +165,20 @@ public class CallParticipantListUpdateTest {
|
||||||
|
|
||||||
private static List<CallParticipant> createParticipants(long[] recipientIds, long[] placeholderIds) {
|
private static List<CallParticipant> createParticipants(long[] recipientIds, long[] placeholderIds) {
|
||||||
List<CallParticipant> participants = new ArrayList<>(recipientIds.length);
|
List<CallParticipant> participants = new ArrayList<>(recipientIds.length);
|
||||||
|
Set<Long> primaries = new HashSet<>();
|
||||||
|
|
||||||
for (int i = 0; i < recipientIds.length; i++) {
|
for (int i = 0; i < recipientIds.length; i++) {
|
||||||
participants.add(createParticipant(recipientIds[i], placeholderIds[i]));
|
participants.add(createParticipant(recipientIds[i], placeholderIds[i], primaries.contains(recipientIds[i]) ? CallParticipant.DeviceOrdinal.SECONDARY : CallParticipant.DeviceOrdinal.PRIMARY));
|
||||||
|
primaries.add(recipientIds[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CallParticipant createParticipant(long recipientId, long deMuxId) {
|
private static CallParticipant createParticipant(long recipientId, long deMuxId, @NonNull CallParticipant.DeviceOrdinal deviceOrdinal) {
|
||||||
Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true);
|
Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true);
|
||||||
|
|
||||||
return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(null), false, false, -1, false, 0);
|
return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(null), false, false, -1, false, 0, deviceOrdinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -213,6 +213,7 @@ public class ParticipantCollectionTest {
|
||||||
false,
|
false,
|
||||||
lastSpoke,
|
lastSpoke,
|
||||||
false,
|
false,
|
||||||
added);
|
added,
|
||||||
|
CallParticipant.DeviceOrdinal.PRIMARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue