mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-20 17:57:29 +00:00
Enable call vanity when joining a video call.
This commit is contained in:
@@ -125,7 +125,8 @@ fun CallScreen(
|
||||
callRecipient = callRecipient,
|
||||
isVideoCall = isRemoteVideoOffer,
|
||||
callStatus = callScreenState.callStatus,
|
||||
callScreenControlsListener = callScreenControlsListener
|
||||
callScreenControlsListener = callScreenControlsListener,
|
||||
localParticipant = localParticipant
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
@@ -40,7 +40,9 @@ import org.signal.core.ui.compose.Previews
|
||||
import org.signal.glide.compose.GlideImage
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState
|
||||
|
||||
private val textShadow = Shadow(
|
||||
color = Color(0f, 0f, 0f, 0.25f),
|
||||
@@ -52,7 +54,8 @@ fun IncomingCallScreen(
|
||||
callRecipient: Recipient,
|
||||
callStatus: String?,
|
||||
isVideoCall: Boolean,
|
||||
callScreenControlsListener: CallScreenControlsListener
|
||||
callScreenControlsListener: CallScreenControlsListener,
|
||||
localParticipant: CallParticipant = CallParticipant.EMPTY
|
||||
) {
|
||||
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
val callTypePadding = remember(isLandscape) {
|
||||
@@ -62,24 +65,37 @@ fun IncomingCallScreen(
|
||||
PaddingValues(top = 22.dp, bottom = 30.dp)
|
||||
}
|
||||
}
|
||||
val showLocalVideo = localParticipant.isVideoEnabled
|
||||
|
||||
Scaffold { contentPadding ->
|
||||
|
||||
GlideImage(
|
||||
model = callRecipient.contactPhoto,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.blur(
|
||||
radiusX = 25.dp,
|
||||
radiusY = 25.dp,
|
||||
edgeTreatment = BlurredEdgeTreatment.Rectangle
|
||||
)
|
||||
)
|
||||
if (showLocalVideo) {
|
||||
RemoteParticipantContent(
|
||||
participant = localParticipant,
|
||||
renderInPip = false,
|
||||
raiseHandAllowed = false,
|
||||
mirrorVideo = localParticipant.cameraDirection == CameraState.Direction.FRONT,
|
||||
showAudioIndicator = false,
|
||||
onInfoMoreInfoClick = null,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
GlideImage(
|
||||
model = callRecipient.contactPhoto,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.blur(
|
||||
radiusX = 25.dp,
|
||||
radiusY = 25.dp,
|
||||
edgeTreatment = BlurredEdgeTreatment.Rectangle
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Black.copy(alpha = 0.4f))
|
||||
.background(color = Color.Black.copy(alpha = if (showLocalVideo) 0.2f else 0.4f))
|
||||
) {}
|
||||
|
||||
CallScreenTopAppBar(
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.KeyguardManager
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -249,6 +250,8 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
||||
if (SignalStore.rateLimit.needsRecaptcha()) {
|
||||
RecaptchaProofBottomSheetFragment.show(supportFragmentManager)
|
||||
}
|
||||
|
||||
updateIncomingRingingVanity()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@@ -263,6 +266,8 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
||||
Log.i(TAG, "onPause")
|
||||
super.onPause()
|
||||
|
||||
disableIncomingRingingVanity()
|
||||
|
||||
if (!isInPipMode() || isFinishing) {
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
@@ -403,6 +408,9 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
||||
recreate()
|
||||
return
|
||||
}
|
||||
if (previousCallState != WebRtcViewModel.State.CALL_INCOMING) {
|
||||
updateIncomingRingingVanity()
|
||||
}
|
||||
}
|
||||
|
||||
WebRtcViewModel.State.CALL_OUTGOING -> handleOutgoingCall(event)
|
||||
@@ -1051,6 +1059,24 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateIncomingRingingVanity() {
|
||||
val event = previousEvent ?: return
|
||||
if (event.state != WebRtcViewModel.State.CALL_INCOMING) return
|
||||
|
||||
val keyguardManager = getSystemService(KeyguardManager::class.java)
|
||||
val shouldEnable = keyguardManager == null || !keyguardManager.isKeyguardLocked
|
||||
|
||||
Log.i(TAG, "updateIncomingRingingVanity(): shouldEnable=$shouldEnable, keyguardLocked=${keyguardManager?.isKeyguardLocked}")
|
||||
AppDependencies.signalCallManager.setIncomingRingingVanity(shouldEnable)
|
||||
}
|
||||
|
||||
private fun disableIncomingRingingVanity() {
|
||||
val event = previousEvent ?: return
|
||||
if (event.state == WebRtcViewModel.State.CALL_INCOMING) {
|
||||
AppDependencies.signalCallManager.setIncomingRingingVanity(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeScreenshotSecurity() {
|
||||
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.CapturerObserver;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoSink;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -47,6 +48,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
private boolean enabled;
|
||||
private boolean isInitialized;
|
||||
private int orientation;
|
||||
@Nullable private volatile VideoSink vanitySink;
|
||||
|
||||
public Camera(@NonNull Context context,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@@ -139,6 +141,15 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a vanity sink that receives camera frames directly from the capturer,
|
||||
* bypassing the WebRTC VideoTrack pipeline. This allows local preview to work
|
||||
* even when the video track is disabled (e.g., during incoming call ringing).
|
||||
*/
|
||||
public void setVanitySink(@Nullable VideoSink vanitySink) {
|
||||
this.vanitySink = vanitySink;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
@@ -327,6 +338,10 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
@Override
|
||||
public void onFrameCaptured(VideoFrame videoFrame) {
|
||||
observer.onFrameCaptured(videoFrame);
|
||||
VideoSink sink = vanitySink;
|
||||
if (sink != null) {
|
||||
sink.onFrame(videoFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.CallSetupState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
|
||||
@@ -133,6 +134,17 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
Log.i(TAG, "handleAcceptCall(): call_id: " + activePeer.getCallId());
|
||||
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
camera.setVanitySink(null);
|
||||
|
||||
if (!answerWithVideo && currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
camera.setEnabled(false);
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallSetupState(activePeer.getCallId())
|
||||
.acceptWithVideo(answerWithVideo)
|
||||
@@ -157,6 +169,11 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
Log.i(TAG, "handleDenyCall():");
|
||||
|
||||
Camera camera = currentState.getVideoState().getCamera();
|
||||
if (camera != null) {
|
||||
camera.setVanitySink(null);
|
||||
}
|
||||
|
||||
webRtcInteractor.sendNotAcceptedCallEventSyncMessage(activePeer,
|
||||
false,
|
||||
currentState.getCallSetupState(activePeer).isRemoteVideoOffer());
|
||||
@@ -170,6 +187,43 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetIncomingRingingVanity(@NonNull WebRtcServiceState currentState, boolean enabled) {
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
boolean isVideoOffer = currentState.getCallSetupState(activePeer).isRemoteVideoOffer();
|
||||
|
||||
if (!isVideoOffer) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
boolean cameraAlreadyEnabled = currentState.getLocalDeviceState().getCameraState().isEnabled();
|
||||
|
||||
if (enabled && cameraAlreadyEnabled) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (!enabled && !cameraAlreadyEnabled) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
|
||||
if (enabled) {
|
||||
Log.i(TAG, "handleSetIncomingRingingVanity(): enabling vanity camera");
|
||||
camera.setVanitySink(currentState.getVideoState().requireLocalSink());
|
||||
camera.setEnabled(true);
|
||||
} else {
|
||||
Log.i(TAG, "handleSetIncomingRingingVanity(): disabling vanity camera");
|
||||
camera.setVanitySink(null);
|
||||
camera.setEnabled(false);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleLocalRinging(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleLocalRinging(): call_id: " + remotePeer.getCallId());
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
@@ -179,8 +180,41 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetIncomingRingingVanity(@NonNull WebRtcServiceState currentState, boolean enabled) {
|
||||
boolean cameraAlreadyEnabled = currentState.getLocalDeviceState().getCameraState().isEnabled();
|
||||
|
||||
if (enabled && cameraAlreadyEnabled) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (!enabled && !cameraAlreadyEnabled) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
|
||||
if (enabled && !camera.isInitialized()) {
|
||||
Log.i(TAG, "handleSetIncomingRingingVanity(): initializing vanity camera");
|
||||
return WebRtcVideoUtil.initializeVanityCamera(currentState);
|
||||
} else if (enabled) {
|
||||
Log.i(TAG, "handleSetIncomingRingingVanity(): enabling vanity camera");
|
||||
camera.setEnabled(true);
|
||||
} else {
|
||||
Log.i(TAG, "handleSetIncomingRingingVanity(): disabling vanity camera");
|
||||
camera.setEnabled(false);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleAcceptCall(@NonNull WebRtcServiceState currentState, boolean answerWithVideo) {
|
||||
currentState = WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
|
||||
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
|
||||
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
|
||||
SignalStore.internal().getGroupCallingServer(),
|
||||
@@ -220,7 +254,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoSource(currentState.getVideoState().requireLocalSink(), currentState.getVideoState().requireCamera());
|
||||
groupCall.setOutgoingVideoMuted(answerWithVideo);
|
||||
groupCall.setOutgoingVideoMuted(!answerWithVideo);
|
||||
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
groupCall.setDataMode(NetworkUtil.getCallingDataMode(context, groupCall.getLocalDeviceState().getNetworkRoute().getLocalAdapterType()));
|
||||
|
||||
@@ -229,6 +263,15 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro
|
||||
return groupCallFailure(currentState, "Unable to join group call", e);
|
||||
}
|
||||
|
||||
if (answerWithVideo) {
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
camera.setEnabled(true);
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.actionProcessor(MultiPeerActionProcessorFactory.GroupActionProcessorFactory.INSTANCE.createJoiningActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
|
||||
@@ -268,6 +268,10 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
process((s, p) -> p.handleSetEnableVideo(s, enabled));
|
||||
}
|
||||
|
||||
public void setIncomingRingingVanity(boolean enabled) {
|
||||
process((s, p) -> p.handleSetIncomingRingingVanity(s, enabled));
|
||||
}
|
||||
|
||||
public void flipCamera() {
|
||||
process((s, p) -> p.handleSetCameraFlip(s));
|
||||
}
|
||||
|
||||
@@ -586,6 +586,10 @@ public abstract class WebRtcActionProcessor {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetIncomingRingingVanity(@NonNull WebRtcServiceState currentState, boolean enabled) {
|
||||
Log.i(tag, "handleSetIncomingRingingVanity not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSelfRaiseHand(@NonNull WebRtcServiceState currentState, boolean raised) {
|
||||
Log.i(tag, "raiseHand not processed");
|
||||
|
||||
Reference in New Issue
Block a user