Add initial support for Group Calling.
This commit is contained in:
parent
696fffb603
commit
b1f6786392
53 changed files with 1887 additions and 130 deletions
|
@ -207,6 +207,7 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
callScreen = findViewById(R.id.callScreen);
|
callScreen = findViewById(R.id.callScreen);
|
||||||
callScreen.setControlsListener(new ControlsListener());
|
callScreen.setControlsListener(new ControlsListener());
|
||||||
|
callScreen.setEventListener(new EventListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeViewModel() {
|
private void initializeViewModel() {
|
||||||
|
@ -381,8 +382,12 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
startService(intent);
|
startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOutgoingCall() {
|
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
|
||||||
callScreen.setStatus(getString(R.string.WebRtcCallActivity__calling));
|
if (event.getGroupState().isNotIdle()) {
|
||||||
|
callScreen.setStatusFromGroupCallState(event.getGroupState());
|
||||||
|
} else {
|
||||||
|
callScreen.setStatus(getString(R.string.WebRtcCallActivity__calling));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTerminate(@NonNull Recipient recipient, @NonNull HangupMessage.Type hangupType) {
|
private void handleTerminate(@NonNull Recipient recipient, @NonNull HangupMessage.Type hangupType) {
|
||||||
|
@ -408,8 +413,11 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH);
|
delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCallConnected() {
|
private void handleCallConnected(@NonNull WebRtcViewModel event) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
|
||||||
|
if (event.getGroupState().isNotIdleOrConnected()) {
|
||||||
|
callScreen.setStatusFromGroupCallState(event.getGroupState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRecipientUnavailable() {
|
private void handleRecipientUnavailable() {
|
||||||
|
@ -486,7 +494,8 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
callScreen.setRecipient(event.getRecipient());
|
callScreen.setRecipient(event.getRecipient());
|
||||||
|
|
||||||
switch (event.getState()) {
|
switch (event.getState()) {
|
||||||
case CALL_CONNECTED: handleCallConnected(); break;
|
case CALL_PRE_JOIN: handleCallPreJoin(event); break;
|
||||||
|
case CALL_CONNECTED: handleCallConnected(event); break;
|
||||||
case NETWORK_FAILURE: handleServerFailure(); break;
|
case NETWORK_FAILURE: handleServerFailure(); break;
|
||||||
case CALL_RINGING: handleCallRinging(); break;
|
case CALL_RINGING: handleCallRinging(); break;
|
||||||
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
|
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
|
||||||
|
@ -496,7 +505,7 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
|
case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
|
||||||
case NO_SUCH_USER: handleNoSuchUser(event); break;
|
case NO_SUCH_USER: handleNoSuchUser(event); break;
|
||||||
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(); break;
|
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(); break;
|
||||||
case CALL_OUTGOING: handleOutgoingCall(); break;
|
case CALL_OUTGOING: handleOutgoingCall(event); break;
|
||||||
case CALL_BUSY: handleCallBusy(); break;
|
case CALL_BUSY: handleCallBusy(); break;
|
||||||
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
|
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
|
||||||
}
|
}
|
||||||
|
@ -511,6 +520,12 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCallPreJoin(@NonNull WebRtcViewModel event) {
|
||||||
|
if (event.getGroupState().isNotIdle()) {
|
||||||
|
callScreen.setStatusFromGroupCallState(event.getGroupState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class ControlsListener implements WebRtcCallView.ControlsListener {
|
private final class ControlsListener implements WebRtcCallView.ControlsListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -605,4 +620,12 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class EventListener implements WebRtcCallView.EventListener {
|
||||||
|
@Override
|
||||||
|
public void onPotentialLayoutChange() {
|
||||||
|
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS);
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,91 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
|
import android.graphics.Point;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.VideoFrame;
|
import org.webrtc.VideoFrame;
|
||||||
import org.webrtc.VideoSink;
|
import org.webrtc.VideoSink;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
public class BroadcastVideoSink implements VideoSink {
|
public class BroadcastVideoSink implements VideoSink {
|
||||||
|
|
||||||
private final EglBase eglBase;
|
private final EglBase eglBase;
|
||||||
private final WeakHashMap<VideoSink, Boolean> sinks;
|
private final WeakHashMap<VideoSink, Boolean> sinks;
|
||||||
|
private final WeakHashMap<Object, Point> requestingSizes;
|
||||||
|
|
||||||
public BroadcastVideoSink(@Nullable EglBase eglBase) {
|
public BroadcastVideoSink(@Nullable EglBase eglBase) {
|
||||||
this.eglBase = eglBase;
|
this.eglBase = eglBase;
|
||||||
this.sinks = new WeakHashMap<>();
|
this.sinks = new WeakHashMap<>();
|
||||||
|
this.requestingSizes = new WeakHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable EglBase getEglBase() {
|
public @Nullable EglBase getEglBase() {
|
||||||
return eglBase;
|
return eglBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSink(@NonNull VideoSink sink) {
|
public synchronized void addSink(@NonNull VideoSink sink) {
|
||||||
sinks.put(sink, true);
|
sinks.put(sink, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeSink(@NonNull VideoSink sink) {
|
public synchronized void removeSink(@NonNull VideoSink sink) {
|
||||||
sinks.remove(sink);
|
sinks.remove(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFrame(@NonNull VideoFrame videoFrame) {
|
public synchronized void onFrame(@NonNull VideoFrame videoFrame) {
|
||||||
for (VideoSink sink : sinks.keySet()) {
|
for (VideoSink sink : sinks.keySet()) {
|
||||||
sink.onFrame(videoFrame);
|
sink.onFrame(videoFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void putRequestingSize(@NonNull Object object, @NonNull Point size) {
|
||||||
|
synchronized (requestingSizes) {
|
||||||
|
requestingSizes.put(object, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeRequestingSize(@NonNull Object object) {
|
||||||
|
synchronized (requestingSizes) {
|
||||||
|
requestingSizes.remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull RequestedSize getMaxRequestingSize() {
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
synchronized (requestingSizes) {
|
||||||
|
for (Point size : requestingSizes.values()) {
|
||||||
|
if (width < size.x) {
|
||||||
|
width = size.x;
|
||||||
|
height = size.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RequestedSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestedSize {
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
private RequestedSize(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.webrtc;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -21,6 +22,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -32,6 +34,9 @@ public class CallParticipantView extends ConstraintLayout {
|
||||||
|
|
||||||
private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
|
private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
|
||||||
|
|
||||||
|
private static final int SMALL_AVATAR = ViewUtil.dpToPx(96);
|
||||||
|
private static final int LARGE_AVATAR = ViewUtil.dpToPx(112);
|
||||||
|
|
||||||
private RecipientId recipientId;
|
private RecipientId recipientId;
|
||||||
private AvatarImageView avatar;
|
private AvatarImageView avatar;
|
||||||
private TextureViewRenderer renderer;
|
private TextureViewRenderer renderer;
|
||||||
|
@ -59,6 +64,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||||
renderer = findViewById(R.id.call_participant_renderer);
|
renderer = findViewById(R.id.call_participant_renderer);
|
||||||
|
|
||||||
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
|
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
|
||||||
|
useLargeAvatar();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCallParticipant(@NonNull CallParticipant participant) {
|
void setCallParticipant(@NonNull CallParticipant participant) {
|
||||||
|
@ -89,6 +95,23 @@ public class CallParticipantView extends ConstraintLayout {
|
||||||
pipAvatar.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
|
pipAvatar.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void useLargeAvatar() {
|
||||||
|
changeAvatarParams(LARGE_AVATAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void useSmallAvatar() {
|
||||||
|
changeAvatarParams(SMALL_AVATAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeAvatarParams(int dimension) {
|
||||||
|
ViewGroup.LayoutParams params = avatar.getLayoutParams();
|
||||||
|
if (params.height != dimension) {
|
||||||
|
params.height = dimension;
|
||||||
|
params.width = dimension;
|
||||||
|
avatar.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setPipAvatar(@NonNull Recipient recipient) {
|
private void setPipAvatar(@NonNull Recipient recipient) {
|
||||||
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
||||||
FallbackContactPhoto fallbackPhoto = recipient.getFallbackContactPhoto(FALLBACK_PHOTO_PROVIDER);
|
FallbackContactPhoto fallbackPhoto = recipient.getFallbackContactPhoto(FALLBACK_PHOTO_PROVIDER);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
|
|
||||||
import com.google.android.flexbox.AlignItems;
|
import com.google.android.flexbox.AlignItems;
|
||||||
import com.google.android.flexbox.FlexboxLayout;
|
import com.google.android.flexbox.FlexboxLayout;
|
||||||
|
@ -14,6 +15,7 @@ import com.google.android.flexbox.FlexboxLayout;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -24,6 +26,9 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class CallParticipantsLayout extends FlexboxLayout {
|
public class CallParticipantsLayout extends FlexboxLayout {
|
||||||
|
|
||||||
|
private static final int MULTIPLE_PARTICIPANT_SPACING = ViewUtil.dpToPx(3);
|
||||||
|
private static final int CORNER_RADIUS = ViewUtil.dpToPx(10);
|
||||||
|
|
||||||
private List<CallParticipant> callParticipants = Collections.emptyList();
|
private List<CallParticipant> callParticipants = Collections.emptyList();
|
||||||
private boolean shouldRenderInPip;
|
private boolean shouldRenderInPip;
|
||||||
|
|
||||||
|
@ -46,17 +51,33 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLayout() {
|
private void updateLayout() {
|
||||||
|
int previousChildCount = getChildCount();
|
||||||
|
|
||||||
if (shouldRenderInPip && Util.hasItems(callParticipants)) {
|
if (shouldRenderInPip && Util.hasItems(callParticipants)) {
|
||||||
updateChildrenCount(1);
|
updateChildrenCount(1);
|
||||||
update(0, callParticipants.get(0));
|
update(0, 1, callParticipants.get(0));
|
||||||
} else {
|
} else {
|
||||||
int count = callParticipants.size();
|
int count = callParticipants.size();
|
||||||
updateChildrenCount(count);
|
updateChildrenCount(count);
|
||||||
|
|
||||||
for (int i = 0; i < callParticipants.size(); i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
update(i, callParticipants.get(i));
|
update(i, count, callParticipants.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (previousChildCount != getChildCount()) {
|
||||||
|
updateMarginsForLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMarginsForLayout() {
|
||||||
|
MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
|
||||||
|
if (callParticipants.size() > 1 && !shouldRenderInPip) {
|
||||||
|
layoutParams.setMargins(MULTIPLE_PARTICIPANT_SPACING, ViewUtil.getStatusBarHeight(this), MULTIPLE_PARTICIPANT_SPACING, 0);
|
||||||
|
} else {
|
||||||
|
layoutParams.setMargins(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
setLayoutParams(layoutParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChildrenCount(int count) {
|
private void updateChildrenCount(int count) {
|
||||||
|
@ -72,15 +93,33 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(int index, @NonNull CallParticipant participant) {
|
private void update(int index, int count, @NonNull CallParticipant participant) {
|
||||||
CallParticipantView callParticipantView = (CallParticipantView) getChildAt(index);
|
View view = getChildAt(index);
|
||||||
|
CardView cardView = view.findViewById(R.id.group_call_participant_card_wrapper);
|
||||||
|
CallParticipantView callParticipantView = view.findViewById(R.id.group_call_participant);
|
||||||
|
|
||||||
callParticipantView.setCallParticipant(participant);
|
callParticipantView.setCallParticipant(participant);
|
||||||
callParticipantView.setRenderInPip(shouldRenderInPip);
|
callParticipantView.setRenderInPip(shouldRenderInPip);
|
||||||
setChildLayoutParams(callParticipantView, index, getChildCount());
|
|
||||||
|
if (count > 1) {
|
||||||
|
view.setPadding(MULTIPLE_PARTICIPANT_SPACING, MULTIPLE_PARTICIPANT_SPACING, MULTIPLE_PARTICIPANT_SPACING, MULTIPLE_PARTICIPANT_SPACING);
|
||||||
|
cardView.setRadius(CORNER_RADIUS);
|
||||||
|
} else {
|
||||||
|
view.setPadding(0, 0, 0, 0);
|
||||||
|
cardView.setRadius(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 2) {
|
||||||
|
callParticipantView.useSmallAvatar();
|
||||||
|
} else {
|
||||||
|
callParticipantView.useLargeAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
setChildLayoutParams(view, index, getChildCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCallParticipantView() {
|
private void addCallParticipantView() {
|
||||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.call_participant_item, this, false);
|
View view = LayoutInflater.from(getContext()).inflate(R.layout.group_call_participant_item, this, false);
|
||||||
FlexboxLayout.LayoutParams params = (FlexboxLayout.LayoutParams) view.getLayoutParams();
|
FlexboxLayout.LayoutParams params = (FlexboxLayout.LayoutParams) view.getLayoutParams();
|
||||||
|
|
||||||
params.setAlignSelf(AlignItems.STRETCH);
|
params.setAlignSelf(AlignItems.STRETCH);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
|
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.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||||
|
@ -21,24 +24,27 @@ public final class CallParticipantsState {
|
||||||
private static final int SMALL_GROUP_MAX = 6;
|
private static final int SMALL_GROUP_MAX = 6;
|
||||||
|
|
||||||
public static final CallParticipantsState STARTING_STATE = new CallParticipantsState(WebRtcViewModel.State.CALL_DISCONNECTED,
|
public static final CallParticipantsState STARTING_STATE = new CallParticipantsState(WebRtcViewModel.State.CALL_DISCONNECTED,
|
||||||
Collections.emptyList(),
|
WebRtcViewModel.GroupCallState.IDLE,
|
||||||
CallParticipant.createLocal(CameraState.UNKNOWN, new BroadcastVideoSink(null), false),
|
Collections.emptyList(),
|
||||||
null,
|
CallParticipant.createLocal(CameraState.UNKNOWN, new BroadcastVideoSink(null), false),
|
||||||
WebRtcLocalRenderState.GONE,
|
null,
|
||||||
false,
|
WebRtcLocalRenderState.GONE,
|
||||||
false,
|
false,
|
||||||
false);
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
private final WebRtcViewModel.State callState;
|
private final WebRtcViewModel.State callState;
|
||||||
private final List<CallParticipant> remoteParticipants;
|
private final WebRtcViewModel.GroupCallState groupCallState;
|
||||||
private final CallParticipant localParticipant;
|
private final List<CallParticipant> remoteParticipants;
|
||||||
private final CallParticipant focusedParticipant;
|
private final CallParticipant localParticipant;
|
||||||
private final WebRtcLocalRenderState localRenderState;
|
private final CallParticipant focusedParticipant;
|
||||||
private final boolean isInPipMode;
|
private final WebRtcLocalRenderState localRenderState;
|
||||||
private final boolean showVideoForOutgoing;
|
private final boolean isInPipMode;
|
||||||
private final boolean isViewingFocusedParticipant;
|
private final boolean showVideoForOutgoing;
|
||||||
|
private final boolean isViewingFocusedParticipant;
|
||||||
|
|
||||||
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
|
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
|
||||||
|
@NonNull WebRtcViewModel.GroupCallState groupCallState,
|
||||||
@NonNull List<CallParticipant> remoteParticipants,
|
@NonNull List<CallParticipant> remoteParticipants,
|
||||||
@NonNull CallParticipant localParticipant,
|
@NonNull CallParticipant localParticipant,
|
||||||
@Nullable CallParticipant focusedParticipant,
|
@Nullable CallParticipant focusedParticipant,
|
||||||
|
@ -48,6 +54,7 @@ public final class CallParticipantsState {
|
||||||
boolean isViewingFocusedParticipant)
|
boolean isViewingFocusedParticipant)
|
||||||
{
|
{
|
||||||
this.callState = callState;
|
this.callState = callState;
|
||||||
|
this.groupCallState = groupCallState;
|
||||||
this.remoteParticipants = remoteParticipants;
|
this.remoteParticipants = remoteParticipants;
|
||||||
this.localParticipant = localParticipant;
|
this.localParticipant = localParticipant;
|
||||||
this.localRenderState = localRenderState;
|
this.localRenderState = localRenderState;
|
||||||
|
@ -61,6 +68,10 @@ public final class CallParticipantsState {
|
||||||
return callState;
|
return callState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull WebRtcViewModel.GroupCallState getGroupCallState() {
|
||||||
|
return groupCallState;
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull List<CallParticipant> getGridParticipants() {
|
public @NonNull List<CallParticipant> getGridParticipants() {
|
||||||
if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
|
if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
|
||||||
return getAllRemoteParticipants().subList(0, SMALL_GROUP_MAX);
|
return getAllRemoteParticipants().subList(0, SMALL_GROUP_MAX);
|
||||||
|
@ -87,6 +98,30 @@ public final class CallParticipantsState {
|
||||||
return listParticipants;
|
return listParticipants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull String getRemoteParticipantsDescription(@NonNull Context context) {
|
||||||
|
switch (remoteParticipants.size()) {
|
||||||
|
case 0:
|
||||||
|
return context.getString(R.string.WebRtcCallView__no_one_else_is_here);
|
||||||
|
case 1:
|
||||||
|
if (callState == WebRtcViewModel.State.CALL_PRE_JOIN && groupCallState.isNotIdle()) {
|
||||||
|
return context.getString(R.string.WebRtcCallView__s_is_in_this_call, remoteParticipants.get(0).getRecipient().getShortDisplayName(context));
|
||||||
|
} else {
|
||||||
|
return remoteParticipants.get(0).getRecipient().getDisplayName(context);
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return context.getString(R.string.WebRtcCallView__s_and_s_are_in_this_call,
|
||||||
|
remoteParticipants.get(0).getRecipient().getShortDisplayName(context),
|
||||||
|
remoteParticipants.get(1).getRecipient().getShortDisplayName(context));
|
||||||
|
default:
|
||||||
|
int others = remoteParticipants.size() - 2;
|
||||||
|
return context.getResources().getQuantityString(R.plurals.WebRtcCallView__s_s_and_d_others_are_in_this_call,
|
||||||
|
others,
|
||||||
|
remoteParticipants.get(0).getRecipient().getShortDisplayName(context),
|
||||||
|
remoteParticipants.get(1).getRecipient().getShortDisplayName(context),
|
||||||
|
others);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull List<CallParticipant> getAllRemoteParticipants() {
|
public @NonNull List<CallParticipant> getAllRemoteParticipants() {
|
||||||
return remoteParticipants;
|
return remoteParticipants;
|
||||||
}
|
}
|
||||||
|
@ -132,6 +167,7 @@ public final class CallParticipantsState {
|
||||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||||
|
|
||||||
return new CallParticipantsState(webRtcViewModel.getState(),
|
return new CallParticipantsState(webRtcViewModel.getState(),
|
||||||
|
webRtcViewModel.getGroupState(),
|
||||||
webRtcViewModel.getRemoteParticipants(),
|
webRtcViewModel.getRemoteParticipants(),
|
||||||
webRtcViewModel.getLocalParticipant(),
|
webRtcViewModel.getLocalParticipant(),
|
||||||
focused,
|
focused,
|
||||||
|
@ -152,6 +188,7 @@ public final class CallParticipantsState {
|
||||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||||
|
|
||||||
return new CallParticipantsState(oldState.callState,
|
return new CallParticipantsState(oldState.callState,
|
||||||
|
oldState.groupCallState,
|
||||||
oldState.remoteParticipants,
|
oldState.remoteParticipants,
|
||||||
oldState.localParticipant,
|
oldState.localParticipant,
|
||||||
focused,
|
focused,
|
||||||
|
@ -172,6 +209,7 @@ public final class CallParticipantsState {
|
||||||
selectedPage == SelectedPage.FOCUSED);
|
selectedPage == SelectedPage.FOCUSED);
|
||||||
|
|
||||||
return new CallParticipantsState(oldState.callState,
|
return new CallParticipantsState(oldState.callState,
|
||||||
|
oldState.groupCallState,
|
||||||
oldState.remoteParticipants,
|
oldState.remoteParticipants,
|
||||||
oldState.localParticipant,
|
oldState.localParticipant,
|
||||||
focused,
|
focused,
|
||||||
|
@ -193,8 +231,8 @@ public final class CallParticipantsState {
|
||||||
|
|
||||||
if (displayLocal || showVideoForOutgoing) {
|
if (displayLocal || showVideoForOutgoing) {
|
||||||
if (callState == WebRtcViewModel.State.CALL_CONNECTED) {
|
if (callState == WebRtcViewModel.State.CALL_CONNECTED) {
|
||||||
if (isViewingFocusedParticipant || numberOfRemoteParticipants > 3) {
|
if (isViewingFocusedParticipant || numberOfRemoteParticipants > 1) {
|
||||||
localRenderState = WebRtcLocalRenderState.SMALL_SQUARE;
|
localRenderState = WebRtcLocalRenderState.SMALLER_RECTANGLE;
|
||||||
} else {
|
} else {
|
||||||
localRenderState = WebRtcLocalRenderState.SMALL_RECTANGLE;
|
localRenderState = WebRtcLocalRenderState.SMALL_RECTANGLE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,12 @@ import android.view.TextureView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.EglRenderer;
|
import org.webrtc.EglRenderer;
|
||||||
import org.webrtc.GlRectDrawer;
|
import org.webrtc.GlRectDrawer;
|
||||||
|
@ -38,6 +42,7 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
private int surfaceHeight;
|
private int surfaceHeight;
|
||||||
private boolean isInitialized;
|
private boolean isInitialized;
|
||||||
private BroadcastVideoSink attachedVideoSink;
|
private BroadcastVideoSink attachedVideoSink;
|
||||||
|
private Lifecycle lifecycle;
|
||||||
|
|
||||||
public TextureViewRenderer(@NonNull Context context) {
|
public TextureViewRenderer(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -59,7 +64,7 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
this.init(eglBase.getEglBaseContext(), null, EglBase.CONFIG_PLAIN, new GlRectDrawer());
|
this.init(eglBase.getEglBaseContext(), null, EglBase.CONFIG_PLAIN, new GlRectDrawer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(@NonNull EglBase.Context sharedContext, @NonNull RendererCommon.RendererEvents rendererEvents, @NonNull int[] configAttributes, @NonNull RendererCommon.GlDrawer drawer) {
|
public void init(@NonNull EglBase.Context sharedContext, @Nullable RendererCommon.RendererEvents rendererEvents, @NonNull int[] configAttributes, @NonNull RendererCommon.GlDrawer drawer) {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
|
|
||||||
this.rendererEvents = rendererEvents;
|
this.rendererEvents = rendererEvents;
|
||||||
|
@ -67,6 +72,16 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
this.rotatedFrameHeight = 0;
|
this.rotatedFrameHeight = 0;
|
||||||
|
|
||||||
this.eglRenderer.init(sharedContext, this, configAttributes, drawer);
|
this.eglRenderer.init(sharedContext, this, configAttributes, drawer);
|
||||||
|
|
||||||
|
this.lifecycle = ViewUtil.getActivityLifecycle(this);
|
||||||
|
if (lifecycle != null) {
|
||||||
|
lifecycle.addObserver(new DefaultLifecycleObserver() {
|
||||||
|
@Override
|
||||||
|
public void onDestroy(@NonNull LifecycleOwner owner) {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attachBroadcastVideoSink(@Nullable BroadcastVideoSink videoSink) {
|
public void attachBroadcastVideoSink(@Nullable BroadcastVideoSink videoSink) {
|
||||||
|
@ -76,10 +91,12 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
|
|
||||||
if (attachedVideoSink != null) {
|
if (attachedVideoSink != null) {
|
||||||
attachedVideoSink.removeSink(this);
|
attachedVideoSink.removeSink(this);
|
||||||
|
attachedVideoSink.removeRequestingSize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoSink != null) {
|
if (videoSink != null) {
|
||||||
videoSink.addSink(this);
|
videoSink.addSink(this);
|
||||||
|
videoSink.putRequestingSize(this, new Point(getWidth(), getHeight()));
|
||||||
} else {
|
} else {
|
||||||
clearImage();
|
clearImage();
|
||||||
}
|
}
|
||||||
|
@ -90,11 +107,17 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
release();
|
if (lifecycle == null || lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
|
||||||
|
release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
eglRenderer.release();
|
eglRenderer.release();
|
||||||
|
if (attachedVideoSink != null) {
|
||||||
|
attachedVideoSink.removeSink(this);
|
||||||
|
attachedVideoSink.removeRequestingSize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFrameListener(@NonNull EglRenderer.FrameListener listener, float scale, @NonNull RendererCommon.GlDrawer drawerParam) {
|
public void addFrameListener(@NonNull EglRenderer.FrameListener listener, float scale, @NonNull RendererCommon.GlDrawer drawerParam) {
|
||||||
|
@ -163,6 +186,10 @@ public class TextureViewRenderer extends TextureView implements TextureView.Surf
|
||||||
setMeasuredDimension(size.x, size.y);
|
setMeasuredDimension(size.x, size.y);
|
||||||
|
|
||||||
Log.d(TAG, "onMeasure(). New size: " + size.x + "x" + size.y);
|
Log.d(TAG, "onMeasure(). New size: " + size.x + "x" + size.y);
|
||||||
|
|
||||||
|
if (attachedVideoSink != null) {
|
||||||
|
attachedVideoSink.putRequestingSize(this, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.ColorMatrix;
|
||||||
import android.graphics.ColorMatrixColorFilter;
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
@ -30,12 +31,14 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.mediasend.SimpleAnimationListener;
|
import org.thoughtcrime.securesms.mediasend.SimpleAnimationListener;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
@ -88,8 +91,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
private ViewPager2 callParticipantsPager;
|
private ViewPager2 callParticipantsPager;
|
||||||
private RecyclerView callParticipantsRecycler;
|
private RecyclerView callParticipantsRecycler;
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
|
private MaterialButton startCall;
|
||||||
private int pagerBottomMarginDp;
|
private int pagerBottomMarginDp;
|
||||||
private boolean controlsVisible = true;
|
private boolean controlsVisible = true;
|
||||||
|
private EventListener eventListener;
|
||||||
|
|
||||||
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
||||||
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
||||||
|
@ -142,13 +147,13 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
callParticipantsPager = findViewById(R.id.call_screen_participants_pager);
|
callParticipantsPager = findViewById(R.id.call_screen_participants_pager);
|
||||||
callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler);
|
callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler);
|
||||||
toolbar = findViewById(R.id.call_screen_toolbar);
|
toolbar = findViewById(R.id.call_screen_toolbar);
|
||||||
|
startCall = findViewById(R.id.call_screen_start_call_start_call);
|
||||||
|
|
||||||
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
||||||
View decline = findViewById(R.id.call_screen_decline_call);
|
View decline = findViewById(R.id.call_screen_decline_call);
|
||||||
View answerLabel = findViewById(R.id.call_screen_answer_call_label);
|
View answerLabel = findViewById(R.id.call_screen_answer_call_label);
|
||||||
View declineLabel = findViewById(R.id.call_screen_decline_call_label);
|
View declineLabel = findViewById(R.id.call_screen_decline_call_label);
|
||||||
Guideline statusBarGuideline = findViewById(R.id.call_screen_status_bar_guideline);
|
Guideline statusBarGuideline = findViewById(R.id.call_screen_status_bar_guideline);
|
||||||
View startCall = findViewById(R.id.call_screen_start_call_start_call);
|
|
||||||
View cancelStartCall = findViewById(R.id.call_screen_start_call_cancel);
|
View cancelStartCall = findViewById(R.id.call_screen_start_call_cancel);
|
||||||
|
|
||||||
callParticipantsPager.setPageTransformer(new MarginPageTransformer(ViewUtil.dpToPx(4)));
|
callParticipantsPager.setPageTransformer(new MarginPageTransformer(ViewUtil.dpToPx(4)));
|
||||||
|
@ -163,6 +168,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
runIfNonNull(controlsListener, listener -> listener.onPageChanged(position == 0 ? CallParticipantsState.SelectedPage.GRID : CallParticipantsState.SelectedPage.FOCUSED));
|
runIfNonNull(controlsListener, listener -> listener.onPageChanged(position == 0 ? CallParticipantsState.SelectedPage.GRID : CallParticipantsState.SelectedPage.FOCUSED));
|
||||||
|
runIfNonNull(eventListener, EventListener::onPotentialLayoutChange);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -233,6 +239,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
this.controlsListener = controlsListener;
|
this.controlsListener = controlsListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEventListener(@Nullable EventListener eventListener) {
|
||||||
|
this.eventListener = eventListener;
|
||||||
|
}
|
||||||
|
|
||||||
public void setMicEnabled(boolean isMicEnabled) {
|
public void setMicEnabled(boolean isMicEnabled) {
|
||||||
micToggle.setChecked(isMicEnabled, false);
|
micToggle.setChecked(isMicEnabled, false);
|
||||||
}
|
}
|
||||||
|
@ -248,6 +258,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
pages.add(WebRtcCallParticipantsPage.forSingleParticipant(state.getFocusedParticipant(), state.isInPipMode()));
|
pages.add(WebRtcCallParticipantsPage.forSingleParticipant(state.getFocusedParticipant(), state.isInPipMode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.getGroupCallState().isConnected()) {
|
||||||
|
recipientName.setText(state.getRemoteParticipantsDescription(getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
pagerAdapter.submitList(pages);
|
pagerAdapter.submitList(pages);
|
||||||
recyclerAdapter.submitList(state.getListParticipants());
|
recyclerAdapter.submitList(state.getListParticipants());
|
||||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant());
|
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant());
|
||||||
|
@ -257,6 +271,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
} else {
|
} else {
|
||||||
layoutParticipantsForSmallCount();
|
layoutParticipantsForSmallCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventListener != null) {
|
||||||
|
eventListener.onPotentialLayoutChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant) {
|
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant) {
|
||||||
|
@ -283,17 +301,17 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
case SMALL_RECTANGLE:
|
case SMALL_RECTANGLE:
|
||||||
smallLocalRenderFrame.setVisibility(View.VISIBLE);
|
smallLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||||
smallLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
smallLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
||||||
animatePipToRectangle();
|
animatePipToLargeRectangle();
|
||||||
|
|
||||||
largeLocalRender.attachBroadcastVideoSink(null);
|
largeLocalRender.attachBroadcastVideoSink(null);
|
||||||
largeLocalRenderFrame.setVisibility(View.GONE);
|
largeLocalRenderFrame.setVisibility(View.GONE);
|
||||||
|
|
||||||
videoToggle.setChecked(true, false);
|
videoToggle.setChecked(true, false);
|
||||||
break;
|
break;
|
||||||
case SMALL_SQUARE:
|
case SMALLER_RECTANGLE:
|
||||||
smallLocalRenderFrame.setVisibility(View.VISIBLE);
|
smallLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||||
smallLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
smallLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
||||||
animatePipToSquare();
|
animatePipToSmallRectangle();
|
||||||
|
|
||||||
largeLocalRender.attachBroadcastVideoSink(null);
|
largeLocalRender.attachBroadcastVideoSink(null);
|
||||||
largeLocalRenderFrame.setVisibility(View.GONE);
|
largeLocalRenderFrame.setVisibility(View.GONE);
|
||||||
|
@ -341,7 +359,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
recipientId = recipient.getId();
|
recipientId = recipient.getId();
|
||||||
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
recipientName.setText(R.string.WebRtcCallView__group_call);
|
recipientName.setText(getContext().getString(R.string.WebRtcCallView__s_group_call, recipient.getDisplayName(getContext())));
|
||||||
if (toolbar.getMenu().findItem(R.id.menu_group_call_participants_list) == null) {
|
if (toolbar.getMenu().findItem(R.id.menu_group_call_participants_list) == null) {
|
||||||
toolbar.inflateMenu(R.menu.group_call);
|
toolbar.inflateMenu(R.menu.group_call);
|
||||||
toolbar.setOnMenuItemClickListener(unused -> showParticipantsList());
|
toolbar.setOnMenuItemClickListener(unused -> showParticipantsList());
|
||||||
|
@ -375,6 +393,27 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStatusFromGroupCallState(@NonNull WebRtcViewModel.GroupCallState groupCallState) {
|
||||||
|
switch (groupCallState) {
|
||||||
|
case DISCONNECTED:
|
||||||
|
status.setText(R.string.WebRtcCallView__disconnected);
|
||||||
|
break;
|
||||||
|
case CONNECTING:
|
||||||
|
status.setText(R.string.WebRtcCallView__connecting);
|
||||||
|
break;
|
||||||
|
case RECONNECTING:
|
||||||
|
status.setText(R.string.WebRtcCallView__reconnecting);
|
||||||
|
break;
|
||||||
|
case CONNECTED_AND_JOINING:
|
||||||
|
status.setText(R.string.WebRtcCallView__joining);
|
||||||
|
break;
|
||||||
|
case CONNECTED_AND_JOINED:
|
||||||
|
case CONNECTED:
|
||||||
|
status.setText("");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setWebRtcControls(@NonNull WebRtcControls webRtcControls) {
|
public void setWebRtcControls(@NonNull WebRtcControls webRtcControls) {
|
||||||
Set<View> lastVisibleSet = new HashSet<>(visibleViewSet);
|
Set<View> lastVisibleSet = new HashSet<>(visibleViewSet);
|
||||||
|
|
||||||
|
@ -383,6 +422,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
if (webRtcControls.displayStartCallControls()) {
|
if (webRtcControls.displayStartCallControls()) {
|
||||||
visibleViewSet.add(footerGradient);
|
visibleViewSet.add(footerGradient);
|
||||||
visibleViewSet.add(startCallControls);
|
visibleViewSet.add(startCallControls);
|
||||||
|
|
||||||
|
startCall.setText(webRtcControls.getStartCallButtonText());
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem item = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list);
|
||||||
|
if (item != null) {
|
||||||
|
item.setVisible(webRtcControls.displayGroupMembersButton());
|
||||||
|
item.setEnabled(webRtcControls.displayGroupMembersButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webRtcControls.displayTopViews()) {
|
if (webRtcControls.displayTopViews()) {
|
||||||
|
@ -462,7 +509,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
return videoToggle;
|
return videoToggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animatePipToRectangle() {
|
private void animatePipToLargeRectangle() {
|
||||||
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
|
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
|
||||||
animation.setDuration(PIP_RESIZE_DURATION);
|
animation.setDuration(PIP_RESIZE_DURATION);
|
||||||
animation.setAnimationListener(new SimpleAnimationListener() {
|
animation.setAnimationListener(new SimpleAnimationListener() {
|
||||||
|
@ -476,11 +523,11 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
smallLocalRenderFrame.startAnimation(animation);
|
smallLocalRenderFrame.startAnimation(animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animatePipToSquare() {
|
private void animatePipToSmallRectangle() {
|
||||||
pictureInPictureGestureHelper.lockToBottomEnd();
|
pictureInPictureGestureHelper.lockToBottomEnd();
|
||||||
|
|
||||||
pictureInPictureGestureHelper.performAfterFling(() -> {
|
pictureInPictureGestureHelper.performAfterFling(() -> {
|
||||||
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(72), ViewUtil.dpToPx(72));
|
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(40), ViewUtil.dpToPx(72));
|
||||||
animation.setDuration(PIP_RESIZE_DURATION);
|
animation.setDuration(PIP_RESIZE_DURATION);
|
||||||
animation.setAnimationListener(new SimpleAnimationListener() {
|
animation.setAnimationListener(new SimpleAnimationListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -606,9 +653,9 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
getHandler().removeCallbacks(fadeOutRunnable);
|
getHandler().removeCallbacks(fadeOutRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runIfNonNull(@Nullable ControlsListener controlsListener, @NonNull Consumer<ControlsListener> controlsListenerConsumer) {
|
private static <T> void runIfNonNull(@Nullable T listener, @NonNull Consumer<T> listenerConsumer) {
|
||||||
if (controlsListener != null) {
|
if (listener != null) {
|
||||||
controlsListenerConsumer.accept(controlsListener);
|
listenerConsumer.accept(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,4 +695,8 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
void onShowParticipantsList();
|
void onShowParticipantsList();
|
||||||
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface EventListener {
|
||||||
|
void onPotentialLayoutChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
|
|
||||||
public class WebRtcCallViewModel extends ViewModel {
|
public class WebRtcCallViewModel extends ViewModel {
|
||||||
|
@ -104,11 +105,13 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||||
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), webRtcViewModel, enableVideo));
|
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), webRtcViewModel, enableVideo));
|
||||||
|
|
||||||
updateWebRtcControls(webRtcViewModel.getState(),
|
updateWebRtcControls(webRtcViewModel.getState(),
|
||||||
|
webRtcViewModel.getGroupState(),
|
||||||
localParticipant.getCameraState().isEnabled(),
|
localParticipant.getCameraState().isEnabled(),
|
||||||
webRtcViewModel.isRemoteVideoEnabled(),
|
webRtcViewModel.isRemoteVideoEnabled(),
|
||||||
webRtcViewModel.isRemoteVideoOffer(),
|
webRtcViewModel.isRemoteVideoOffer(),
|
||||||
localParticipant.isMoreThanOneCameraAvailable(),
|
localParticipant.isMoreThanOneCameraAvailable(),
|
||||||
webRtcViewModel.isBluetoothAvailable(),
|
webRtcViewModel.isBluetoothAvailable(),
|
||||||
|
Util.hasItems(webRtcViewModel.getRemoteParticipants()),
|
||||||
repository.getAudioOutput());
|
repository.getAudioOutput());
|
||||||
|
|
||||||
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
|
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
|
||||||
|
@ -133,11 +136,13 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateWebRtcControls(@NonNull WebRtcViewModel.State state,
|
private void updateWebRtcControls(@NonNull WebRtcViewModel.State state,
|
||||||
|
@NonNull WebRtcViewModel.GroupCallState groupState,
|
||||||
boolean isLocalVideoEnabled,
|
boolean isLocalVideoEnabled,
|
||||||
boolean isRemoteVideoEnabled,
|
boolean isRemoteVideoEnabled,
|
||||||
boolean isRemoteVideoOffer,
|
boolean isRemoteVideoOffer,
|
||||||
boolean isMoreThanOneCameraAvailable,
|
boolean isMoreThanOneCameraAvailable,
|
||||||
boolean isBluetoothAvailable,
|
boolean isBluetoothAvailable,
|
||||||
|
boolean hasAtLeastOneRemote,
|
||||||
@NonNull WebRtcAudioOutput audioOutput)
|
@NonNull WebRtcAudioOutput audioOutput)
|
||||||
{
|
{
|
||||||
final WebRtcControls.CallState callState;
|
final WebRtcControls.CallState callState;
|
||||||
|
@ -166,12 +171,34 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||||
callState = WebRtcControls.CallState.ONGOING;
|
callState = WebRtcControls.CallState.ONGOING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final WebRtcControls.GroupCallState groupCallState;
|
||||||
|
|
||||||
|
switch (groupState) {
|
||||||
|
case DISCONNECTED:
|
||||||
|
groupCallState = WebRtcControls.GroupCallState.DISCONNECTED;
|
||||||
|
break;
|
||||||
|
case CONNECTING:
|
||||||
|
case RECONNECTING:
|
||||||
|
groupCallState = WebRtcControls.GroupCallState.CONNECTING;
|
||||||
|
break;
|
||||||
|
case CONNECTED:
|
||||||
|
case CONNECTED_AND_JOINING:
|
||||||
|
case CONNECTED_AND_JOINED:
|
||||||
|
groupCallState = WebRtcControls.GroupCallState.CONNECTED;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
groupCallState = WebRtcControls.GroupCallState.NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
webRtcControls.setValue(new WebRtcControls(isLocalVideoEnabled,
|
webRtcControls.setValue(new WebRtcControls(isLocalVideoEnabled,
|
||||||
isRemoteVideoEnabled || isRemoteVideoOffer,
|
isRemoteVideoEnabled || isRemoteVideoOffer,
|
||||||
isMoreThanOneCameraAvailable,
|
isMoreThanOneCameraAvailable,
|
||||||
isBluetoothAvailable,
|
isBluetoothAvailable,
|
||||||
Boolean.TRUE.equals(isInPipMode.getValue()),
|
Boolean.TRUE.equals(isInPipMode.getValue()),
|
||||||
|
hasAtLeastOneRemote,
|
||||||
callState,
|
callState,
|
||||||
|
groupCallState,
|
||||||
audioOutput));
|
audioOutput));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
public final class WebRtcControls {
|
public final class WebRtcControls {
|
||||||
|
|
||||||
public static final WebRtcControls NONE = new WebRtcControls();
|
public static final WebRtcControls NONE = new WebRtcControls();
|
||||||
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, CallState.NONE, WebRtcAudioOutput.HANDSET);
|
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET);
|
||||||
|
|
||||||
private final boolean isRemoteVideoEnabled;
|
private final boolean isRemoteVideoEnabled;
|
||||||
private final boolean isLocalVideoEnabled;
|
private final boolean isLocalVideoEnabled;
|
||||||
private final boolean isMoreThanOneCameraAvailable;
|
private final boolean isMoreThanOneCameraAvailable;
|
||||||
private final boolean isBluetoothAvailable;
|
private final boolean isBluetoothAvailable;
|
||||||
private final boolean isInPipMode;
|
private final boolean isInPipMode;
|
||||||
|
private final boolean hasAtLeastOneRemote;
|
||||||
private final CallState callState;
|
private final CallState callState;
|
||||||
|
private final GroupCallState groupCallState;
|
||||||
private final WebRtcAudioOutput audioOutput;
|
private final WebRtcAudioOutput audioOutput;
|
||||||
|
|
||||||
private WebRtcControls() {
|
private WebRtcControls() {
|
||||||
this(false, false, false, false, false, CallState.NONE, WebRtcAudioOutput.HANDSET);
|
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebRtcControls(boolean isLocalVideoEnabled,
|
WebRtcControls(boolean isLocalVideoEnabled,
|
||||||
|
@ -24,7 +29,9 @@ public final class WebRtcControls {
|
||||||
boolean isMoreThanOneCameraAvailable,
|
boolean isMoreThanOneCameraAvailable,
|
||||||
boolean isBluetoothAvailable,
|
boolean isBluetoothAvailable,
|
||||||
boolean isInPipMode,
|
boolean isInPipMode,
|
||||||
|
boolean hasAtLeastOneRemote,
|
||||||
@NonNull CallState callState,
|
@NonNull CallState callState,
|
||||||
|
@NonNull GroupCallState groupCallState,
|
||||||
@NonNull WebRtcAudioOutput audioOutput)
|
@NonNull WebRtcAudioOutput audioOutput)
|
||||||
{
|
{
|
||||||
this.isLocalVideoEnabled = isLocalVideoEnabled;
|
this.isLocalVideoEnabled = isLocalVideoEnabled;
|
||||||
|
@ -32,7 +39,9 @@ public final class WebRtcControls {
|
||||||
this.isBluetoothAvailable = isBluetoothAvailable;
|
this.isBluetoothAvailable = isBluetoothAvailable;
|
||||||
this.isMoreThanOneCameraAvailable = isMoreThanOneCameraAvailable;
|
this.isMoreThanOneCameraAvailable = isMoreThanOneCameraAvailable;
|
||||||
this.isInPipMode = isInPipMode;
|
this.isInPipMode = isInPipMode;
|
||||||
|
this.hasAtLeastOneRemote = hasAtLeastOneRemote;
|
||||||
this.callState = callState;
|
this.callState = callState;
|
||||||
|
this.groupCallState = groupCallState;
|
||||||
this.audioOutput = audioOutput;
|
this.audioOutput = audioOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +49,17 @@ public final class WebRtcControls {
|
||||||
return isPreJoin();
|
return isPreJoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StringRes int getStartCallButtonText() {
|
||||||
|
if (isGroupCall() && hasAtLeastOneRemote) {
|
||||||
|
return R.string.WebRtcCallView__join_call;
|
||||||
|
}
|
||||||
|
return R.string.WebRtcCallView__start_call;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean displayGroupMembersButton() {
|
||||||
|
return groupCallState == GroupCallState.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
boolean displayEndCall() {
|
boolean displayEndCall() {
|
||||||
return isAtLeastOutgoing();
|
return isAtLeastOutgoing();
|
||||||
}
|
}
|
||||||
|
@ -116,6 +136,10 @@ public final class WebRtcControls {
|
||||||
return callState.isAtLeast(CallState.OUTGOING);
|
return callState.isAtLeast(CallState.OUTGOING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isGroupCall() {
|
||||||
|
return groupCallState != GroupCallState.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
public enum CallState {
|
public enum CallState {
|
||||||
NONE,
|
NONE,
|
||||||
PRE_JOIN,
|
PRE_JOIN,
|
||||||
|
@ -124,8 +148,16 @@ public final class WebRtcControls {
|
||||||
ONGOING,
|
ONGOING,
|
||||||
ENDING;
|
ENDING;
|
||||||
|
|
||||||
boolean isAtLeast(@NonNull CallState other) {
|
boolean isAtLeast(@SuppressWarnings("SameParameterValue") @NonNull CallState other) {
|
||||||
return compareTo(other) >= 0;
|
return compareTo(other) >= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum GroupCallState {
|
||||||
|
NONE,
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTED,
|
||||||
|
RECONNECTING
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.components.webrtc;
|
||||||
public enum WebRtcLocalRenderState {
|
public enum WebRtcLocalRenderState {
|
||||||
GONE,
|
GONE,
|
||||||
SMALL_RECTANGLE,
|
SMALL_RECTANGLE,
|
||||||
SMALL_SQUARE,
|
SMALLER_RECTANGLE,
|
||||||
LARGE,
|
LARGE,
|
||||||
LARGE_NO_VIDEO
|
LARGE_NO_VIDEO
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||||
import org.thoughtcrime.securesms.util.MappingModel;
|
import org.thoughtcrime.securesms.util.MappingModel;
|
||||||
|
|
||||||
|
@ -79,9 +80,14 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
|
||||||
private void updateList(@NonNull CallParticipantsState callParticipantsState) {
|
private void updateList(@NonNull CallParticipantsState callParticipantsState) {
|
||||||
List<MappingModel<?>> items = new ArrayList<>();
|
List<MappingModel<?>> items = new ArrayList<>();
|
||||||
|
|
||||||
items.add(new CallParticipantsListHeader(callParticipantsState.getAllRemoteParticipants().size() + 1));
|
boolean includeSelf = callParticipantsState.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
|
||||||
|
|
||||||
|
items.add(new CallParticipantsListHeader(callParticipantsState.getAllRemoteParticipants().size() + (includeSelf ? 1 : 0)));
|
||||||
|
|
||||||
|
if (includeSelf) {
|
||||||
|
items.add(new CallParticipantViewState(callParticipantsState.getLocalParticipant()));
|
||||||
|
}
|
||||||
|
|
||||||
items.add(new CallParticipantViewState(callParticipantsState.getLocalParticipant()));
|
|
||||||
for (CallParticipant callParticipant : callParticipantsState.getAllRemoteParticipants()) {
|
for (CallParticipant callParticipant : callParticipantsState.getAllRemoteParticipants()) {
|
||||||
items.add(new CallParticipantViewState(callParticipant));
|
items.add(new CallParticipantViewState(callParticipant));
|
||||||
}
|
}
|
||||||
|
|
|
@ -818,6 +818,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
|
if (isSecureText) inflater.inflate(R.menu.conversation_callable_secure, menu);
|
||||||
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
|
else inflater.inflate(R.menu.conversation_callable_insecure, menu);
|
||||||
} else if (isGroupConversation()) {
|
} else if (isGroupConversation()) {
|
||||||
|
if (isActiveV2Group && FeatureFlags.groupCalling()) {
|
||||||
|
inflater.inflate(R.menu.conversation_callable_groupv2, menu);
|
||||||
|
}
|
||||||
|
|
||||||
inflater.inflate(R.menu.conversation_group_options, menu);
|
inflater.inflate(R.menu.conversation_group_options, menu);
|
||||||
|
|
||||||
if (!isPushGroupConversation()) {
|
if (!isPushGroupConversation()) {
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.thoughtcrime.securesms.events;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow system to identify a call participant by their device demux id and their
|
||||||
|
* recipient id.
|
||||||
|
*/
|
||||||
|
public final class CallParticipantId {
|
||||||
|
|
||||||
|
private static final long DEFAULT_ID = -1;
|
||||||
|
|
||||||
|
private final long demuxId;
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public CallParticipantId(@NonNull Recipient recipient) {
|
||||||
|
this(DEFAULT_ID, recipient.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallParticipantId(long demuxId, @NonNull RecipientId recipientId) {
|
||||||
|
this.demuxId = demuxId;
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDemuxId() {
|
||||||
|
return demuxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull RecipientId getRecipientId() {
|
||||||
|
return recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final CallParticipantId that = (CallParticipantId) o;
|
||||||
|
return demuxId == that.demuxId &&
|
||||||
|
recipientId.equals(that.recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(demuxId, recipientId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,8 +45,45 @@ public class WebRtcViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final @NonNull State state;
|
public enum GroupCallState {
|
||||||
private final @NonNull Recipient recipient;
|
IDLE,
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTING,
|
||||||
|
RECONNECTING,
|
||||||
|
CONNECTED,
|
||||||
|
CONNECTED_AND_JOINING,
|
||||||
|
CONNECTED_AND_JOINED;
|
||||||
|
|
||||||
|
public boolean isNotIdle() {
|
||||||
|
return this != IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
switch (this) {
|
||||||
|
case CONNECTED:
|
||||||
|
case CONNECTED_AND_JOINING:
|
||||||
|
case CONNECTED_AND_JOINED:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNotIdleOrConnected() {
|
||||||
|
switch (this) {
|
||||||
|
case DISCONNECTED:
|
||||||
|
case CONNECTING:
|
||||||
|
case RECONNECTING:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final @NonNull State state;
|
||||||
|
private final @NonNull GroupCallState groupState;
|
||||||
|
private final @NonNull Recipient recipient;
|
||||||
|
|
||||||
private final boolean isBluetoothAvailable;
|
private final boolean isBluetoothAvailable;
|
||||||
private final boolean isRemoteVideoOffer;
|
private final boolean isRemoteVideoOffer;
|
||||||
|
@ -56,6 +93,7 @@ public class WebRtcViewModel {
|
||||||
private final List<CallParticipant> remoteParticipants;
|
private final List<CallParticipant> remoteParticipants;
|
||||||
|
|
||||||
public WebRtcViewModel(@NonNull State state,
|
public WebRtcViewModel(@NonNull State state,
|
||||||
|
@NonNull GroupCallState groupState,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull CameraState localCameraState,
|
@NonNull CameraState localCameraState,
|
||||||
@Nullable BroadcastVideoSink localSink,
|
@Nullable BroadcastVideoSink localSink,
|
||||||
|
@ -66,6 +104,7 @@ public class WebRtcViewModel {
|
||||||
@NonNull List<CallParticipant> remoteParticipants)
|
@NonNull List<CallParticipant> remoteParticipants)
|
||||||
{
|
{
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
this.groupState = groupState;
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.isBluetoothAvailable = isBluetoothAvailable;
|
this.isBluetoothAvailable = isBluetoothAvailable;
|
||||||
this.isRemoteVideoOffer = isRemoteVideoOffer;
|
this.isRemoteVideoOffer = isRemoteVideoOffer;
|
||||||
|
@ -79,12 +118,16 @@ public class WebRtcViewModel {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull GroupCallState getGroupState() {
|
||||||
|
return groupState;
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull Recipient getRecipient() {
|
public @NonNull Recipient getRecipient() {
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRemoteVideoEnabled() {
|
public boolean isRemoteVideoEnabled() {
|
||||||
return Stream.of(remoteParticipants).anyMatch(CallParticipant::isVideoEnabled);
|
return Stream.of(remoteParticipants).anyMatch(CallParticipant::isVideoEnabled) || (groupState.isNotIdle() && remoteParticipants.size() > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBluetoothAvailable() {
|
public boolean isBluetoothAvailable() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
|
@ -25,7 +26,9 @@ import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class GroupManager {
|
public final class GroupManager {
|
||||||
|
|
||||||
|
@ -390,6 +393,19 @@ public final class GroupManager {
|
||||||
new GroupManagerV2(context).sendNoopGroupUpdate(groupMasterKey, currentState);
|
new GroupManagerV2(context).sendNoopGroupUpdate(groupMasterKey, currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static @NonNull GroupExternalCredential getGroupExternalCredential(@NonNull Context context,
|
||||||
|
@NonNull GroupId.V2 groupId)
|
||||||
|
throws IOException, VerificationFailedException
|
||||||
|
{
|
||||||
|
return new GroupManagerV2(context).getGroupExternalCredential(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public static @NonNull Map<UUID, UuidCiphertext> getUuidCipherTexts(@NonNull Context context, @NonNull GroupId.V2 groupId) {
|
||||||
|
return new GroupManagerV2(context).getUuidCipherTexts(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
public static class GroupActionResult {
|
public static class GroupActionResult {
|
||||||
private final Recipient groupRecipient;
|
private final Recipient groupRecipient;
|
||||||
private final long threadId;
|
private final long threadId;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
|
@ -22,6 +23,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
|
import org.signal.zkgroup.groups.ClientZkGroupCipher;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.signal.zkgroup.groups.GroupSecretParams;
|
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||||
import org.signal.zkgroup.groups.UuidCiphertext;
|
import org.signal.zkgroup.groups.UuidCiphertext;
|
||||||
|
@ -30,7 +32,6 @@ import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
@ -69,8 +70,10 @@ import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -109,6 +112,35 @@ final class GroupManagerV2 {
|
||||||
authorization.getAuthorizationForToday(Recipient.self().requireUuid(), groupSecretParams));
|
authorization.getAuthorizationForToday(Recipient.self().requireUuid(), groupSecretParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull GroupExternalCredential getGroupExternalCredential(@NonNull GroupId.V2 groupId)
|
||||||
|
throws IOException, VerificationFailedException
|
||||||
|
{
|
||||||
|
GroupMasterKey groupMasterKey = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
.requireGroup(groupId)
|
||||||
|
.requireV2GroupProperties()
|
||||||
|
.getGroupMasterKey();
|
||||||
|
|
||||||
|
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
||||||
|
|
||||||
|
return groupsV2Api.getGroupExternalCredential(authorization.getAuthorizationForToday(Recipient.self().requireUuid(), groupSecretParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull Map<UUID, UuidCiphertext> getUuidCipherTexts(@NonNull GroupId.V2 groupId) {
|
||||||
|
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).requireGroup(groupId);
|
||||||
|
GroupMasterKey groupMasterKey = groupRecord.requireV2GroupProperties().getGroupMasterKey();
|
||||||
|
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||||
|
List<Recipient> recipients = Recipient.resolvedList(groupRecord.getMembers());
|
||||||
|
|
||||||
|
Map<UUID, UuidCiphertext> uuidCipherTexts = new HashMap<>();
|
||||||
|
for (Recipient recipient : recipients) {
|
||||||
|
uuidCipherTexts.put(recipient.requireUuid(), clientZkGroupCipher.encryptUuid(recipient.requireUuid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuidCipherTexts;
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
GroupCreator create() throws GroupChangeBusyException {
|
GroupCreator create() throws GroupChangeBusyException {
|
||||||
return new GroupCreator(GroupsV2ProcessingLock.acquireGroupProcessingLock());
|
return new GroupCreator(GroupsV2ProcessingLock.acquireGroupProcessingLock());
|
||||||
|
|
|
@ -109,6 +109,7 @@ import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
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 org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
||||||
|
@ -423,6 +424,7 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||||
else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get());
|
else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get());
|
||||||
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId);
|
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId);
|
||||||
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(content, message.getBusyMessage().get());
|
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(content, message.getBusyMessage().get());
|
||||||
|
else if (message.getOpaqueMessage().isPresent()) handleCallOpaqueMessage(content, message.getOpaqueMessage().get());
|
||||||
} else if (content.getReceiptMessage().isPresent()) {
|
} else if (content.getReceiptMessage().isPresent()) {
|
||||||
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
|
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
|
||||||
|
|
||||||
|
@ -625,6 +627,27 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCallOpaqueMessage(@NonNull SignalServiceContent content,
|
||||||
|
@NonNull OpaqueMessage message)
|
||||||
|
{
|
||||||
|
log(TAG, String.valueOf(content.getTimestamp()), "handleCallOpaqueMessage");
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||||
|
|
||||||
|
long messageAgeSeconds = 0;
|
||||||
|
if (content.getServerReceivedTimestamp() > 0 && content.getServerDeliveredTimestamp() >= content.getServerReceivedTimestamp()) {
|
||||||
|
messageAgeSeconds = (content.getServerDeliveredTimestamp() - content.getServerReceivedTimestamp()) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.setAction(WebRtcCallService.ACTION_RECEIVE_OPAQUE_MESSAGE)
|
||||||
|
.putExtra(WebRtcCallService.EXTRA_OPAQUE_MESSAGE, message.getOpaque())
|
||||||
|
.putExtra(WebRtcCallService.EXTRA_UUID, Recipient.externalHighTrustPush(context, content.getSender()).requireUuid().toString())
|
||||||
|
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice())
|
||||||
|
.putExtra(WebRtcCallService.EXTRA_MESSAGE_AGE_SECONDS, messageAgeSeconds);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleEndSessionMessage(@NonNull SignalServiceContent content,
|
private void handleEndSessionMessage(@NonNull SignalServiceContent content,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,14 +15,19 @@ import android.telephony.TelephonyManager;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.ringrtc.CallException;
|
import org.signal.ringrtc.CallException;
|
||||||
import org.signal.ringrtc.CallId;
|
import org.signal.ringrtc.CallId;
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.signal.ringrtc.CallManager.CallEvent;
|
import org.signal.ringrtc.CallManager.CallEvent;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
import org.signal.ringrtc.HttpHeader;
|
import org.signal.ringrtc.HttpHeader;
|
||||||
import org.signal.ringrtc.IceCandidate;
|
import org.signal.ringrtc.IceCandidate;
|
||||||
import org.signal.ringrtc.Remote;
|
import org.signal.ringrtc.Remote;
|
||||||
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
|
@ -30,8 +35,10 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||||
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
||||||
|
@ -55,12 +62,15 @@ import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -71,8 +81,9 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class WebRtcCallService extends Service implements CallManager.Observer,
|
public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
BluetoothStateManager.BluetoothStateListener,
|
BluetoothStateManager.BluetoothStateListener,
|
||||||
CameraEventListener
|
CameraEventListener,
|
||||||
|
GroupCall.Observer
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = WebRtcCallService.class.getSimpleName();
|
private static final String TAG = WebRtcCallService.class.getSimpleName();
|
||||||
|
@ -107,6 +118,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
public static final String EXTRA_CAMERA_STATE = "camera_state";
|
public static final String EXTRA_CAMERA_STATE = "camera_state";
|
||||||
public static final String EXTRA_IS_ALWAYS_TURN = "is_always_turn";
|
public static final String EXTRA_IS_ALWAYS_TURN = "is_always_turn";
|
||||||
public static final String EXTRA_TURN_SERVER_INFO = "turn_server_info";
|
public static final String EXTRA_TURN_SERVER_INFO = "turn_server_info";
|
||||||
|
public static final String EXTRA_GROUP_EXTERNAL_TOKEN = "group_external_token";
|
||||||
|
public static final String EXTRA_HTTP_REQUEST_ID = "http_request_id";
|
||||||
|
public static final String EXTRA_HTTP_RESPONSE_STATUS = "http_response_status";
|
||||||
|
public static final String EXTRA_HTTP_RESPONSE_BODY = "http_response_body";
|
||||||
|
public static final String EXTRA_OPAQUE_MESSAGE = "opaque";
|
||||||
|
public static final String EXTRA_UUID = "uuid";
|
||||||
|
public static final String EXTRA_MESSAGE_AGE_SECONDS = "message_age_seconds";
|
||||||
|
|
||||||
public static final String ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN";
|
public static final String ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN";
|
||||||
public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL";
|
public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL";
|
||||||
|
@ -158,6 +176,17 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
public static final String ACTION_CAMERA_SWITCH_COMPLETED = "CAMERA_FLIP_COMPLETE";
|
public static final String ACTION_CAMERA_SWITCH_COMPLETED = "CAMERA_FLIP_COMPLETE";
|
||||||
public static final String ACTION_TURN_SERVER_UPDATE = "TURN_SERVER_UPDATE";
|
public static final String ACTION_TURN_SERVER_UPDATE = "TURN_SERVER_UPDATE";
|
||||||
public static final String ACTION_SETUP_FAILURE = "SETUP_FAILURE";
|
public static final String ACTION_SETUP_FAILURE = "SETUP_FAILURE";
|
||||||
|
public static final String ACTION_HTTP_SUCCESS = "HTTP_SUCCESS";
|
||||||
|
public static final String ACTION_HTTP_FAILURE = "HTTP_FAILURE";
|
||||||
|
public static final String ACTION_SEND_OPAQUE_MESSAGE = "SEND_OPAQUE_MESSAGE";
|
||||||
|
public static final String ACTION_RECEIVE_OPAQUE_MESSAGE = "RECEIVE_OPAQUE_MESSAGE";
|
||||||
|
|
||||||
|
public static final String ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED = "GROUP_LOCAL_DEVICE_CHANGE";
|
||||||
|
public static final String ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED = "GROUP_REMOTE_DEVICE_CHANGE";
|
||||||
|
public static final String ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED = "GROUP_JOINED_MEMBERS_CHANGE";
|
||||||
|
public static final String ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF = "GROUP_REQUEST_MEMBERSHIP_PROOF";
|
||||||
|
public static final String ACTION_GROUP_REQUEST_UPDATE_MEMBERS = "GROUP_REQUEST_UPDATE_MEMBERS";
|
||||||
|
public static final String ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS = "GROUP_UPDATE_RENDERED_RESOLUTIONS";
|
||||||
|
|
||||||
public static final int BUSY_TONE_LENGTH = 2000;
|
public static final int BUSY_TONE_LENGTH = 2000;
|
||||||
|
|
||||||
|
@ -215,7 +244,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
webRtcInteractor = new WebRtcInteractor(this, callManager, lockManager, new SignalAudioManager(this), bluetoothStateManager, this);
|
webRtcInteractor = new WebRtcInteractor(this,
|
||||||
|
callManager,
|
||||||
|
lockManager,
|
||||||
|
new SignalAudioManager(this),
|
||||||
|
bluetoothStateManager,
|
||||||
|
this,
|
||||||
|
this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,9 +401,9 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
public void setCallInProgressNotification(int type, @NonNull Recipient recipient) {
|
||||||
startForeground(CallNotificationBuilder.getNotificationId(getApplicationContext(), type),
|
startForeground(CallNotificationBuilder.getNotificationId(getApplicationContext(), type),
|
||||||
CallNotificationBuilder.getCallInProgressNotification(this, type, remotePeer.getRecipient()));
|
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage() {
|
public void sendMessage() {
|
||||||
|
@ -377,6 +412,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
|
|
||||||
public void sendMessage(@NonNull WebRtcServiceState state) {
|
public void sendMessage(@NonNull WebRtcServiceState state) {
|
||||||
EventBus.getDefault().postSticky(new WebRtcViewModel(state.getCallInfoState().getCallState(),
|
EventBus.getDefault().postSticky(new WebRtcViewModel(state.getCallInfoState().getCallState(),
|
||||||
|
state.getCallInfoState().getGroupCallState(),
|
||||||
state.getCallInfoState().getCallRecipient(),
|
state.getCallInfoState().getCallRecipient(),
|
||||||
state.getLocalDeviceState().getCameraState(),
|
state.getLocalDeviceState().getCameraState(),
|
||||||
state.getVideoState().getLocalSink(),
|
state.getVideoState().getLocalSink(),
|
||||||
|
@ -612,6 +648,10 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
listenableFutureTask.addListener(new SendCallMessageListener<>(remotePeer));
|
listenableFutureTask.addListener(new SendCallMessageListener<>(remotePeer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage opaqueMessage) {
|
||||||
|
sendMessage(new RemotePeer(RecipientId.from(uuid, null)), opaqueMessage);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartCall(@Nullable Remote remote, @NonNull CallId callId, @NonNull Boolean isOutgoing, @Nullable CallManager.CallMediaType callMediaType) {
|
public void onStartCall(@Nullable Remote remote, @NonNull CallId callId, @NonNull Boolean isOutgoing, @Nullable CallManager.CallMediaType callMediaType) {
|
||||||
Log.i(TAG, "onStartCall(): callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType);
|
Log.i(TAG, "onStartCall(): callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType);
|
||||||
|
@ -871,12 +911,93 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] bytes) {
|
public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] opaque) {
|
||||||
Log.i(TAG, "onSendCallMessage:");
|
Log.i(TAG, "onSendCallMessage:");
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
|
||||||
|
intent.setAction(ACTION_SEND_OPAQUE_MESSAGE)
|
||||||
|
.putExtra(EXTRA_UUID, uuid.toString())
|
||||||
|
.putExtra(EXTRA_OPAQUE_MESSAGE, opaque);
|
||||||
|
|
||||||
|
startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendHttpRequest(long l, @NonNull String s, @NonNull CallManager.HttpMethod httpMethod, @Nullable List<HttpHeader> list, @Nullable byte[] bytes) {
|
public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull CallManager.HttpMethod httpMethod, @Nullable List<HttpHeader> headers, @Nullable byte[] body) {
|
||||||
Log.i(TAG, "onSendHttpRequest:");
|
Log.i(TAG, "onSendHttpRequest(): request_id: " + requestId);
|
||||||
|
networkExecutor.execute(() -> {
|
||||||
|
List<Pair<String, String>> headerPairs;
|
||||||
|
if (headers != null) {
|
||||||
|
headerPairs = Stream.of(headers)
|
||||||
|
.map(header -> new Pair<>(header.getName(), header.getValue()))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
headerPairs = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CallingResponse response = messageSender.makeCallingRequest(requestId, url, httpMethod.name(), headerPairs, body);
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
|
||||||
|
if (response instanceof CallingResponse.Success) {
|
||||||
|
CallingResponse.Success success = (CallingResponse.Success) response;
|
||||||
|
|
||||||
|
intent.setAction(ACTION_HTTP_SUCCESS)
|
||||||
|
.putExtra(EXTRA_HTTP_REQUEST_ID, success.getRequestId())
|
||||||
|
.putExtra(EXTRA_HTTP_RESPONSE_STATUS, success.getResponseStatus())
|
||||||
|
.putExtra(EXTRA_HTTP_RESPONSE_BODY, success.getResponseBody());
|
||||||
|
} else {
|
||||||
|
intent.setAction(ACTION_HTTP_FAILURE)
|
||||||
|
.putExtra(EXTRA_HTTP_REQUEST_ID, response.getRequestId());
|
||||||
|
}
|
||||||
|
|
||||||
|
startService(intent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestMembershipProof(@NonNull GroupCall groupCall) {
|
||||||
|
Log.i(TAG, "requestMembershipProof():");
|
||||||
|
|
||||||
|
networkExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(this, serviceState.getCallInfoState().getCallRecipient().getGroupId().get().requireV2());
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
|
||||||
|
intent.setAction(ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF)
|
||||||
|
.putExtra(EXTRA_GROUP_EXTERNAL_TOKEN, credential.getTokenBytes().toByteArray());
|
||||||
|
|
||||||
|
startService(intent);
|
||||||
|
} catch (IOException | VerificationFailedException e) {
|
||||||
|
Log.w(TAG, "Unable to fetch group membership proof", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestGroupMembers(@NonNull GroupCall groupCall) {
|
||||||
|
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_REQUEST_UPDATE_MEMBERS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocalDeviceStateChanged(@NonNull GroupCall groupCall) {
|
||||||
|
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemoteDeviceStatesChanged(@NonNull GroupCall groupCall) {
|
||||||
|
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onJoinedMembersChanged(@NonNull GroupCall groupCall) {
|
||||||
|
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnded(@NonNull GroupCall groupCall, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
|
||||||
|
Log.i(TAG, "onEnded: " + groupCallEndReason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||||
|
|
||||||
Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId());
|
Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId());
|
||||||
|
|
||||||
CallParticipant oldParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
CallParticipant oldParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||||
CallParticipant newParticipant = oldParticipant.withVideoEnabled(enable);
|
CallParticipant newParticipant = oldParticipant.withVideoEnabled(enable);
|
||||||
|
|
||||||
return currentState.builder()
|
return currentState.builder()
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
package org.thoughtcrime.securesms.service.webrtc;
|
||||||
|
|
||||||
|
import android.util.LongSparseArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.CallException;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||||
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||||
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||||
|
import org.webrtc.VideoTrack;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base group call action processor that handles general callbacks around call members
|
||||||
|
* and call specific setup information that is the same for any group call state.
|
||||||
|
*/
|
||||||
|
public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||||
|
public GroupActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||||
|
super(webRtcInteractor, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRemoteDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupRemoteDeviceStateChanged():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
|
||||||
|
|
||||||
|
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);
|
||||||
|
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
|
||||||
|
CallParticipant callParticipant = participants.get(callParticipantId);
|
||||||
|
|
||||||
|
BroadcastVideoSink videoSink;
|
||||||
|
VideoTrack videoTrack = device.getVideoTrack();
|
||||||
|
if (videoTrack != null) {
|
||||||
|
videoSink = (callParticipant != null && callParticipant.getVideoSink().getEglBase() != null) ? callParticipant.getVideoSink()
|
||||||
|
: new BroadcastVideoSink(currentState.getVideoState().requireEglBase());
|
||||||
|
videoTrack.addSink(videoSink);
|
||||||
|
} else {
|
||||||
|
videoSink = new BroadcastVideoSink(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.putParticipant(callParticipantId,
|
||||||
|
CallParticipant.createRemote(recipient,
|
||||||
|
null,
|
||||||
|
videoSink,
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
|
||||||
|
Log.i(tag, "handleGroupRequestMembershipProof():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
try {
|
||||||
|
groupCall.setMembershipProof(groupMembershipToken);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to set group membership proof", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRequestUpdateMembers(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupRequestUpdateMembers():");
|
||||||
|
|
||||||
|
Recipient group = currentState.getCallInfoState().getCallRecipient();
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
|
||||||
|
List<GroupCall.GroupMemberInfo> members = Stream.of(GroupManager.getUuidCipherTexts(context, group.requireGroupId().requireV2()))
|
||||||
|
.map(e -> new GroupCall.GroupMemberInfo(e.getKey(), e.getValue().serialize()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setGroupMembers(new ArrayList<>(members));
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable set group members", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
|
||||||
|
|
||||||
|
ArrayList<GroupCall.RenderedResolution> renderedResolutions = new ArrayList<>(participants.size());
|
||||||
|
for (Map.Entry<CallParticipantId, CallParticipant> entry : participants.entrySet()) {
|
||||||
|
BroadcastVideoSink.RequestedSize maxSize = entry.getValue().getVideoSink().getMaxRequestingSize();
|
||||||
|
renderedResolutions.add(new GroupCall.RenderedResolution(entry.getKey().getDemuxId(), maxSize.getWidth(), maxSize.getHeight(), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentState.getCallInfoState().requireGroupCall().setRenderedResolutions(renderedResolutions);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to set rendered resolutions", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
|
||||||
|
try {
|
||||||
|
webRtcInteractor.getCallManager().receivedHttpResponse(httpData.getRequestId(), httpData.getStatus(), httpData.getBody() != null ? httpData.getBody() : new byte[0]);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to process received http response", e);
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
|
||||||
|
try {
|
||||||
|
webRtcInteractor.getCallManager().httpRequestFailed(httpData.getRequestId());
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to process received http response", e);
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSendOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||||
|
Log.i(tag, "handleSendOpaqueMessage():");
|
||||||
|
|
||||||
|
OpaqueMessage opaqueMessage = new OpaqueMessage(opaqueMessageMetadata.getOpaque());
|
||||||
|
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOpaque(opaqueMessage, true, null);
|
||||||
|
|
||||||
|
webRtcInteractor.sendOpaqueCallMessage(opaqueMessageMetadata.getUuid(), callMessage);
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||||
|
Log.i(tag, "handleReceivedOpaqueMessage():");
|
||||||
|
|
||||||
|
try {
|
||||||
|
webRtcInteractor.getCallManager().receivedCallMessage(opaqueMessageMetadata.getUuid(),
|
||||||
|
opaqueMessageMetadata.getRemoteDeviceId(),
|
||||||
|
1,
|
||||||
|
opaqueMessageMetadata.getOpaque(),
|
||||||
|
opaqueMessageMetadata.getMessageAgeSeconds());
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to receive opaque message", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull WebRtcServiceState groupCallFailure(@NonNull WebRtcServiceState currentState, @NonNull String message, @NonNull Throwable error) {
|
||||||
|
Log.w(tag, "groupCallFailure(): " + message, error);
|
||||||
|
|
||||||
|
currentState = currentState.builder()
|
||||||
|
.changeCallInfoState()
|
||||||
|
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
webRtcInteractor.sendMessage(currentState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
|
||||||
|
if (groupCall != null) {
|
||||||
|
groupCall.disconnect();
|
||||||
|
}
|
||||||
|
webRtcInteractor.getCallManager().reset();
|
||||||
|
} catch (CallException e) {
|
||||||
|
Log.w(tag, "Unable to reset call manager: ", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return terminateGroupCall(currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) {
|
||||||
|
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||||
|
webRtcInteractor.stopForegroundService();
|
||||||
|
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||||
|
webRtcInteractor.stopAudio(playDisconnectSound);
|
||||||
|
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||||
|
|
||||||
|
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||||
|
|
||||||
|
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||||
|
|
||||||
|
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.thoughtcrime.securesms.service.webrtc;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.CallException;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process actions for when the call has at least once been connected and joined.
|
||||||
|
*/
|
||||||
|
public class GroupConnectedActionProcessor extends GroupActionProcessor {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(GroupConnectedActionProcessor.class);
|
||||||
|
|
||||||
|
public GroupConnectedActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||||
|
super(webRtcInteractor, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||||
|
Log.i(TAG, "handleSetEnableVideo():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
Camera camera = currentState.getVideoState().requireCamera();
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setOutgoingVideoMuted(!enable);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable set video muted", e);
|
||||||
|
}
|
||||||
|
camera.setEnabled(enable);
|
||||||
|
|
||||||
|
currentState = currentState.builder()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.cameraState(camera.getCameraState())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||||
|
try {
|
||||||
|
currentState.getCallInfoState().requireGroupCall().setOutgoingAudioMuted(muted);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to set audio muted", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.isMicrophoneEnabled(!muted)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(TAG, "handleLocalHangup():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
try {
|
||||||
|
groupCall.disconnect();
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState = currentState.builder()
|
||||||
|
.changeCallInfoState()
|
||||||
|
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
webRtcInteractor.sendMessage(currentState);
|
||||||
|
|
||||||
|
return terminateGroupCall(currentState);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package org.thoughtcrime.securesms.service.webrtc;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.CallException;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||||
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process actions to go from lobby to a joined call.
|
||||||
|
*/
|
||||||
|
public class GroupJoiningActionProcessor extends GroupActionProcessor {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(GroupJoiningActionProcessor.class);
|
||||||
|
|
||||||
|
private final CallSetupActionProcessorDelegate callSetupDelegate;
|
||||||
|
|
||||||
|
public GroupJoiningActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||||
|
super(webRtcInteractor, TAG);
|
||||||
|
callSetupDelegate = new CallSetupActionProcessorDelegate(webRtcInteractor, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||||
|
|
||||||
|
Log.i(tag, "local device changed: " + device.getConnectionState() + " " + device.getJoinState());
|
||||||
|
|
||||||
|
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||||
|
|
||||||
|
switch (device.getConnectionState()) {
|
||||||
|
case NOT_CONNECTED:
|
||||||
|
case RECONNECTING:
|
||||||
|
builder.changeCallInfoState()
|
||||||
|
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||||
|
.commit();
|
||||||
|
break;
|
||||||
|
case CONNECTING:
|
||||||
|
case CONNECTED:
|
||||||
|
if (device.getJoinState() == GroupCall.JoinState.JOINED) {
|
||||||
|
|
||||||
|
webRtcInteractor.startAudioCommunication(true);
|
||||||
|
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||||
|
|
||||||
|
if (currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||||
|
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
||||||
|
} else {
|
||||||
|
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, currentState.getCallInfoState().getCallRecipient());
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setOutgoingVideoMuted(!currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||||
|
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||||
|
} catch (CallException e) {
|
||||||
|
Log.e(tag, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.changeCallInfoState()
|
||||||
|
.callState(WebRtcViewModel.State.CALL_CONNECTED)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED)
|
||||||
|
.callConnectedTime(System.currentTimeMillis())
|
||||||
|
.commit()
|
||||||
|
.actionProcessor(new GroupConnectedActionProcessor(webRtcInteractor))
|
||||||
|
.build();
|
||||||
|
} else if (device.getJoinState() == GroupCall.JoinState.JOINING) {
|
||||||
|
builder.changeCallInfoState()
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||||
|
.commit();
|
||||||
|
} else {
|
||||||
|
builder.changeCallInfoState()
|
||||||
|
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleLocalHangup():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
try {
|
||||||
|
groupCall.disconnect();
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState = currentState.builder()
|
||||||
|
.changeCallInfoState()
|
||||||
|
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
webRtcInteractor.sendMessage(currentState);
|
||||||
|
|
||||||
|
return terminateGroupCall(currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
Camera camera = currentState.getVideoState().requireCamera();
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setOutgoingVideoMuted(!enable);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to set video muted", e);
|
||||||
|
}
|
||||||
|
camera.setEnabled(enable);
|
||||||
|
|
||||||
|
currentState = currentState.builder()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.cameraState(camera.getCameraState())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||||
|
try {
|
||||||
|
currentState.getCallInfoState().requireGroupCall().setOutgoingAudioMuted(muted);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to set audio muted", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.isMicrophoneEnabled(!muted)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package org.thoughtcrime.securesms.service.webrtc;
|
||||||
|
|
||||||
|
import android.media.AudioManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.CallException;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||||
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process actions while the user is in the pre-join lobby for the call.
|
||||||
|
*/
|
||||||
|
public class GroupPreJoinActionProcessor extends GroupActionProcessor {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(GroupPreJoinActionProcessor.class);
|
||||||
|
|
||||||
|
public GroupPreJoinActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||||
|
super(webRtcInteractor, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||||
|
Log.i(TAG, "handlePreJoinCall():");
|
||||||
|
|
||||||
|
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
|
||||||
|
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
|
||||||
|
currentState.getVideoState().requireEglBase(),
|
||||||
|
webRtcInteractor.getGroupCallObserver());
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setOutgoingAudioMuted(true);
|
||||||
|
groupCall.setOutgoingVideoMuted(true);
|
||||||
|
|
||||||
|
Log.i(TAG, "Connecting to group call: " + currentState.getCallInfoState().getCallRecipient().getId());
|
||||||
|
groupCall.connect();
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to connect to group call", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.changeCallInfoState()
|
||||||
|
.groupCall(groupCall)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleCancelPreJoinCall(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(TAG, "handleCancelPreJoinCall():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
try {
|
||||||
|
groupCall.disconnect();
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||||
|
|
||||||
|
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||||
|
|
||||||
|
Log.i(tag, "local device changed: " + device.getConnectionState() + " " + device.getJoinState());
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.changeCallInfoState()
|
||||||
|
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupJoinedMembershipChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupJoinedMembershipChanged():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
|
||||||
|
List<Recipient> callParticipants = Stream.of(groupCall.getJoinedGroupMembers())
|
||||||
|
.map(uuid -> Recipient.externalPush(context, uuid, null, false))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder()
|
||||||
|
.changeCallInfoState();
|
||||||
|
|
||||||
|
for (Recipient recipient : callParticipants) {
|
||||||
|
builder.putParticipant(recipient, CallParticipant.createRemote(recipient, null, new BroadcastVideoSink(null), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState,
|
||||||
|
@NonNull RemotePeer remotePeer,
|
||||||
|
@NonNull OfferMessage.Type offerType)
|
||||||
|
{
|
||||||
|
Log.i(TAG, "handleOutgoingCall():");
|
||||||
|
|
||||||
|
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||||
|
|
||||||
|
currentState = WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||||
|
|
||||||
|
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
androidAudioManager.setSpeakerphoneOn(false);
|
||||||
|
|
||||||
|
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||||
|
webRtcInteractor.initializeAudioForCall();
|
||||||
|
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||||
|
webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, currentState.getCallInfoState().getCallRecipient());
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupCall.setOutgoingVideoSource(currentState.getVideoState().requireLocalSink(), currentState.getVideoState().requireCamera());
|
||||||
|
groupCall.setOutgoingVideoMuted(!currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||||
|
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||||
|
groupCall.setBandwidthMode(GroupCall.BandwidthMode.NORMAL);
|
||||||
|
|
||||||
|
groupCall.join();
|
||||||
|
} catch (CallException e) {
|
||||||
|
return groupCallFailure(currentState, "Unable to join group call", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.actionProcessor(new GroupJoiningActionProcessor(webRtcInteractor))
|
||||||
|
.changeCallInfoState()
|
||||||
|
.callState(WebRtcViewModel.State.CALL_OUTGOING)
|
||||||
|
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||||
|
Log.i(TAG, "handleSetEnableVideo(): Changing for pre-join group call. enable: " + enable);
|
||||||
|
|
||||||
|
currentState.getVideoState().requireCamera().setEnabled(enable);
|
||||||
|
return currentState.builder()
|
||||||
|
.changeCallSetupState()
|
||||||
|
.enableVideoOnCreate(enable)
|
||||||
|
.commit()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.cameraState(currentState.getVideoState().requireCamera().getCameraState())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||||
|
Log.i(TAG, "handleSetMuteAudio(): Changing for pre-join group call. muted: " + muted);
|
||||||
|
|
||||||
|
return currentState.builder()
|
||||||
|
.changeLocalDeviceState()
|
||||||
|
.isMicrophoneEnabled(!muted)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,6 @@ import org.webrtc.CapturerObserver;
|
||||||
import org.webrtc.VideoFrame;
|
import org.webrtc.VideoFrame;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action handler for when the system is at rest. Mainly responsible
|
* Action handler for when the system is at rest. Mainly responsible
|
||||||
* for starting pre-call state, starting an outgoing call, or receiving an
|
* for starting pre-call state, starting an outgoing call, or receiving an
|
||||||
|
@ -52,14 +50,21 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
||||||
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||||
Log.i(TAG, "handlePreJoinCall():");
|
Log.i(TAG, "handlePreJoinCall():");
|
||||||
|
|
||||||
WebRtcServiceState newState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
|
boolean isGroupCall = remotePeer.getRecipient().isPushV2Group();
|
||||||
|
WebRtcActionProcessor processor = isGroupCall ? new GroupPreJoinActionProcessor(webRtcInteractor)
|
||||||
|
: new PreJoinActionProcessor(webRtcInteractor);
|
||||||
|
|
||||||
return newState.builder()
|
currentState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
|
||||||
.actionProcessor(new PreJoinActionProcessor(webRtcInteractor))
|
|
||||||
.changeCallInfoState()
|
currentState = currentState.builder()
|
||||||
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
|
.actionProcessor(processor)
|
||||||
.callRecipient(remotePeer.getRecipient())
|
.changeCallInfoState()
|
||||||
.build();
|
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
|
||||||
|
.callRecipient(remotePeer.getRecipient())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return isGroupCall ? currentState.getActionProcessor().handlePreJoinCall(currentState, remotePeer)
|
||||||
|
: currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
|
private @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
|
||||||
|
|
|
@ -80,7 +80,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||||
boolean hideIp = !activePeer.getRecipient().isSystemContact() || isAlwaysTurn;
|
boolean hideIp = !activePeer.getRecipient().isSystemContact() || isAlwaysTurn;
|
||||||
VideoState videoState = currentState.getVideoState();
|
VideoState videoState = currentState.getVideoState();
|
||||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||||
|
|
|
@ -107,7 +107,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||||
try {
|
try {
|
||||||
VideoState videoState = currentState.getVideoState();
|
VideoState videoState = currentState.getVideoState();
|
||||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||||
|
|
||||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||||
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.CallMetadata;
|
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.CallMetadata;
|
||||||
|
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.HttpData;
|
||||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.OfferMetadata;
|
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.OfferMetadata;
|
||||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedOfferMetadata;
|
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedOfferMetadata;
|
||||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||||
|
@ -57,6 +58,14 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_SIGNALING_FAILURE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_SIGNALING_FAILURE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_TIMEOUT;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_TIMEOUT;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_FLIP_CAMERA;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_FLIP_CAMERA;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_HTTP_FAILURE;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_HTTP_SUCCESS;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_IS_IN_CALL_QUERY;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_IS_IN_CALL_QUERY;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_HANGUP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_HANGUP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING;
|
||||||
|
@ -71,6 +80,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIV
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_HANGUP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_HANGUP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_ICE_CANDIDATES;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_ICE_CANDIDATES;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_OFFER;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_OFFER;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_OPAQUE_MESSAGE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_RINGING;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_RINGING;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_VIDEO_ENABLE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_VIDEO_ENABLE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SCREEN_OFF;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SCREEN_OFF;
|
||||||
|
@ -79,6 +89,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_B
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_HANGUP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_HANGUP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_ICE_CANDIDATES;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_ICE_CANDIDATES;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_OFFER;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_OFFER;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_OPAQUE_MESSAGE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SETUP_FAILURE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SETUP_FAILURE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_SPEAKER;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_SPEAKER;
|
||||||
|
@ -90,20 +101,22 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_TURN_S
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BLUETOOTH;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BLUETOOTH;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CAMERA_STATE;
|
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_IS_ALWAYS_TURN;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_IS_ALWAYS_TURN;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MUTE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MUTE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RESULT_RECEIVER;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RESULT_RECEIVER;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
||||||
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.OpaqueMessageMetadata;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedAnswerMetadata;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedAnswerMetadata;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getAvailable;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getAvailable;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getBroadcastFlag;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getBroadcastFlag;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCallId;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCallId;
|
||||||
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCameraState;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getEnable;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getEnable;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorCallState;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorCallState;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorIdentityKey;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorIdentityKey;
|
||||||
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupMembershipToken;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceCandidates;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceCandidates;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers;
|
||||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType;
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType;
|
||||||
|
@ -189,7 +202,7 @@ public abstract class WebRtcActionProcessor {
|
||||||
case ACTION_SET_AUDIO_SPEAKER: return handleSetSpeakerAudio(currentState, intent.getBooleanExtra(EXTRA_SPEAKER, false));
|
case ACTION_SET_AUDIO_SPEAKER: return handleSetSpeakerAudio(currentState, intent.getBooleanExtra(EXTRA_SPEAKER, false));
|
||||||
case ACTION_SET_AUDIO_BLUETOOTH: return handleSetBluetoothAudio(currentState, intent.getBooleanExtra(EXTRA_BLUETOOTH, false));
|
case ACTION_SET_AUDIO_BLUETOOTH: return handleSetBluetoothAudio(currentState, intent.getBooleanExtra(EXTRA_BLUETOOTH, false));
|
||||||
case ACTION_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent));
|
case ACTION_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent));
|
||||||
case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, intent.getParcelableExtra(EXTRA_CAMERA_STATE));
|
case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent));
|
||||||
|
|
||||||
// End Call Actions
|
// End Call Actions
|
||||||
case ACTION_ENDED_REMOTE_HANGUP:
|
case ACTION_ENDED_REMOTE_HANGUP:
|
||||||
|
@ -208,6 +221,20 @@ public abstract class WebRtcActionProcessor {
|
||||||
|
|
||||||
// Local Call Failure Actions
|
// Local Call Failure Actions
|
||||||
case ACTION_SETUP_FAILURE: return handleSetupFailure(currentState, getCallId(intent));
|
case ACTION_SETUP_FAILURE: return handleSetupFailure(currentState, getCallId(intent));
|
||||||
|
|
||||||
|
// Group Calling
|
||||||
|
case ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED: return handleGroupLocalDeviceStateChanged(currentState);
|
||||||
|
case ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED: return handleGroupRemoteDeviceStateChanged(currentState);
|
||||||
|
case ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED: return handleGroupJoinedMembershipChanged(currentState);
|
||||||
|
case ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF: return handleGroupRequestMembershipProof(currentState, getGroupMembershipToken(intent));
|
||||||
|
case ACTION_GROUP_REQUEST_UPDATE_MEMBERS: return handleGroupRequestUpdateMembers(currentState);
|
||||||
|
case ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS: return handleUpdateRenderedResolutions(currentState);
|
||||||
|
|
||||||
|
case ACTION_HTTP_SUCCESS: return handleHttpSuccess(currentState, HttpData.fromIntent(intent));
|
||||||
|
case ACTION_HTTP_FAILURE: return handleHttpFailure(currentState, HttpData.fromIntent(intent));
|
||||||
|
|
||||||
|
case ACTION_SEND_OPAQUE_MESSAGE: return handleSendOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
|
||||||
|
case ACTION_RECEIVE_OPAQUE_MESSAGE: return handleReceivedOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentState;
|
return currentState;
|
||||||
|
@ -275,8 +302,8 @@ public abstract class WebRtcActionProcessor {
|
||||||
//region Incoming call
|
//region Incoming call
|
||||||
|
|
||||||
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
|
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
|
||||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
@NonNull CallMetadata callMetadata,
|
||||||
@NonNull WebRtcData.OfferMetadata offerMetadata,
|
@NonNull OfferMetadata offerMetadata,
|
||||||
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
|
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
|
||||||
{
|
{
|
||||||
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||||
|
@ -386,7 +413,7 @@ public abstract class WebRtcActionProcessor {
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected @NonNull WebRtcServiceState handleSendBusy(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast) {
|
protected @NonNull WebRtcServiceState handleSendBusy(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast) {
|
||||||
Log.i(tag, "handleSendBusy(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
Log.i(tag, "handleSendBusy(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||||
|
|
||||||
BusyMessage busyMessage = new BusyMessage(callMetadata.getCallId().longValue());
|
BusyMessage busyMessage = new BusyMessage(callMetadata.getCallId().longValue());
|
||||||
|
@ -473,7 +500,7 @@ public abstract class WebRtcActionProcessor {
|
||||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||||
|
|
||||||
if (errorCallState == WebRtcViewModel.State.UNTRUSTED_IDENTITY) {
|
if (errorCallState == WebRtcViewModel.State.UNTRUSTED_IDENTITY) {
|
||||||
CallParticipant participant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
CallParticipant participant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||||
CallParticipant untrusted = participant.withIdentityKey(identityKey.get());
|
CallParticipant untrusted = participant.withIdentityKey(identityKey.get());
|
||||||
|
|
||||||
builder.changeCallInfoState()
|
builder.changeCallInfoState()
|
||||||
|
@ -648,4 +675,68 @@ public abstract class WebRtcActionProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
//region Group Calling
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupLocalDeviceStateChanged not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRemoteDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupRemoteDeviceStateChanged not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupJoinedMembershipChanged(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupJoinedMembershipChanged not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
|
||||||
|
Log.i(tag, "handleGroupRequestMembershipProof not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleGroupRequestUpdateMembers(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleGroupRequestUpdateMembers not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
|
||||||
|
Log.i(tag, "handleUpdateRenderedResolutions not processed");
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {
|
||||||
|
try {
|
||||||
|
webRtcInteractor.getCallManager().receivedHttpResponse(httpData.getRequestId(), httpData.getStatus(), httpData.getBody() != null ? httpData.getBody() : new byte[0]);
|
||||||
|
} catch (CallException e) {
|
||||||
|
return callFailure(currentState, "Unable to process received http response", e);
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {
|
||||||
|
try {
|
||||||
|
webRtcInteractor.getCallManager().httpRequestFailed(httpData.getRequestId());
|
||||||
|
} catch (CallException e) {
|
||||||
|
return callFailure(currentState, "Unable to process received http response", e);
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleSendOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||||
|
Log.i(tag, "handleSendOpaqueMessage not processed");
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||||
|
Log.i(tag, "handleReceivedOpaqueMessage not processed");
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,18 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_DEVICE_ID;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_DEVICE_ID;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_IS_LEGACY;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_IS_LEGACY;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_TYPE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_TYPE;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_REQUEST_ID;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_RESPONSE_BODY;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_RESPONSE_STATUS;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MESSAGE_AGE_SECONDS;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_DELIVERED_TIMESTAMP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_DELIVERED_TIMESTAMP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP;
|
||||||
|
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemoteDevice;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of classes to ease parsing data from intents and passing said data
|
* Collection of classes to ease parsing data from intents and passing said data
|
||||||
|
@ -224,4 +231,77 @@ public class WebRtcData {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http response data.
|
||||||
|
*/
|
||||||
|
static class HttpData {
|
||||||
|
private final long requestId;
|
||||||
|
private final int status;
|
||||||
|
private final byte[] body;
|
||||||
|
|
||||||
|
static @NonNull HttpData fromIntent(@NonNull Intent intent) {
|
||||||
|
return new HttpData(intent.getLongExtra(EXTRA_HTTP_REQUEST_ID, -1),
|
||||||
|
intent.getIntExtra(EXTRA_HTTP_RESPONSE_STATUS, -1),
|
||||||
|
intent.getByteArrayExtra(EXTRA_HTTP_RESPONSE_BODY));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpData(long requestId, int status, @Nullable byte[] body) {
|
||||||
|
this.requestId = requestId;
|
||||||
|
this.status = status;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable byte[] getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An opaque calling message.
|
||||||
|
*/
|
||||||
|
static class OpaqueMessageMetadata {
|
||||||
|
private final UUID uuid;
|
||||||
|
private final byte[] opaque;
|
||||||
|
private final int remoteDeviceId;
|
||||||
|
private final long messageAgeSeconds;
|
||||||
|
|
||||||
|
static @NonNull OpaqueMessageMetadata fromIntent(@NonNull Intent intent) {
|
||||||
|
return new OpaqueMessageMetadata(WebRtcIntentParser.getUuid(intent),
|
||||||
|
WebRtcIntentParser.getOpaque(intent),
|
||||||
|
getRemoteDevice(intent),
|
||||||
|
intent.getLongExtra(EXTRA_MESSAGE_AGE_SECONDS, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpaqueMessageMetadata(@NonNull UUID uuid, @NonNull byte[] opaque, int remoteDeviceId, long messageAgeSeconds) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.opaque = opaque;
|
||||||
|
this.remoteDeviceId = remoteDeviceId;
|
||||||
|
this.messageAgeSeconds = messageAgeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull byte[] getOpaque() {
|
||||||
|
return opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRemoteDeviceId() {
|
||||||
|
return remoteDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getMessageAgeSeconds() {
|
||||||
|
return messageAgeSeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.signal.ringrtc.CallId;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||||
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel;
|
import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel;
|
||||||
|
@ -18,29 +19,35 @@ import org.webrtc.PeerConnection;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_OPAQUE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_OPAQUE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_SDP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_SDP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_AVAILABLE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_AVAILABLE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BROADCAST;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BROADCAST;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CALL_ID;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CALL_ID;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CAMERA_STATE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ENABLE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ENABLE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_CALL_STATE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_CALL_STATE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_IDENTITY_KEY;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_IDENTITY_KEY;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_EXTERNAL_TOKEN;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ICE_CANDIDATES;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ICE_CANDIDATES;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MULTI_RING;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MULTI_RING;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_OPAQUE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_OPAQUE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_SDP;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_SDP;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_TYPE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_TYPE;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OPAQUE_MESSAGE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_DEVICE;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_DEVICE;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_PEER_KEY;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_PEER_KEY;
|
||||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_TURN_SERVER_INFO;
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_TURN_SERVER_INFO;
|
||||||
|
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to parse the various attributes out of intents passed to the service.
|
* Helper to parse the various attributes out of intents passed to the service.
|
||||||
|
@ -111,6 +118,14 @@ public final class WebRtcIntentParser {
|
||||||
return intent.getByteArrayExtra(EXTRA_OFFER_OPAQUE);
|
return intent.getByteArrayExtra(EXTRA_OFFER_OPAQUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull byte[] getOpaque(@NonNull Intent intent) {
|
||||||
|
return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_OPAQUE_MESSAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull UUID getUuid(@NonNull Intent intent) {
|
||||||
|
return UuidUtil.parseOrThrow(intent.getStringExtra(EXTRA_UUID));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean getBroadcastFlag(@NonNull Intent intent) {
|
public static boolean getBroadcastFlag(@NonNull Intent intent) {
|
||||||
return intent.getBooleanExtra(EXTRA_BROADCAST, false);
|
return intent.getBooleanExtra(EXTRA_BROADCAST, false);
|
||||||
}
|
}
|
||||||
|
@ -149,10 +164,17 @@ public final class WebRtcIntentParser {
|
||||||
return intent.getBooleanExtra(EXTRA_ENABLE, false);
|
return intent.getBooleanExtra(EXTRA_ENABLE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull byte[] getGroupMembershipToken(@NonNull Intent intent) {
|
||||||
|
return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_GROUP_EXTERNAL_TOKEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull CameraState getCameraState(@NonNull Intent intent) {
|
||||||
|
return Objects.requireNonNull(intent.getParcelableExtra(EXTRA_CAMERA_STATE));
|
||||||
|
}
|
||||||
public static @NonNull WebRtcViewModel.State getErrorCallState(@NonNull Intent intent) {
|
public static @NonNull WebRtcViewModel.State getErrorCallState(@NonNull Intent intent) {
|
||||||
return (WebRtcViewModel.State) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_ERROR_CALL_STATE));
|
return (WebRtcViewModel.State) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_ERROR_CALL_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull Optional<IdentityKey> getErrorIdentityKey(@NonNull Intent intent) {
|
public static @NonNull Optional<IdentityKey> getErrorIdentityKey(@NonNull Intent intent) {
|
||||||
IdentityKeyParcelable identityKeyParcelable = (IdentityKeyParcelable) intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY);
|
IdentityKeyParcelable identityKeyParcelable = (IdentityKeyParcelable) intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY);
|
||||||
if (identityKeyParcelable != null) {
|
if (identityKeyParcelable != null) {
|
||||||
|
|
|
@ -6,6 +6,9 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||||
|
@ -16,6 +19,8 @@ import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serves as the bridge between the action processing framework as the WebRTC service. Attempts
|
* Serves as the bridge between the action processing framework as the WebRTC service. Attempts
|
||||||
* to minimize direct access to various managers by providing a simple proxy to them. Due to the
|
* to minimize direct access to various managers by providing a simple proxy to them. Due to the
|
||||||
|
@ -23,19 +28,21 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess
|
||||||
*/
|
*/
|
||||||
public class WebRtcInteractor {
|
public class WebRtcInteractor {
|
||||||
|
|
||||||
@NonNull private final WebRtcCallService webRtcCallService;
|
@NonNull private final WebRtcCallService webRtcCallService;
|
||||||
@NonNull private final CallManager callManager;
|
@NonNull private final CallManager callManager;
|
||||||
@NonNull private final LockManager lockManager;
|
@NonNull private final LockManager lockManager;
|
||||||
@NonNull private final SignalAudioManager audioManager;
|
@NonNull private final SignalAudioManager audioManager;
|
||||||
@NonNull private final BluetoothStateManager bluetoothStateManager;
|
@NonNull private final BluetoothStateManager bluetoothStateManager;
|
||||||
@NonNull private final CameraEventListener cameraEventListener;
|
@NonNull private final CameraEventListener cameraEventListener;
|
||||||
|
@NonNull private final GroupCall.Observer groupCallObserver;
|
||||||
|
|
||||||
public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService,
|
public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService,
|
||||||
@NonNull CallManager callManager,
|
@NonNull CallManager callManager,
|
||||||
@NonNull LockManager lockManager,
|
@NonNull LockManager lockManager,
|
||||||
@NonNull SignalAudioManager audioManager,
|
@NonNull SignalAudioManager audioManager,
|
||||||
@NonNull BluetoothStateManager bluetoothStateManager,
|
@NonNull BluetoothStateManager bluetoothStateManager,
|
||||||
@NonNull CameraEventListener cameraEventListener)
|
@NonNull CameraEventListener cameraEventListener,
|
||||||
|
@NonNull GroupCall.Observer groupCallObserver)
|
||||||
{
|
{
|
||||||
this.webRtcCallService = webRtcCallService;
|
this.webRtcCallService = webRtcCallService;
|
||||||
this.callManager = callManager;
|
this.callManager = callManager;
|
||||||
|
@ -43,6 +50,7 @@ public class WebRtcInteractor {
|
||||||
this.audioManager = audioManager;
|
this.audioManager = audioManager;
|
||||||
this.bluetoothStateManager = bluetoothStateManager;
|
this.bluetoothStateManager = bluetoothStateManager;
|
||||||
this.cameraEventListener = cameraEventListener;
|
this.cameraEventListener = cameraEventListener;
|
||||||
|
this.groupCallObserver = groupCallObserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull CameraEventListener getCameraEventListener() {
|
@NonNull CameraEventListener getCameraEventListener() {
|
||||||
|
@ -57,6 +65,10 @@ public class WebRtcInteractor {
|
||||||
return webRtcCallService;
|
return webRtcCallService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull GroupCall.Observer getGroupCallObserver() {
|
||||||
|
return groupCallObserver;
|
||||||
|
}
|
||||||
|
|
||||||
void setWantsBluetoothConnection(boolean enabled) {
|
void setWantsBluetoothConnection(boolean enabled) {
|
||||||
bluetoothStateManager.setWantsConnection(enabled);
|
bluetoothStateManager.setWantsConnection(enabled);
|
||||||
}
|
}
|
||||||
|
@ -73,8 +85,16 @@ public class WebRtcInteractor {
|
||||||
webRtcCallService.sendCallMessage(remotePeer, callMessage);
|
webRtcCallService.sendCallMessage(remotePeer, callMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage callMessage) {
|
||||||
|
webRtcCallService.sendOpaqueCallMessage(uuid, callMessage);
|
||||||
|
}
|
||||||
|
|
||||||
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
||||||
webRtcCallService.setCallInProgressNotification(type, remotePeer);
|
webRtcCallService.setCallInProgressNotification(type, remotePeer.getRecipient());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCallInProgressNotification(int type, @NonNull Recipient recipient) {
|
||||||
|
webRtcCallService.setCallInProgressNotification(type, recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
|
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import android.media.AudioManager;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
@ -56,4 +58,17 @@ public final class WebRtcUtil {
|
||||||
androidAudioManager.setSpeakerphoneOn(true);
|
androidAudioManager.setSpeakerphoneOn(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull WebRtcViewModel.GroupCallState groupCallStateForConnection(@NonNull GroupCall.ConnectionState connectionState) {
|
||||||
|
switch (connectionState) {
|
||||||
|
case CONNECTING:
|
||||||
|
return WebRtcViewModel.GroupCallState.CONNECTING;
|
||||||
|
case CONNECTED:
|
||||||
|
return WebRtcViewModel.GroupCallState.CONNECTED;
|
||||||
|
case RECONNECTING:
|
||||||
|
return WebRtcViewModel.GroupCallState.RECONNECTING;
|
||||||
|
default:
|
||||||
|
return WebRtcViewModel.GroupCallState.DISCONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.service.webrtc.state;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
|
@ -12,6 +14,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -20,27 +23,31 @@ import java.util.Objects;
|
||||||
*/
|
*/
|
||||||
public class CallInfoState {
|
public class CallInfoState {
|
||||||
|
|
||||||
WebRtcViewModel.State callState;
|
WebRtcViewModel.State callState;
|
||||||
Recipient callRecipient;
|
Recipient callRecipient;
|
||||||
long callConnectedTime;
|
long callConnectedTime;
|
||||||
Map<Recipient, CallParticipant> remoteParticipants;
|
Map<CallParticipantId, CallParticipant> remoteParticipants;
|
||||||
Map<Integer, RemotePeer> peerMap;
|
Map<Integer, RemotePeer> peerMap;
|
||||||
RemotePeer activePeer;
|
RemotePeer activePeer;
|
||||||
|
GroupCall groupCall;
|
||||||
|
WebRtcViewModel.GroupCallState groupState;
|
||||||
|
|
||||||
public CallInfoState() {
|
public CallInfoState() {
|
||||||
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null);
|
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null, null, WebRtcViewModel.GroupCallState.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallInfoState(@NonNull CallInfoState toCopy) {
|
public CallInfoState(@NonNull CallInfoState toCopy) {
|
||||||
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer);
|
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer, toCopy.groupCall, toCopy.groupState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallInfoState(@NonNull WebRtcViewModel.State callState,
|
public CallInfoState(@NonNull WebRtcViewModel.State callState,
|
||||||
@NonNull Recipient callRecipient,
|
@NonNull Recipient callRecipient,
|
||||||
long callConnectedTime,
|
long callConnectedTime,
|
||||||
@NonNull Map<Recipient, CallParticipant> remoteParticipants,
|
@NonNull Map<CallParticipantId, CallParticipant> remoteParticipants,
|
||||||
@NonNull Map<Integer, RemotePeer> peerMap,
|
@NonNull Map<Integer, RemotePeer> peerMap,
|
||||||
@Nullable RemotePeer activePeer)
|
@Nullable RemotePeer activePeer,
|
||||||
|
@Nullable GroupCall groupCall,
|
||||||
|
@NonNull WebRtcViewModel.GroupCallState groupState)
|
||||||
{
|
{
|
||||||
this.callState = callState;
|
this.callState = callState;
|
||||||
this.callRecipient = callRecipient;
|
this.callRecipient = callRecipient;
|
||||||
|
@ -48,6 +55,8 @@ public class CallInfoState {
|
||||||
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
|
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
|
||||||
this.peerMap = new HashMap<>(peerMap);
|
this.peerMap = new HashMap<>(peerMap);
|
||||||
this.activePeer = activePeer;
|
this.activePeer = activePeer;
|
||||||
|
this.groupCall = groupCall;
|
||||||
|
this.groupState = groupState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Recipient getCallRecipient() {
|
public @NonNull Recipient getCallRecipient() {
|
||||||
|
@ -58,11 +67,19 @@ public class CallInfoState {
|
||||||
return callConnectedTime;
|
return callConnectedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable CallParticipant getRemoteParticipant(@NonNull Recipient recipient) {
|
public @NonNull Map<CallParticipantId, CallParticipant> getRemoteCallParticipantsMap() {
|
||||||
return remoteParticipants.get(recipient);
|
return new LinkedHashMap<>(remoteParticipants);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull ArrayList<CallParticipant> getRemoteCallParticipants() {
|
public @Nullable CallParticipant getRemoteCallParticipant(@NonNull Recipient recipient) {
|
||||||
|
return getRemoteCallParticipant(new CallParticipantId(recipient));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable CallParticipant getRemoteCallParticipant(@NonNull CallParticipantId callParticipantId) {
|
||||||
|
return remoteParticipants.get(callParticipantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull List<CallParticipant> getRemoteCallParticipants() {
|
||||||
return new ArrayList<>(remoteParticipants.values());
|
return new ArrayList<>(remoteParticipants.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,4 +98,16 @@ public class CallInfoState {
|
||||||
public @NonNull RemotePeer requireActivePeer() {
|
public @NonNull RemotePeer requireActivePeer() {
|
||||||
return Objects.requireNonNull(activePeer);
|
return Objects.requireNonNull(activePeer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable GroupCall getGroupCall() {
|
||||||
|
return groupCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull GroupCall requireGroupCall() {
|
||||||
|
return Objects.requireNonNull(groupCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull WebRtcViewModel.GroupCallState getGroupCallState() {
|
||||||
|
return groupState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.service.webrtc.state;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.ringrtc.GroupCall;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||||
|
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||||
|
@ -192,8 +194,18 @@ public class WebRtcServiceStateBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull CallInfoStateBuilder putParticipant(@NonNull CallParticipantId callParticipantId, @NonNull CallParticipant callParticipant) {
|
||||||
|
toBuild.remoteParticipants.put(callParticipantId, callParticipant);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull CallInfoStateBuilder putParticipant(@NonNull Recipient recipient, @NonNull CallParticipant callParticipant) {
|
public @NonNull CallInfoStateBuilder putParticipant(@NonNull Recipient recipient, @NonNull CallParticipant callParticipant) {
|
||||||
toBuild.remoteParticipants.put(recipient, callParticipant);
|
toBuild.remoteParticipants.put(new CallParticipantId(recipient), callParticipant);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull CallInfoStateBuilder clearParticipantMap() {
|
||||||
|
toBuild.remoteParticipants.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,5 +228,15 @@ public class WebRtcServiceStateBuilder {
|
||||||
toBuild.activePeer = activePeer;
|
toBuild.activePeer = activePeer;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull CallInfoStateBuilder groupCall(@Nullable GroupCall groupCall) {
|
||||||
|
toBuild.groupCall = groupCall;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull CallInfoStateBuilder groupCallState(@Nullable WebRtcViewModel.GroupCallState groupState) {
|
||||||
|
toBuild.groupState = groupState;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ public final class FeatureFlags {
|
||||||
private static final String MAX_ENVELOPE_SIZE = "android.maxEnvelopeSize";
|
private static final String MAX_ENVELOPE_SIZE = "android.maxEnvelopeSize";
|
||||||
private static final String GV1_AUTO_MIGRATE_VERSION = "android.groupsv2.autoMigrateVersion";
|
private static final String GV1_AUTO_MIGRATE_VERSION = "android.groupsv2.autoMigrateVersion";
|
||||||
private static final String GV1_MANUAL_MIGRATE_VERSION = "android.groupsv2.manualMigrateVersion";
|
private static final String GV1_MANUAL_MIGRATE_VERSION = "android.groupsv2.manualMigrateVersion";
|
||||||
|
private static final String GROUP_CALLING_VERSION = "android.groupsv2.callingVersion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||||
|
@ -84,7 +85,8 @@ public final class FeatureFlags {
|
||||||
VIEWED_RECEIPTS,
|
VIEWED_RECEIPTS,
|
||||||
MAX_ENVELOPE_SIZE,
|
MAX_ENVELOPE_SIZE,
|
||||||
GV1_AUTO_MIGRATE_VERSION,
|
GV1_AUTO_MIGRATE_VERSION,
|
||||||
GV1_MANUAL_MIGRATE_VERSION
|
GV1_MANUAL_MIGRATE_VERSION,
|
||||||
|
GROUP_CALLING_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -274,6 +276,10 @@ public final class FeatureFlags {
|
||||||
return groupsV1AutoMigration() && getVersionFlag(GV1_MANUAL_MIGRATE_VERSION) == VersionFlag.ON;
|
return groupsV1AutoMigration() && getVersionFlag(GV1_MANUAL_MIGRATE_VERSION) == VersionFlag.ON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean groupCalling() {
|
||||||
|
return getVersionFlag(GROUP_CALLING_VERSION) == VersionFlag.ON;
|
||||||
|
}
|
||||||
|
|
||||||
/** Only for rendering debug info. */
|
/** Only for rendering debug info. */
|
||||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||||
return new TreeMap<>(REMOTE_VALUES);
|
return new TreeMap<>(REMOTE_VALUES);
|
||||||
|
|
|
@ -36,8 +36,11 @@ import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.LayoutRes;
|
import androidx.annotation.LayoutRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
|
@ -276,4 +279,20 @@ public final class ViewUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @Nullable Lifecycle getActivityLifecycle(@NonNull View view) {
|
||||||
|
return getActivityLifecycle(view.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Lifecycle getActivityLifecycle(@Nullable Context context) {
|
||||||
|
if (context instanceof ContextThemeWrapper) {
|
||||||
|
return getActivityLifecycle(((ContextThemeWrapper) context).getBaseContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof AppCompatActivity) {
|
||||||
|
return ((AppCompatActivity) context).getLifecycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
5
app/src/main/res/color/core_green_text_button.xml
Normal file
5
app/src/main/res/color/core_green_text_button.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/core_white" android:state_enabled="true" />
|
||||||
|
<item android:color="@color/transparent_white_40" />
|
||||||
|
</selector>
|
|
@ -9,14 +9,12 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
android:id="@+id/call_participant_item_avatar"
|
android:id="@+id/call_participant_item_avatar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="112dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="112dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="h,1:1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintWidth_percent="0.5"
|
|
||||||
tools:srcCompat="@tools:sample/avatars" />
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
27
app/src/main/res/layout/group_call_participant_item.xml
Normal file
27
app/src/main/res/layout/group_call_participant_item.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
tools:layout_height="match_parent"
|
||||||
|
tools:layout_width="match_parent">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/group_call_participant_card_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@null"
|
||||||
|
android:clipChildren="true"
|
||||||
|
app:cardCornerRadius="10dp">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/group_call_participant"
|
||||||
|
layout="@layout/call_participant_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Space xmlns:android="http://schemas.android.com/apk/res/android"
|
<Space xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="72dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="72dp"
|
android:layout_height="72dp"
|
||||||
tools:background="@color/red"
|
tools:background="@color/red"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
|
@ -4,6 +4,9 @@
|
||||||
android:id="@+id/call_screen_call_participants"
|
android:id="@+id/call_screen_call_participants"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
app:alignContent="stretch"
|
app:alignContent="stretch"
|
||||||
|
app:alignItems="stretch"
|
||||||
app:flexDirection="row"
|
app:flexDirection="row"
|
||||||
app:flexWrap="wrap" />
|
app:flexWrap="wrap"
|
||||||
|
app:justifyContent="flex_start" />
|
|
@ -4,12 +4,6 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:parentTag="org.thoughtcrime.securesms.components.webrtc.WebRtcCallView">
|
tools:parentTag="org.thoughtcrime.securesms.components.webrtc.WebRtcCallView">
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/call_screen_blur_overlay"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/transparent_black_40" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/call_screen_participants_parent"
|
android:id="@+id/call_screen_participants_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -316,7 +310,7 @@
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/WebRtcCallView__start_call"
|
android:text="@string/WebRtcCallView__start_call"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:textColor="@color/core_white"
|
android:textColor="@color/core_green_text_button"
|
||||||
app:backgroundTint="@color/core_green" />
|
app:backgroundTint="@color/core_green" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -3,11 +3,10 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
app:contentInsetStart="0dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="240dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal">
|
android:layout_gravity="center_horizontal">
|
||||||
|
|
||||||
|
@ -22,7 +21,9 @@
|
||||||
android:id="@+id/call_screen_recipient_name"
|
android:id="@+id/call_screen_recipient_name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
|
android:maxLines="2"
|
||||||
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
||||||
android:textColor="@color/core_white"
|
android:textColor="@color/core_white"
|
||||||
app:layout_constraintBottom_toTopOf="@id/action_bar_guideline"
|
app:layout_constraintBottom_toTopOf="@id/action_bar_guideline"
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textAppearance="@style/TextAppearance.Signal.Subtitle"
|
android:textAppearance="@style/TextAppearance.Signal.Body2"
|
||||||
android:textColor="@color/core_white"
|
android:textColor="@color/core_white"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
10
app/src/main/res/menu/conversation_callable_groupv2.xml
Normal file
10
app/src/main/res/menu/conversation_callable_groupv2.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item android:title="@string/conversation_callable_secure__menu_video"
|
||||||
|
android:id="@+id/menu_video_secure"
|
||||||
|
android:icon="@drawable/ic_video_solid_24"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -1332,9 +1332,23 @@
|
||||||
<string name="WebRtcCallView__signal_voice_call">Signal voice call…</string>
|
<string name="WebRtcCallView__signal_voice_call">Signal voice call…</string>
|
||||||
<string name="WebRtcCallView__signal_video_call">Signal video call…</string>
|
<string name="WebRtcCallView__signal_video_call">Signal video call…</string>
|
||||||
<string name="WebRtcCallView__start_call">Start Call</string>
|
<string name="WebRtcCallView__start_call">Start Call</string>
|
||||||
<string name="WebRtcCallView__group_call">Group Call</string>
|
<string name="WebRtcCallView__join_call">Join Call</string>
|
||||||
|
<string name="WebRtcCallView__s_group_call">\"%1$s\" Group Call</string>
|
||||||
<string name="WebRtcCallView__view_participants_list">View participants</string>
|
<string name="WebRtcCallView__view_participants_list">View participants</string>
|
||||||
<string name="WebRtcCallView__your_video_is_off">Your video is off</string>
|
<string name="WebRtcCallView__your_video_is_off">Your video is off</string>
|
||||||
|
<string name="WebRtcCallView__connecting">Connecting…</string>
|
||||||
|
<string name="WebRtcCallView__reconnecting">Reconnecting…</string>
|
||||||
|
<string name="WebRtcCallView__joining">Joining…</string>
|
||||||
|
<string name="WebRtcCallView__disconnected">Disconnected</string>
|
||||||
|
|
||||||
|
<string name="WebRtcCallView__no_one_else_is_here">No one else is here</string>
|
||||||
|
<string name="WebRtcCallView__s_is_in_this_call">%1$s is in this call</string>
|
||||||
|
<string name="WebRtcCallView__s_and_s_are_in_this_call">%1$s and %2$s are in this call</string>
|
||||||
|
|
||||||
|
<plurals name="WebRtcCallView__s_s_and_d_others_are_in_this_call">
|
||||||
|
<item quantity="one">%1$s, %2$s, and %3$d other are in this call</item>
|
||||||
|
<item quantity="other">%1$s, %2$s, and %3$d others are in this call</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- CallParticipantsListDialog -->
|
<!-- CallParticipantsListDialog -->
|
||||||
<plurals name="CallParticipantsListDialog_in_this_call_d_people">
|
<plurals name="CallParticipantsListDialog_in_this_call_d_people">
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||||
|
@ -89,7 +90,6 @@ import org.whispersystems.util.Base64;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.sql.Time;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -239,6 +239,20 @@ public class SignalServiceMessageSender {
|
||||||
sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), content, false, null);
|
sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), content, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an http request on behalf of the calling infrastructure.
|
||||||
|
*
|
||||||
|
* @param requestId Request identifier
|
||||||
|
* @param url Fully qualified URL to request
|
||||||
|
* @param httpMethod Http method to use (e.g., "GET", "POST")
|
||||||
|
* @param headers Optional list of headers to send with request
|
||||||
|
* @param body Optional body to send with request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public CallingResponse makeCallingRequest(long requestId, String url, String httpMethod, List<Pair<String, String>> headers, byte[] body) {
|
||||||
|
return socket.makeCallingRequest(requestId, url, httpMethod, headers, body);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message to a single recipient.
|
* Send a message to a single recipient.
|
||||||
*
|
*
|
||||||
|
@ -773,6 +787,8 @@ public class SignalServiceMessageSender {
|
||||||
}
|
}
|
||||||
} else if (callMessage.getBusyMessage().isPresent()) {
|
} else if (callMessage.getBusyMessage().isPresent()) {
|
||||||
builder.setBusy(CallMessage.Busy.newBuilder().setId(callMessage.getBusyMessage().get().getId()));
|
builder.setBusy(CallMessage.Busy.newBuilder().setId(callMessage.getBusyMessage().get().getId()));
|
||||||
|
} else if (callMessage.getOpaqueMessage().isPresent()) {
|
||||||
|
builder.setOpaque(CallMessage.Opaque.newBuilder().setData(ByteString.copyFrom(callMessage.getOpaqueMessage().get().getOpaque())));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setMultiRing(callMessage.isMultiRing());
|
builder.setMultiRing(callMessage.isMultiRing());
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.signal.storageservice.protos.groups.Group;
|
||||||
import org.signal.storageservice.protos.groups.GroupAttributeBlob;
|
import org.signal.storageservice.protos.groups.GroupAttributeBlob;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.GroupChanges;
|
import org.signal.storageservice.protos.groups.GroupChanges;
|
||||||
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
|
@ -166,6 +167,12 @@ public final class GroupsV2Api {
|
||||||
return socket.patchGroupsV2Group(groupChange, authorization.toString(), groupLinkPassword);
|
return socket.patchGroupsV2Group(groupChange, authorization.toString(), groupLinkPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GroupExternalCredential getGroupExternalCredential(GroupsV2AuthorizationString authorization)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return socket.getGroupExternalCredential(authorization);
|
||||||
|
}
|
||||||
|
|
||||||
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)
|
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||||
|
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 org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
||||||
|
@ -616,6 +617,9 @@ public final class SignalServiceContent {
|
||||||
} else if (content.hasBusy()) {
|
} else if (content.hasBusy()) {
|
||||||
SignalServiceProtos.CallMessage.Busy busy = content.getBusy();
|
SignalServiceProtos.CallMessage.Busy busy = content.getBusy();
|
||||||
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()), isMultiRing, destinationDeviceId);
|
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()), isMultiRing, destinationDeviceId);
|
||||||
|
} else if (content.hasOpaque()) {
|
||||||
|
SignalServiceProtos.CallMessage.Opaque opaque = content.getOpaque();
|
||||||
|
return SignalServiceCallMessage.forOpaque(new OpaqueMessage(opaque.getData().toByteArray()), isMultiRing, destinationDeviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SignalServiceCallMessage.empty();
|
return SignalServiceCallMessage.empty();
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.whispersystems.signalservice.api.messages.calls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate the response to an http request on behalf of ringrtc.
|
||||||
|
*/
|
||||||
|
public abstract class CallingResponse {
|
||||||
|
private final long requestId;
|
||||||
|
|
||||||
|
CallingResponse(long requestId) {
|
||||||
|
this.requestId = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Success extends CallingResponse {
|
||||||
|
private final int responseStatus;
|
||||||
|
private final byte[] responseBody;
|
||||||
|
|
||||||
|
public Success(long requestId, int responseStatus, byte[] responseBody) {
|
||||||
|
super(requestId);
|
||||||
|
this.responseStatus = responseStatus;
|
||||||
|
this.responseBody = responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponseStatus() {
|
||||||
|
return responseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getResponseBody() {
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Error extends CallingResponse {
|
||||||
|
private final Throwable throwable;
|
||||||
|
|
||||||
|
public Error(long requestId, Throwable throwable) {
|
||||||
|
super(requestId);
|
||||||
|
this.throwable = throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getThrowable() {
|
||||||
|
return throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.whispersystems.signalservice.api.messages.calls;
|
||||||
|
|
||||||
|
public class OpaqueMessage {
|
||||||
|
|
||||||
|
private final byte[] opaque;
|
||||||
|
|
||||||
|
public OpaqueMessage(byte[] opaque) {
|
||||||
|
this.opaque = opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOpaque() {
|
||||||
|
return opaque;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ public class SignalServiceCallMessage {
|
||||||
private final Optional<HangupMessage> hangupMessage;
|
private final Optional<HangupMessage> hangupMessage;
|
||||||
private final Optional<BusyMessage> busyMessage;
|
private final Optional<BusyMessage> busyMessage;
|
||||||
private final Optional<List<IceUpdateMessage>> iceUpdateMessages;
|
private final Optional<List<IceUpdateMessage>> iceUpdateMessages;
|
||||||
|
private final Optional<OpaqueMessage> opaqueMessage;
|
||||||
private final Optional<Integer> destinationDeviceId;
|
private final Optional<Integer> destinationDeviceId;
|
||||||
private final boolean isMultiRing;
|
private final boolean isMultiRing;
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional<List<IceUpdateMessage>> iceUpdateMessages,
|
Optional<List<IceUpdateMessage>> iceUpdateMessages,
|
||||||
Optional<HangupMessage> hangupMessage,
|
Optional<HangupMessage> hangupMessage,
|
||||||
Optional<BusyMessage> busyMessage,
|
Optional<BusyMessage> busyMessage,
|
||||||
|
Optional<OpaqueMessage> opaqueMessage,
|
||||||
boolean isMultiRing,
|
boolean isMultiRing,
|
||||||
Optional<Integer> destinationDeviceId)
|
Optional<Integer> destinationDeviceId)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +30,7 @@ public class SignalServiceCallMessage {
|
||||||
this.iceUpdateMessages = iceUpdateMessages;
|
this.iceUpdateMessages = iceUpdateMessages;
|
||||||
this.hangupMessage = hangupMessage;
|
this.hangupMessage = hangupMessage;
|
||||||
this.busyMessage = busyMessage;
|
this.busyMessage = busyMessage;
|
||||||
|
this.opaqueMessage = opaqueMessage;
|
||||||
this.isMultiRing = isMultiRing;
|
this.isMultiRing = isMultiRing;
|
||||||
this.destinationDeviceId = destinationDeviceId;
|
this.destinationDeviceId = destinationDeviceId;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +41,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -48,6 +52,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -58,6 +63,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.of(iceUpdateMessages),
|
Optional.of(iceUpdateMessages),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -71,6 +77,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.of(iceUpdateMessages),
|
Optional.of(iceUpdateMessages),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -81,6 +88,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.of(hangupMessage),
|
Optional.of(hangupMessage),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -91,6 +99,18 @@ public class SignalServiceCallMessage {
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.of(busyMessage),
|
Optional.of(busyMessage),
|
||||||
|
Optional.absent(),
|
||||||
|
isMultiRing,
|
||||||
|
Optional.fromNullable(destinationDeviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignalServiceCallMessage forOpaque(OpaqueMessage opaqueMessage, boolean isMultiRing, Integer destinationDeviceId) {
|
||||||
|
return new SignalServiceCallMessage(Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.of(opaqueMessage),
|
||||||
isMultiRing,
|
isMultiRing,
|
||||||
Optional.fromNullable(destinationDeviceId));
|
Optional.fromNullable(destinationDeviceId));
|
||||||
}
|
}
|
||||||
|
@ -102,7 +122,7 @@ public class SignalServiceCallMessage {
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
Optional.absent(),
|
Optional.absent(),
|
||||||
false,
|
Optional.absent(), false,
|
||||||
Optional.absent());
|
Optional.absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +146,10 @@ public class SignalServiceCallMessage {
|
||||||
return busyMessage;
|
return busyMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<OpaqueMessage> getOpaqueMessage() {
|
||||||
|
return opaqueMessage;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isMultiRing() {
|
public boolean isMultiRing() {
|
||||||
return isMultiRing;
|
return isMultiRing;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.signal.storageservice.protos.groups.AvatarUploadAttributes;
|
||||||
import org.signal.storageservice.protos.groups.Group;
|
import org.signal.storageservice.protos.groups.Group;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.GroupChanges;
|
import org.signal.storageservice.protos.groups.GroupChanges;
|
||||||
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
|
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
|
@ -38,6 +39,7 @@ import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
|
||||||
|
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||||
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
|
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||||
|
@ -204,6 +206,7 @@ public class PushServiceSocket {
|
||||||
private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s";
|
private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s";
|
||||||
private static final String GROUPSV2_AVATAR_REQUEST = "/v1/groups/avatar/form";
|
private static final String GROUPSV2_AVATAR_REQUEST = "/v1/groups/avatar/form";
|
||||||
private static final String GROUPSV2_GROUP_JOIN = "/v1/groups/join/%s";
|
private static final String GROUPSV2_GROUP_JOIN = "/v1/groups/join/%s";
|
||||||
|
private static final String GROUPSV2_TOKEN = "/v1/groups/token";
|
||||||
|
|
||||||
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
|
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
|
||||||
|
|
||||||
|
@ -1665,6 +1668,42 @@ public class PushServiceSocket {
|
||||||
throw new NonSuccessfulResponseCodeException("Response: " + response);
|
throw new NonSuccessfulResponseCodeException("Response: " + response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CallingResponse makeCallingRequest(long requestId, String url, String httpMethod, List<Pair<String, String>> headers, byte[] body) {
|
||||||
|
ConnectionHolder connectionHolder = getRandom(serviceClients, random);
|
||||||
|
OkHttpClient okHttpClient = connectionHolder.getClient()
|
||||||
|
.newBuilder()
|
||||||
|
.followRedirects(true)
|
||||||
|
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||||
|
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RequestBody requestBody = body != null ? RequestBody.create(null, body) : null;
|
||||||
|
Request.Builder builder = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.method(httpMethod, requestBody);
|
||||||
|
|
||||||
|
if (headers != null) {
|
||||||
|
for (Pair<String, String> header : headers) {
|
||||||
|
builder.addHeader(header.first(), header.second());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Call call = okHttpClient.newCall(builder.build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Response response = call.execute();
|
||||||
|
int responseStatus = response.code();
|
||||||
|
byte[] responseBody = response.body() != null ? response.body().bytes() : new byte[0];
|
||||||
|
|
||||||
|
return new CallingResponse.Success(requestId, responseStatus, responseBody);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Exception during ringrtc http call.", e);
|
||||||
|
return new CallingResponse.Error(requestId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private ServiceConnectionHolder[] createServiceConnectionHolders(SignalUrl[] urls,
|
private ServiceConnectionHolder[] createServiceConnectionHolders(SignalUrl[] urls,
|
||||||
List<Interceptor> interceptors,
|
List<Interceptor> interceptors,
|
||||||
Optional<Dns> dns)
|
Optional<Dns> dns)
|
||||||
|
@ -2037,6 +2076,18 @@ public class PushServiceSocket {
|
||||||
return GroupJoinInfo.parseFrom(readBodyBytes(response));
|
return GroupJoinInfo.parseFrom(readBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GroupExternalCredential getGroupExternalCredential(GroupsV2AuthorizationString authorization)
|
||||||
|
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
|
||||||
|
{
|
||||||
|
ResponseBody response = makeStorageRequest(authorization.toString(),
|
||||||
|
GROUPSV2_TOKEN,
|
||||||
|
"GET",
|
||||||
|
null,
|
||||||
|
NO_HANDLER);
|
||||||
|
|
||||||
|
return GroupExternalCredential.parseFrom(readBodyBytes(response));
|
||||||
|
}
|
||||||
|
|
||||||
public static final class GroupHistory {
|
public static final class GroupHistory {
|
||||||
private final GroupChanges groupChanges;
|
private final GroupChanges groupChanges;
|
||||||
private final Optional<ContentRange> contentRange;
|
private final Optional<ContentRange> contentRange;
|
||||||
|
|
|
@ -210,3 +210,7 @@ message GroupJoinInfo {
|
||||||
uint32 revision = 6;
|
uint32 revision = 6;
|
||||||
bool pendingAdminApproval = 7;
|
bool pendingAdminApproval = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GroupExternalCredential {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -92,6 +92,9 @@ message CallMessage {
|
||||||
optional uint32 deviceId = 3;
|
optional uint32 deviceId = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Opaque {
|
||||||
|
optional bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
optional Offer offer = 1;
|
optional Offer offer = 1;
|
||||||
optional Answer answer = 2;
|
optional Answer answer = 2;
|
||||||
|
@ -102,6 +105,7 @@ message CallMessage {
|
||||||
optional Hangup hangup = 7;
|
optional Hangup hangup = 7;
|
||||||
optional bool multiRing = 8;
|
optional bool multiRing = 8;
|
||||||
optional uint32 destinationDeviceId = 9;
|
optional uint32 destinationDeviceId = 9;
|
||||||
|
optional Opaque opaque = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DataMessage {
|
message DataMessage {
|
||||||
|
|
Loading…
Add table
Reference in a new issue