Show call full UI when group call is full.

This commit is contained in:
Cody Henthorne
2020-12-07 16:17:39 -05:00
committed by GitHub
parent 13616b9820
commit c00b0727e3
14 changed files with 178 additions and 57 deletions

View File

@@ -34,7 +34,8 @@ public final class CallParticipantsState {
WebRtcLocalRenderState.GONE,
false,
false,
false);
false,
0);
private final WebRtcViewModel.State callState;
private final WebRtcViewModel.GroupCallState groupCallState;
@@ -45,6 +46,7 @@ public final class CallParticipantsState {
private final boolean isInPipMode;
private final boolean showVideoForOutgoing;
private final boolean isViewingFocusedParticipant;
private final long remoteDevicesCount;
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
@NonNull WebRtcViewModel.GroupCallState groupCallState,
@@ -54,7 +56,8 @@ public final class CallParticipantsState {
@NonNull WebRtcLocalRenderState localRenderState,
boolean isInPipMode,
boolean showVideoForOutgoing,
boolean isViewingFocusedParticipant)
boolean isViewingFocusedParticipant,
long remoteDevicesCount)
{
this.callState = callState;
this.groupCallState = groupCallState;
@@ -65,6 +68,7 @@ public final class CallParticipantsState {
this.isInPipMode = isInPipMode;
this.showVideoForOutgoing = showVideoForOutgoing;
this.isViewingFocusedParticipant = isViewingFocusedParticipant;
this.remoteDevicesCount = remoteDevicesCount;
}
public @NonNull WebRtcViewModel.State getCallState() {
@@ -154,6 +158,10 @@ public final class CallParticipantsState {
return Stream.of(getAllRemoteParticipants()).anyMatch(p -> p.getVideoSink().needsNewRequestingSize());
}
public long getRemoteDevicesCount() {
return remoteDevicesCount;
}
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState,
@NonNull WebRtcViewModel webRtcViewModel,
boolean enableVideo)
@@ -186,7 +194,8 @@ public final class CallParticipantsState {
localRenderState,
oldState.isInPipMode,
newShowVideoForOutgoing,
oldState.isViewingFocusedParticipant);
oldState.isViewingFocusedParticipant,
webRtcViewModel.getRemoteDevicesCount());
}
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, boolean isInPip) {
@@ -208,7 +217,8 @@ public final class CallParticipantsState {
localRenderState,
isInPip,
oldState.showVideoForOutgoing,
oldState.isViewingFocusedParticipant);
oldState.isViewingFocusedParticipant,
oldState.remoteDevicesCount);
}
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
@@ -230,7 +240,8 @@ public final class CallParticipantsState {
localRenderState,
oldState.isInPipMode,
oldState.showVideoForOutgoing,
selectedPage == SelectedPage.FOCUSED);
selectedPage == SelectedPage.FOCUSED,
oldState.remoteDevicesCount);
}
private static @NonNull WebRtcLocalRenderState determineLocalRenderMode(@NonNull CallParticipant localParticipant,

View File

@@ -96,6 +96,7 @@ public class WebRtcCallView extends FrameLayout {
private MaterialButton startCall;
private TextView participantCount;
private Stub<FrameLayout> groupCallSpeakerHint;
private Stub<View> groupCallFullStub;
private int pagerBottomMarginDp;
private boolean controlsVisible = true;
@@ -152,6 +153,7 @@ public class WebRtcCallView extends FrameLayout {
toolbar = findViewById(R.id.call_screen_toolbar);
startCall = findViewById(R.id.call_screen_start_call_start_call);
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
View topGradient = findViewById(R.id.call_screen_header_gradient);
View decline = findViewById(R.id.call_screen_decline_call);
@@ -269,7 +271,7 @@ public class WebRtcCallView extends FrameLayout {
if (state.getGroupCallState().isNotIdle() && participantCount != null) {
boolean includeSelf = state.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
participantCount.setText(String.valueOf(state.getAllRemoteParticipants().size() + (includeSelf ? 1 : 0)));
participantCount.setText(String.valueOf(state.getRemoteDevicesCount() + (includeSelf ? 1 : 0)));
}
pagerAdapter.submitList(pages);
@@ -421,7 +423,14 @@ public class WebRtcCallView extends FrameLayout {
visibleViewSet.add(startCallControls);
startCall.setText(webRtcControls.getStartCallButtonText());
startCall.setEnabled(true);
startCall.setEnabled(webRtcControls.isStartCallEnabled());
}
if (webRtcControls.displayGroupCallFull()) {
groupCallFullStub.get().setVisibility(View.VISIBLE);
((TextView) groupCallFullStub.get().findViewById(R.id.group_call_call_full_message)).setText(webRtcControls.getGroupCallFullMessage(getContext()));
} else if (groupCallFullStub.resolved()) {
groupCallFullStub.get().setVisibility(View.GONE);
}
MenuItem item = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list);

View File

@@ -5,6 +5,7 @@ import android.os.Looper;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
@@ -163,7 +164,9 @@ public class WebRtcCallViewModel extends ViewModel {
localParticipant.isMoreThanOneCameraAvailable(),
webRtcViewModel.isBluetoothAvailable(),
Util.hasItems(webRtcViewModel.getRemoteParticipants()),
repository.getAudioOutput());
repository.getAudioOutput(),
webRtcViewModel.getRemoteDevicesCount(),
webRtcViewModel.getParticipantLimit());
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
callConnectedTime = webRtcViewModel.getCallConnectedTime();
@@ -206,7 +209,9 @@ public class WebRtcCallViewModel extends ViewModel {
boolean isMoreThanOneCameraAvailable,
boolean isBluetoothAvailable,
boolean hasAtLeastOneRemote,
@NonNull WebRtcAudioOutput audioOutput)
@NonNull WebRtcAudioOutput audioOutput,
long remoteDevicesCount,
@Nullable Long participantLimit)
{
final WebRtcControls.CallState callState;
@@ -242,7 +247,8 @@ public class WebRtcCallViewModel extends ViewModel {
break;
case CONNECTING:
case RECONNECTING:
groupCallState = WebRtcControls.GroupCallState.CONNECTING;
groupCallState = (participantLimit == null || remoteDevicesCount < participantLimit) ? WebRtcControls.GroupCallState.CONNECTING
: WebRtcControls.GroupCallState.FULL;
break;
case CONNECTED:
case CONNECTED_AND_JOINING:
@@ -262,7 +268,8 @@ public class WebRtcCallViewModel extends ViewModel {
hasAtLeastOneRemote,
callState,
groupCallState,
audioOutput));
audioOutput,
participantLimit));
}
private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) {

View File

@@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.components.webrtc;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.R;
@@ -8,7 +11,7 @@ import org.thoughtcrime.securesms.R;
public final class WebRtcControls {
public static final WebRtcControls NONE = new WebRtcControls();
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET);
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
private final boolean isRemoteVideoEnabled;
private final boolean isLocalVideoEnabled;
@@ -19,9 +22,10 @@ public final class WebRtcControls {
private final CallState callState;
private final GroupCallState groupCallState;
private final WebRtcAudioOutput audioOutput;
private final Long participantLimit;
private WebRtcControls() {
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET);
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
}
WebRtcControls(boolean isLocalVideoEnabled,
@@ -32,7 +36,8 @@ public final class WebRtcControls {
boolean hasAtLeastOneRemote,
@NonNull CallState callState,
@NonNull GroupCallState groupCallState,
@NonNull WebRtcAudioOutput audioOutput)
@NonNull WebRtcAudioOutput audioOutput,
@Nullable Long participantLimit)
{
this.isLocalVideoEnabled = isLocalVideoEnabled;
this.isRemoteVideoEnabled = isRemoteVideoEnabled;
@@ -43,6 +48,7 @@ public final class WebRtcControls {
this.callState = callState;
this.groupCallState = groupCallState;
this.audioOutput = audioOutput;
this.participantLimit = participantLimit;
}
boolean displayStartCallControls() {
@@ -50,12 +56,31 @@ public final class WebRtcControls {
}
@StringRes int getStartCallButtonText() {
if (isGroupCall() && hasAtLeastOneRemote) {
return R.string.WebRtcCallView__join_call;
if (isGroupCall()) {
if (groupCallState == GroupCallState.FULL) {
return R.string.WebRtcCallView__call_is_full;
} else if (hasAtLeastOneRemote) {
return R.string.WebRtcCallView__join_call;
}
}
return R.string.WebRtcCallView__start_call;
}
boolean isStartCallEnabled() {
return groupCallState != GroupCallState.FULL;
}
boolean displayGroupCallFull() {
return groupCallState == GroupCallState.FULL;
}
@NonNull String getGroupCallFullMessage(@NonNull Context context) {
if (participantLimit != null) {
return context.getString(R.string.WebRtcCallView__the_maximum_number_of_d_participants_has_been_Reached_for_this_call, participantLimit);
}
return "";
}
boolean displayGroupMembersButton() {
return groupCallState.isAtLeast(GroupCallState.CONNECTING);
}
@@ -158,6 +183,7 @@ public final class WebRtcControls {
DISCONNECTED,
RECONNECTING,
CONNECTING,
FULL,
CONNECTED;
boolean isAtLeast(@SuppressWarnings("SameParameterValue") @NonNull GroupCallState other) {

View File

@@ -90,7 +90,7 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
boolean includeSelf = callParticipantsState.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
items.add(new CallParticipantsListHeader(callParticipantsState.getAllRemoteParticipants().size() + (includeSelf ? 1 : 0)));
items.add(new CallParticipantsListHeader((int) callParticipantsState.getRemoteDevicesCount() + (includeSelf ? 1 : 0)));
if (includeSelf) {
items.add(new CallParticipantViewState(callParticipantsState.getLocalParticipant()));

View File

@@ -8,7 +8,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import java.util.List;
import java.util.Set;
@@ -94,29 +94,24 @@ public class WebRtcViewModel {
private final CallParticipant localParticipant;
private final List<CallParticipant> remoteParticipants;
private final Set<RecipientId> identityChangedRecipients;
private final long remoteDevicesCount;
private final Long participantLimit;
public WebRtcViewModel(@NonNull State state,
@NonNull GroupCallState groupState,
@NonNull Recipient recipient,
@NonNull CameraState localCameraState,
@Nullable BroadcastVideoSink localSink,
boolean isBluetoothAvailable,
boolean isMicrophoneEnabled,
boolean isRemoteVideoOffer,
long callConnectedTime,
@NonNull List<CallParticipant> remoteParticipants,
@NonNull Set<RecipientId> identityChangedRecipients)
{
this.state = state;
this.groupState = groupState;
this.recipient = recipient;
this.isBluetoothAvailable = isBluetoothAvailable;
this.isRemoteVideoOffer = isRemoteVideoOffer;
this.callConnectedTime = callConnectedTime;
this.remoteParticipants = remoteParticipants;
this.identityChangedRecipients = identityChangedRecipients;
localParticipant = CallParticipant.createLocal(localCameraState, localSink != null ? localSink : new BroadcastVideoSink(null), isMicrophoneEnabled);
public WebRtcViewModel(@NonNull WebRtcServiceState state) {
this.state = state.getCallInfoState().getCallState();
this.groupState = state.getCallInfoState().getGroupCallState();
this.recipient = state.getCallInfoState().getCallRecipient();
this.isRemoteVideoOffer = state.getCallSetupState().isRemoteVideoOffer();
this.isBluetoothAvailable = state.getLocalDeviceState().isBluetoothAvailable();
this.remoteParticipants = state.getCallInfoState().getRemoteCallParticipants();
this.identityChangedRecipients = state.getCallInfoState().getIdentityChangedRecipients();
this.callConnectedTime = state.getCallInfoState().getCallConnectedTime();
this.remoteDevicesCount = state.getCallInfoState().getRemoteDevicesCount();
this.participantLimit = state.getCallInfoState().getParticipantLimit();
this.localParticipant = CallParticipant.createLocal(state.getLocalDeviceState().getCameraState(),
state.getVideoState().getLocalSink() != null ? state.getVideoState().getLocalSink()
: new BroadcastVideoSink(null),
state.getLocalDeviceState().isMicrophoneEnabled());
}
public @NonNull State getState() {
@@ -159,6 +154,14 @@ public class WebRtcViewModel {
return identityChangedRecipients;
}
public long getRemoteDevicesCount() {
return remoteDevicesCount;
}
public @Nullable Long getParticipantLimit() {
return participantLimit;
}
@Override
public @NonNull String toString() {
return "WebRtcViewModel{" +
@@ -170,6 +173,8 @@ public class WebRtcViewModel {
", localParticipant=" + localParticipant +
", remoteParticipants=" + remoteParticipants +
", identityChangedRecipients=" + identityChangedRecipients +
", remoteDevicesCount=" + remoteDevicesCount +
", participantLimit=" + participantLimit +
'}';
}
}

View File

@@ -430,17 +430,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
}
public void sendMessage(@NonNull WebRtcServiceState state) {
EventBus.getDefault().postSticky(new WebRtcViewModel(state.getCallInfoState().getCallState(),
state.getCallInfoState().getGroupCallState(),
state.getCallInfoState().getCallRecipient(),
state.getLocalDeviceState().getCameraState(),
state.getVideoState().getLocalSink(),
state.getLocalDeviceState().isBluetoothAvailable(),
state.getLocalDeviceState().isMicrophoneEnabled(),
state.getCallSetupState().isRemoteVideoOffer(),
state.getCallInfoState().getCallConnectedTime(),
state.getCallInfoState().getRemoteCallParticipants(),
state.getCallInfoState().getIdentityChangedRecipients()));
EventBus.getDefault().postSticky(new WebRtcViewModel(state));
}
private @NonNull ListenableFutureTask<Boolean> sendMessage(@NonNull final RemotePeer remotePeer,

View File

@@ -62,12 +62,17 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
LongSparseArray<GroupCall.RemoteDeviceState> remoteDevices = groupCall.getRemoteDeviceStates();
if (remoteDevices == null) {
Log.w(tag, "Unable to update remote devices with null list.");
return currentState;
}
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder()
.changeCallInfoState()
.clearParticipantMap();
LongSparseArray<GroupCall.RemoteDeviceState> remoteDevices = groupCall.getRemoteDeviceStates();
for (int i = 0; i < remoteDevices.size(); i++) {
GroupCall.RemoteDeviceState device = remoteDevices.get(remoteDevices.keyAt(i));
Recipient recipient = Recipient.externalPush(context, device.getUserId(), null, false);
@@ -96,6 +101,8 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
device.getAddedTime()));
}
builder.remoteDevicesCount(remoteDevices.size());
return builder.build();
}

View File

@@ -112,7 +112,9 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
.toList();
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder()
.changeCallInfoState();
.changeCallInfoState()
.remoteDevicesCount(peekInfo.getDeviceCount())
.participantLimit(peekInfo.getMaxDevices());
for (Recipient recipient : callParticipants) {
builder.putParticipant(recipient, CallParticipant.createRemote(new CallParticipantId(recipient), recipient, null, new BroadcastVideoSink(null), true, true, 0, false, 0));

View File

@@ -35,6 +35,8 @@ public class CallInfoState {
GroupCall groupCall;
WebRtcViewModel.GroupCallState groupState;
Set<RecipientId> identityChangedRecipients;
long remoteDevicesCount;
Long participantLimit;
public CallInfoState() {
this(WebRtcViewModel.State.IDLE,
@@ -45,7 +47,9 @@ public class CallInfoState {
null,
null,
WebRtcViewModel.GroupCallState.IDLE,
Collections.emptySet());
Collections.emptySet(),
0L,
null);
}
public CallInfoState(@NonNull CallInfoState toCopy) {
@@ -57,7 +61,9 @@ public class CallInfoState {
toCopy.activePeer,
toCopy.groupCall,
toCopy.groupState,
toCopy.identityChangedRecipients);
toCopy.identityChangedRecipients,
toCopy.remoteDevicesCount,
toCopy.participantLimit);
}
public CallInfoState(@NonNull WebRtcViewModel.State callState,
@@ -68,7 +74,9 @@ public class CallInfoState {
@Nullable RemotePeer activePeer,
@Nullable GroupCall groupCall,
@NonNull WebRtcViewModel.GroupCallState groupState,
@NonNull Set<RecipientId> identityChangedRecipients)
@NonNull Set<RecipientId> identityChangedRecipients,
long remoteDevicesCount,
@Nullable Long participantLimit)
{
this.callState = callState;
this.callRecipient = callRecipient;
@@ -79,6 +87,8 @@ public class CallInfoState {
this.groupCall = groupCall;
this.groupState = groupState;
this.identityChangedRecipients = new HashSet<>(identityChangedRecipients);
this.remoteDevicesCount = remoteDevicesCount;
this.participantLimit = participantLimit;
}
public @NonNull Recipient getCallRecipient() {
@@ -136,4 +146,12 @@ public class CallInfoState {
public @NonNull Set<RecipientId> getIdentityChangedRecipients() {
return identityChangedRecipients;
}
public long getRemoteDevicesCount() {
return remoteDevicesCount;
}
public @Nullable Long getParticipantLimit() {
return participantLimit;
}
}

View File

@@ -256,5 +256,15 @@ public class WebRtcServiceStateBuilder {
toBuild.identityChangedRecipients.removeAll(ids);
return this;
}
public @NonNull CallInfoStateBuilder remoteDevicesCount(long remoteDevicesCount) {
toBuild.remoteDevicesCount = remoteDevicesCount;
return this;
}
public @NonNull CallInfoStateBuilder participantLimit(@Nullable Long participantLimit) {
toBuild.participantLimit = participantLimit;
return this;
}
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/transparent_black_40"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/TextAppearance.Signal.Body1.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/WebRtcCallView__call_is_full"
android:gravity="center"
android:textColor="@color/core_white" />
<TextView
android:id="@+id/group_call_call_full_message"
style="@style/TextAppearance.Signal.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="The maximum number of 16 participants has been reached for this call. Try again later."
android:gravity="center"
android:textColor="@color/core_white" />
</LinearLayout>

View File

@@ -26,6 +26,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewStub
android:id="@+id/group_call_call_full_view"
android:layout="@layout/group_call_call_full"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/call_screen"
android:layout_width="match_parent"

View File

@@ -1413,6 +1413,8 @@
<string name="WebRtcCallView__signal_video_call">Signal video call…</string>
<string name="WebRtcCallView__start_call">Start Call</string>
<string name="WebRtcCallView__join_call">Join Call</string>
<string name="WebRtcCallView__call_is_full">Call is full</string>
<string name="WebRtcCallView__the_maximum_number_of_d_participants_has_been_Reached_for_this_call">The maximum number of %1$d participants has been reached for this call. Try again later.</string>
<string name="WebRtcCallView__s_group_call">\"%1$s\" Group Call</string>
<string name="WebRtcCallView__view_participants_list">View participants</string>
<string name="WebRtcCallView__your_video_is_off">Your video is off</string>