Update ringrtc to v1.0.1
Add support for RingRTC Call Manager, a new component which provides the control layer for all calling features.
This commit is contained in:
parent
81532cad95
commit
0970fd7040
16 changed files with 1789 additions and 1387 deletions
|
@ -119,7 +119,7 @@ dependencies {
|
|||
|
||||
implementation 'org.signal:argon2:13.1@aar'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:0.3.3'
|
||||
implementation 'org.signal:ringrtc-android:1.0.1'
|
||||
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
|
|
|
@ -34,7 +34,7 @@ import com.google.android.gms.security.ProviderInstaller;
|
|||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.signal.ringrtc.CallConnectionFactory;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
@ -336,9 +336,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
||||
}
|
||||
|
||||
CallConnectionFactory.initialize(this, new RingRtcLogger());
|
||||
CallManager.initialize(this, new RingRtcLogger());
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.w(TAG, e);
|
||||
throw new AssertionError("Unable to load ringrtc library", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
|||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -173,8 +174,8 @@ public class WebRtcCallActivity extends Activity {
|
|||
|
||||
private void handleSetMuteVideo(boolean muted) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_VIDEO);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_MUTE, muted);
|
||||
intent.setAction(WebRtcCallService.ACTION_SET_ENABLE_VIDEO);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ENABLE, !muted);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
|
@ -198,7 +199,7 @@ public class WebRtcCallActivity extends Activity {
|
|||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_answering), event.getLocalRenderer());
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_ANSWER_CALL);
|
||||
intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL);
|
||||
startService(intent);
|
||||
})
|
||||
.onAnyDenied(this::handleDenyCall)
|
||||
|
@ -249,7 +250,7 @@ public class WebRtcCallActivity extends Activity {
|
|||
|
||||
private void handleCallBusy(@NonNull WebRtcViewModel event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy), event.getLocalRenderer());
|
||||
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
|
||||
}
|
||||
|
||||
|
@ -260,11 +261,13 @@ public class WebRtcCallActivity extends Activity {
|
|||
|
||||
private void handleRecipientUnavailable(@NonNull WebRtcViewModel event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable), event.getLocalRenderer());
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleServerFailure(@NonNull WebRtcViewModel event) {
|
||||
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed), event.getLocalRenderer());
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
|
@ -304,8 +307,8 @@ public class WebRtcCallActivity extends Activity {
|
|||
}
|
||||
|
||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId()));
|
||||
startService(intent);
|
||||
}
|
||||
});
|
||||
|
@ -411,4 +414,4 @@ public class WebRtcCallActivity extends Activity {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,8 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
|
@ -368,18 +370,21 @@ public final class PushProcessMessageJob extends BaseJob {
|
|||
@NonNull OfferMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
Log.w(TAG, "handleCallOfferMessage...");
|
||||
Log.i(TAG, "handleCallOfferMessage...");
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
database.markAsMissedCall(smsMessageId.get());
|
||||
} else {
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp());
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
RemotePeer remotePeer = new RemotePeer(Recipient.externalPush(context, content.getSender()).getId());
|
||||
|
||||
intent.setAction(WebRtcCallService.ACTION_RECEIVE_OFFER)
|
||||
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice())
|
||||
.putExtra(WebRtcCallService.EXTRA_OFFER_DESCRIPTION, message.getDescription())
|
||||
.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent);
|
||||
else context.startService(intent);
|
||||
|
@ -390,11 +395,14 @@ public final class PushProcessMessageJob extends BaseJob {
|
|||
@NonNull AnswerMessage message)
|
||||
{
|
||||
Log.i(TAG, "handleCallAnswerMessage...");
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
RemotePeer remotePeer = new RemotePeer(Recipient.externalPush(context, content.getSender()).getId());
|
||||
|
||||
intent.setAction(WebRtcCallService.ACTION_RECEIVE_ANSWER)
|
||||
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice())
|
||||
.putExtra(WebRtcCallService.EXTRA_ANSWER_DESCRIPTION, message.getDescription());
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
@ -402,18 +410,25 @@ public final class PushProcessMessageJob extends BaseJob {
|
|||
private void handleCallIceUpdateMessage(@NonNull SignalServiceContent content,
|
||||
@NonNull List<IceUpdateMessage> messages)
|
||||
{
|
||||
Log.w(TAG, "handleCallIceUpdateMessage... " + messages.size());
|
||||
for (IceUpdateMessage message : messages) {
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex());
|
||||
Log.i(TAG, "handleCallIceUpdateMessage... " + messages.size());
|
||||
|
||||
context.startService(intent);
|
||||
ArrayList<IceCandidateParcel> iceCandidates = new ArrayList(messages.size());
|
||||
long callId = -1;
|
||||
for (IceUpdateMessage iceMessage : messages) {
|
||||
iceCandidates.add(new IceCandidateParcel(iceMessage));
|
||||
callId = iceMessage.getId();
|
||||
}
|
||||
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
RemotePeer remotePeer = new RemotePeer(Recipient.externalPush(context, content.getSender()).getId());
|
||||
|
||||
intent.setAction(WebRtcCallService.ACTION_RECEIVE_ICE_CANDIDATES)
|
||||
.putExtra(WebRtcCallService.EXTRA_CALL_ID, callId)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice())
|
||||
.putParcelableArrayListExtra(WebRtcCallService.EXTRA_ICE_CANDIDATES, iceCandidates);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
private void handleCallHangupMessage(@NonNull SignalServiceContent content,
|
||||
|
@ -424,10 +439,13 @@ public final class PushProcessMessageJob extends BaseJob {
|
|||
if (smsMessageId.isPresent()) {
|
||||
DatabaseFactory.getSmsDatabase(context).markAsMissedCall(smsMessageId.get());
|
||||
} else {
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId());
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
RemotePeer remotePeer = new RemotePeer(Recipient.externalPush(context, content.getSender()).getId());
|
||||
|
||||
intent.setAction(WebRtcCallService.ACTION_RECEIVE_HANGUP)
|
||||
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice());
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
@ -436,10 +454,15 @@ public final class PushProcessMessageJob extends BaseJob {
|
|||
private void handleCallBusyMessage(@NonNull SignalServiceContent content,
|
||||
@NonNull BusyMessage message)
|
||||
{
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, Recipient.externalPush(context, content.getSender()).getId());
|
||||
Log.i(TAG, "handleCallBusyMessage");
|
||||
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
RemotePeer remotePeer = new RemotePeer(Recipient.externalPush(context, content.getSender()).getId());
|
||||
|
||||
intent.setAction(WebRtcCallService.ACTION_RECEIVE_BUSY)
|
||||
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice());
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
|
|
@ -1,400 +0,0 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.signal.ringrtc.CallConnection;
|
||||
import org.signal.ringrtc.CallConnectionFactory;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.SignalMessageRecipient;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.webrtc.AudioSource;
|
||||
import org.webrtc.AudioTrack;
|
||||
import org.webrtc.Camera1Enumerator;
|
||||
import org.webrtc.Camera2Capturer;
|
||||
import org.webrtc.Camera2Enumerator;
|
||||
import org.webrtc.CameraEnumerator;
|
||||
import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.BACK;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.FRONT;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.NONE;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.PENDING;
|
||||
|
||||
public class CallConnectionWrapper {
|
||||
private static final String TAG = Log.tag(CallConnectionWrapper.class);
|
||||
|
||||
@NonNull private final CallConnection callConnection;
|
||||
@NonNull private final AudioTrack audioTrack;
|
||||
@NonNull private final AudioSource audioSource;
|
||||
@NonNull private final Camera camera;
|
||||
@Nullable private final VideoSource videoSource;
|
||||
@Nullable private final VideoTrack videoTrack;
|
||||
|
||||
public CallConnectionWrapper(@NonNull Context context,
|
||||
@NonNull CallConnectionFactory factory,
|
||||
@NonNull CallConnection.Observer observer,
|
||||
@NonNull VideoSink localRenderer,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull EglBase eglBase,
|
||||
boolean hideIp,
|
||||
long callId,
|
||||
boolean outBound,
|
||||
@NonNull SignalMessageRecipient recipient,
|
||||
@NonNull SignalServiceAccountManager accountManager)
|
||||
throws UnregisteredUserException, IOException, CallException
|
||||
{
|
||||
|
||||
CallConnection.Configuration configuration = new CallConnection.Configuration(callId,
|
||||
outBound,
|
||||
recipient,
|
||||
accountManager,
|
||||
hideIp);
|
||||
|
||||
this.callConnection = factory.createCallConnection(configuration, observer);
|
||||
this.callConnection.setAudioPlayout(false);
|
||||
this.callConnection.setAudioRecording(false);
|
||||
|
||||
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
|
||||
MediaConstraints audioConstraints = new MediaConstraints();
|
||||
|
||||
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||
this.audioSource = factory.createAudioSource(audioConstraints);
|
||||
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
|
||||
this.audioTrack.setEnabled(false);
|
||||
mediaStream.addTrack(audioTrack);
|
||||
|
||||
this.camera = new Camera(context, cameraEventListener);
|
||||
|
||||
if (camera.capturer != null) {
|
||||
this.videoSource = factory.createVideoSource(false);
|
||||
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
|
||||
|
||||
camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
|
||||
|
||||
this.videoTrack.addSink(localRenderer);
|
||||
this.videoTrack.setEnabled(false);
|
||||
mediaStream.addTrack(videoTrack);
|
||||
} else {
|
||||
this.videoSource = null;
|
||||
this.videoTrack = null;
|
||||
}
|
||||
|
||||
this.callConnection.addStream(mediaStream);
|
||||
}
|
||||
|
||||
public boolean addIceCandidate(IceCandidate candidate) {
|
||||
return callConnection.addIceCandidate(candidate);
|
||||
}
|
||||
|
||||
public void sendOffer() throws CallException {
|
||||
callConnection.sendOffer();
|
||||
}
|
||||
|
||||
public boolean validateResponse(SignalMessageRecipient recipient, Long inCallId)
|
||||
throws CallException
|
||||
{
|
||||
return callConnection.validateResponse(recipient, inCallId);
|
||||
}
|
||||
|
||||
public void handleOfferAnswer(String sessionDescription) throws CallException {
|
||||
callConnection.handleOfferAnswer(sessionDescription);
|
||||
}
|
||||
|
||||
public void acceptOffer(String offer) throws CallException {
|
||||
callConnection.acceptOffer(offer);
|
||||
}
|
||||
|
||||
public void hangUp() throws CallException {
|
||||
callConnection.hangUp();
|
||||
}
|
||||
|
||||
public void answerCall() throws CallException {
|
||||
callConnection.answerCall();
|
||||
}
|
||||
|
||||
public void setVideoEnabled(boolean enabled) throws CallException {
|
||||
if (videoTrack != null) {
|
||||
videoTrack.setEnabled(enabled);
|
||||
}
|
||||
camera.setEnabled(enabled);
|
||||
callConnection.sendVideoStatus(enabled);
|
||||
}
|
||||
|
||||
public void flipCamera() {
|
||||
camera.flip();
|
||||
}
|
||||
|
||||
public CameraState getCameraState() {
|
||||
return new CameraState(camera.getActiveDirection(), camera.getCount());
|
||||
}
|
||||
|
||||
public void setCommunicationMode() {
|
||||
callConnection.setAudioPlayout(true);
|
||||
callConnection.setAudioRecording(true);
|
||||
}
|
||||
|
||||
public void setAudioEnabled(boolean enabled) {
|
||||
audioTrack.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
camera.dispose();
|
||||
|
||||
if (videoSource != null) {
|
||||
videoSource.dispose();
|
||||
}
|
||||
|
||||
audioSource.dispose();
|
||||
callConnection.dispose();
|
||||
}
|
||||
|
||||
private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {
|
||||
|
||||
@Nullable
|
||||
private final CameraVideoCapturer capturer;
|
||||
private final CameraEventListener cameraEventListener;
|
||||
private final int cameraCount;
|
||||
|
||||
private CameraState.Direction activeDirection;
|
||||
private boolean enabled;
|
||||
|
||||
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener)
|
||||
{
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
CameraEnumerator enumerator = getCameraEnumerator(context);
|
||||
cameraCount = enumerator.getDeviceNames().length;
|
||||
|
||||
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = FRONT;
|
||||
} else {
|
||||
capturerCandidate = createVideoCapturer(enumerator, BACK);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = BACK;
|
||||
} else {
|
||||
activeDirection = NONE;
|
||||
}
|
||||
}
|
||||
capturer = capturerCandidate;
|
||||
}
|
||||
|
||||
void flip() {
|
||||
if (capturer == null || cameraCount < 2) {
|
||||
throw new AssertionError("Tried to flip the camera, but we only have " + cameraCount +
|
||||
" of them.");
|
||||
}
|
||||
activeDirection = PENDING;
|
||||
capturer.switchCamera(this);
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
|
||||
if (capturer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled) {
|
||||
capturer.startCapture(1280, 720, 30);
|
||||
} else {
|
||||
capturer.stopCapture();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return cameraCount;
|
||||
}
|
||||
|
||||
@NonNull CameraState.Direction getActiveDirection() {
|
||||
return enabled ? activeDirection : NONE;
|
||||
}
|
||||
|
||||
@Nullable CameraVideoCapturer getCapturer() {
|
||||
return capturer;
|
||||
}
|
||||
|
||||
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
|
||||
@NonNull CameraState.Direction direction)
|
||||
{
|
||||
String[] deviceNames = enumerator.getDeviceNames();
|
||||
for (String deviceName : deviceNames) {
|
||||
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
|
||||
(direction == BACK && enumerator.isBackFacing(deviceName)))
|
||||
{
|
||||
return enumerator.createCapturer(deviceName, null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
|
||||
boolean camera2EnumeratorIsSupported = false;
|
||||
try {
|
||||
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
|
||||
} catch (final Throwable throwable) {
|
||||
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
|
||||
|
||||
return camera2EnumeratorIsSupported ? new FilteredCamera2Enumerator(context)
|
||||
: new Camera1Enumerator(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontFacing) {
|
||||
activeDirection = isFrontFacing ? FRONT : BACK;
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorMessage) {
|
||||
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
}
|
||||
|
||||
public interface CameraEventListener {
|
||||
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private static class FilteredCamera2Enumerator extends Camera2Enumerator {
|
||||
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final CameraManager cameraManager;
|
||||
@Nullable private String[] deviceNames;
|
||||
|
||||
FilteredCamera2Enumerator(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
this.context = context;
|
||||
this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
this.deviceNames = null;
|
||||
}
|
||||
|
||||
private boolean isMonochrome(String deviceName, CameraManager cameraManager) {
|
||||
|
||||
try {
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(deviceName);
|
||||
int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
|
||||
|
||||
if (capabilities != null) {
|
||||
for (int cap : capabilities) {
|
||||
if (cap == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CameraAccessException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLensFacing(String deviceName, CameraManager cameraManager, Integer facing) {
|
||||
|
||||
try {
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(deviceName);
|
||||
Integer lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
|
||||
return facing.equals(lensFacing);
|
||||
} catch (CameraAccessException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String[] getDeviceNames() {
|
||||
|
||||
if (deviceNames != null) {
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> cameraList = new LinkedList<>();
|
||||
|
||||
if (cameraManager != null) {
|
||||
// While skipping cameras that are monochrome, gather cameras
|
||||
// until we have at most 1 front facing camera and 1 back
|
||||
// facing camera.
|
||||
|
||||
List<String> devices = Stream.of(cameraManager.getCameraIdList())
|
||||
.filterNot(id -> isMonochrome(id, cameraManager))
|
||||
.toList();
|
||||
|
||||
String frontCamera = Stream.of(devices)
|
||||
.filter(id -> isLensFacing(id, cameraManager, CameraMetadata.LENS_FACING_FRONT))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (frontCamera != null) {
|
||||
cameraList.add(frontCamera);
|
||||
}
|
||||
|
||||
String backCamera = Stream.of(devices)
|
||||
.filter(id -> isLensFacing(id, cameraManager, CameraMetadata.LENS_FACING_BACK))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (backCamera != null) {
|
||||
cameraList.add(backCamera);
|
||||
}
|
||||
}
|
||||
|
||||
this.deviceNames = cameraList.toArray(new String[0]);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "Camera access exception: " + e);
|
||||
this.deviceNames = new String[] {};
|
||||
}
|
||||
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CameraVideoCapturer createCapturer(@Nullable String deviceName,
|
||||
@Nullable CameraVideoCapturer.CameraEventsHandler eventsHandler) {
|
||||
return new Camera2Capturer(context, deviceName, eventsHandler, new FilteredCamera2Enumerator(context));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
/**
|
||||
*
|
||||
* Enumeration of call state
|
||||
*
|
||||
*/
|
||||
public enum CallState {
|
||||
|
||||
/** Idle, setting up objects */
|
||||
IDLE,
|
||||
|
||||
/** Dialing. Outgoing call is signaling the remote peer */
|
||||
DIALING,
|
||||
|
||||
/** Answering. Incoming call is responding to remote peer */
|
||||
ANSWERING,
|
||||
|
||||
/** Remote ringing. Outgoing call, ICE negotiation is complete */
|
||||
REMOTE_RINGING,
|
||||
|
||||
/** Local ringing. Incoming call, ICE negotiation is complete */
|
||||
LOCAL_RINGING,
|
||||
|
||||
/** Connected. Incoming/Outgoing call, the call is connected */
|
||||
CONNECTED,
|
||||
|
||||
/** Terminated. Incoming/Outgoing call, the call is terminated */
|
||||
TERMINATED,
|
||||
|
||||
/** Busy. Outgoing call received a busy notification */
|
||||
RECEIVED_BUSY;
|
||||
|
||||
}
|
280
app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java
Normal file
280
app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java
Normal file
|
@ -0,0 +1,280 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.signal.ringrtc.CameraControl;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.webrtc.Camera1Enumerator;
|
||||
import org.webrtc.Camera2Capturer;
|
||||
import org.webrtc.Camera2Enumerator;
|
||||
import org.webrtc.CameraEnumerator;
|
||||
import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.CapturerObserver;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.BACK;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.FRONT;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.NONE;
|
||||
import static org.thoughtcrime.securesms.ringrtc.CameraState.Direction.PENDING;
|
||||
|
||||
/**
|
||||
* Encapsulate the camera functionality needed for video calling.
|
||||
*/
|
||||
public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHandler {
|
||||
|
||||
private static final String TAG = Log.tag(Camera.class);
|
||||
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final CameraVideoCapturer capturer;
|
||||
@NonNull private final CameraEventListener cameraEventListener;
|
||||
@NonNull private final EglBase eglBase;
|
||||
private final int cameraCount;
|
||||
@NonNull private CameraState.Direction activeDirection;
|
||||
private boolean enabled;
|
||||
|
||||
public Camera(@NonNull Context context,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull EglBase eglBase)
|
||||
{
|
||||
this.context = context;
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
this.eglBase = eglBase;
|
||||
CameraEnumerator enumerator = getCameraEnumerator(context);
|
||||
cameraCount = enumerator.getDeviceNames().length;
|
||||
|
||||
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = FRONT;
|
||||
} else {
|
||||
capturerCandidate = createVideoCapturer(enumerator, BACK);
|
||||
if (capturerCandidate != null) {
|
||||
activeDirection = BACK;
|
||||
} else {
|
||||
activeDirection = NONE;
|
||||
}
|
||||
}
|
||||
capturer = capturerCandidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initCapturer(@NonNull CapturerObserver observer) {
|
||||
if (capturer != null) {
|
||||
capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()),
|
||||
context,
|
||||
observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCapturer() {
|
||||
return capturer != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flip() {
|
||||
if (capturer == null || cameraCount < 2) {
|
||||
throw new AssertionError("Tried to flip the camera, but we only have " + cameraCount + " of them.");
|
||||
}
|
||||
activeDirection = PENDING;
|
||||
capturer.switchCamera(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
Log.i(TAG, "setEnabled(): " + enabled);
|
||||
|
||||
this.enabled = enabled;
|
||||
|
||||
if (capturer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled) {
|
||||
Log.i(TAG, "setEnabled(): starting capture");
|
||||
capturer.startCapture(1280, 720, 30);
|
||||
} else {
|
||||
Log.i(TAG, "setEnabled(): stopping capture");
|
||||
capturer.stopCapture();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return cameraCount;
|
||||
}
|
||||
|
||||
@NonNull CameraState.Direction getActiveDirection() {
|
||||
return enabled ? activeDirection : NONE;
|
||||
}
|
||||
|
||||
@NonNull public CameraState getCameraState() {
|
||||
return new CameraState(getActiveDirection(), getCount());
|
||||
}
|
||||
|
||||
@Nullable CameraVideoCapturer getCapturer() {
|
||||
return capturer;
|
||||
}
|
||||
|
||||
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
|
||||
@NonNull CameraState.Direction direction)
|
||||
{
|
||||
String[] deviceNames = enumerator.getDeviceNames();
|
||||
for (String deviceName : deviceNames) {
|
||||
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
|
||||
(direction == BACK && enumerator.isBackFacing(deviceName)))
|
||||
{
|
||||
return enumerator.createCapturer(deviceName, null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
|
||||
boolean camera2EnumeratorIsSupported = false;
|
||||
try {
|
||||
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
|
||||
} catch (final Throwable throwable) {
|
||||
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
|
||||
|
||||
return camera2EnumeratorIsSupported ? new FilteredCamera2Enumerator(context)
|
||||
: new Camera1Enumerator(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchDone(boolean isFrontFacing) {
|
||||
activeDirection = isFrontFacing ? FRONT : BACK;
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraSwitchError(String errorMessage) {
|
||||
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
|
||||
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private static class FilteredCamera2Enumerator extends Camera2Enumerator {
|
||||
|
||||
private static final String TAG = Log.tag(Camera2Enumerator.class);
|
||||
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final CameraManager cameraManager;
|
||||
@Nullable private String[] deviceNames;
|
||||
|
||||
FilteredCamera2Enumerator(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
this.context = context;
|
||||
this.cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
this.deviceNames = null;
|
||||
}
|
||||
|
||||
private static boolean isMonochrome(@NonNull String deviceName, @NonNull CameraManager cameraManager) {
|
||||
try {
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(deviceName);
|
||||
int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
|
||||
|
||||
if (capabilities != null) {
|
||||
for (int capability : capabilities) {
|
||||
if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CameraAccessException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isLensFacing(@NonNull String deviceName, @NonNull CameraManager cameraManager, @NonNull Integer facing) {
|
||||
try {
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(deviceName);
|
||||
Integer lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
|
||||
return facing.equals(lensFacing);
|
||||
} catch (CameraAccessException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String[] getDeviceNames() {
|
||||
if (deviceNames != null) {
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> cameraList = new LinkedList<>();
|
||||
|
||||
if (cameraManager != null) {
|
||||
List<String> devices = Stream.of(cameraManager.getCameraIdList())
|
||||
.filterNot(id -> isMonochrome(id, cameraManager))
|
||||
.toList();
|
||||
|
||||
String frontCamera = Stream.of(devices)
|
||||
.filter(id -> isLensFacing(id, cameraManager, CameraMetadata.LENS_FACING_FRONT))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (frontCamera != null) {
|
||||
cameraList.add(frontCamera);
|
||||
}
|
||||
|
||||
String backCamera = Stream.of(devices)
|
||||
.filter(id -> isLensFacing(id, cameraManager, CameraMetadata.LENS_FACING_BACK))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (backCamera != null) {
|
||||
cameraList.add(backCamera);
|
||||
}
|
||||
}
|
||||
|
||||
this.deviceNames = cameraList.toArray(new String[0]);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "Camera access exception: " + e);
|
||||
this.deviceNames = new String[] {};
|
||||
}
|
||||
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CameraVideoCapturer createCapturer(@Nullable String deviceName,
|
||||
@Nullable CameraVideoCapturer.CameraEventsHandler eventsHandler)
|
||||
{
|
||||
return new Camera2Capturer(context, deviceName, eventsHandler, new FilteredCamera2Enumerator(context));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface CameraEventListener {
|
||||
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
|
||||
}
|
|
@ -26,6 +26,11 @@ public class CameraState {
|
|||
return this.activeDirection != Direction.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "count: " + cameraCount + ", activeDirection: " + activeDirection;
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
FRONT, BACK, NONE, PENDING
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallId;
|
||||
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
|
||||
/**
|
||||
* Utility class for passing ICE candidate objects via Intents.
|
||||
*
|
||||
* Also provides utility methods for converting to/from Signal ICE
|
||||
* candidate messages.
|
||||
*/
|
||||
public class IceCandidateParcel implements Parcelable {
|
||||
|
||||
@NonNull private final IceCandidate iceCandidate;
|
||||
|
||||
public IceCandidateParcel(@NonNull IceCandidate iceCandidate) {
|
||||
this.iceCandidate = iceCandidate;
|
||||
}
|
||||
|
||||
public IceCandidateParcel(@NonNull IceUpdateMessage iceUpdateMessage) {
|
||||
this.iceCandidate = new IceCandidate(iceUpdateMessage.getSdpMid(),
|
||||
iceUpdateMessage.getSdpMLineIndex(),
|
||||
iceUpdateMessage.getSdp());
|
||||
}
|
||||
|
||||
private IceCandidateParcel(@NonNull Parcel in) {
|
||||
this.iceCandidate = new IceCandidate(in.readString(),
|
||||
in.readInt(),
|
||||
in.readString());
|
||||
}
|
||||
|
||||
public @NonNull IceCandidate getIceCandidate() {
|
||||
return iceCandidate;
|
||||
}
|
||||
|
||||
public @NonNull IceUpdateMessage getIceUpdateMessage(@NonNull CallId callId) {
|
||||
return new IceUpdateMessage(callId.longValue(),
|
||||
iceCandidate.sdpMid,
|
||||
iceCandidate.sdpMLineIndex,
|
||||
iceCandidate.sdp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(iceCandidate.sdpMid);
|
||||
dest.writeInt(iceCandidate.sdpMLineIndex);
|
||||
dest.writeString(iceCandidate.sdp);
|
||||
}
|
||||
|
||||
public static final Creator<IceCandidateParcel> CREATOR = new Creator<IceCandidateParcel>() {
|
||||
@Override
|
||||
public IceCandidateParcel createFromParcel(@NonNull Parcel in) {
|
||||
return new IceCandidateParcel(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IceCandidateParcel[] newArray(int size) {
|
||||
return new IceCandidateParcel[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.signal.ringrtc.SignalMessageRecipient;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public final class MessageRecipient implements SignalMessageRecipient {
|
||||
|
||||
private static final String TAG = Log.tag(MessageRecipient.class);
|
||||
|
||||
@NonNull private final Recipient recipient;
|
||||
@NonNull private final SignalServiceMessageSender messageSender;
|
||||
|
||||
public MessageRecipient(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull Recipient recipient)
|
||||
{
|
||||
this.recipient = recipient;
|
||||
this.messageSender = messageSender;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
return recipient.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(@NonNull SignalMessageRecipient inRecipient) {
|
||||
if (!(inRecipient instanceof MessageRecipient)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != inRecipient.getClass()) {
|
||||
Log.e(TAG, "CLASSES NOT EQUAL: " + getClass().toString() + ", " + recipient.getClass().toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageRecipient that = (MessageRecipient) inRecipient;
|
||||
|
||||
return recipient.equals(that.recipient);
|
||||
}
|
||||
|
||||
private void sendMessage(Context context, SignalServiceCallMessage callMessage)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOfferMessage(Context context, long callId, String description)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendOfferMessage(): callId: 0x" + Long.toHexString(callId));
|
||||
|
||||
OfferMessage offerMessage = new OfferMessage(callId, description);
|
||||
sendMessage(context, SignalServiceCallMessage.forOffer(offerMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAnswerMessage(Context context, long callId, String description)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendAnswerMessage(): callId: 0x" + Long.toHexString(callId));
|
||||
|
||||
AnswerMessage answerMessage = new AnswerMessage(callId, description);
|
||||
sendMessage(context, SignalServiceCallMessage.forAnswer(answerMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendIceUpdates(Context context, List<IceUpdateMessage> iceUpdateMessages)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendIceUpdates(): iceUpdates: " + iceUpdateMessages.size());
|
||||
|
||||
sendMessage(context, SignalServiceCallMessage.forIceUpdates(iceUpdateMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHangupMessage(Context context, long callId)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.i(TAG, "MessageRecipient::sendHangupMessage(): callId: 0x" + Long.toHexString(callId));
|
||||
|
||||
HangupMessage hangupMessage = new HangupMessage(callId);
|
||||
sendMessage(context, SignalServiceCallMessage.forHangup(hangupMessage));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallId;
|
||||
import org.signal.ringrtc.Remote;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
/**
|
||||
* Container class that represents the remote peer and current state
|
||||
* of a video/voice call.
|
||||
*
|
||||
* The class is also Parcelable for passing around via an Intent.
|
||||
*/
|
||||
public final class RemotePeer implements Remote, Parcelable
|
||||
{
|
||||
@NonNull private final RecipientId recipientId;
|
||||
@NonNull private CallState callState;
|
||||
@NonNull private CallId callId;
|
||||
|
||||
public RemotePeer(@NonNull RecipientId recipientId) {
|
||||
this.recipientId = recipientId;
|
||||
this.callState = CallState.IDLE;
|
||||
this.callId = new CallId(-1L);
|
||||
}
|
||||
|
||||
private RemotePeer(@NonNull Parcel in) {
|
||||
this.recipientId = RecipientId.CREATOR.createFromParcel(in);
|
||||
this.callState = CallState.values()[in.readInt()];
|
||||
this.callId = new CallId(in.readLong());
|
||||
}
|
||||
|
||||
public @NonNull CallId getCallId() {
|
||||
return callId;
|
||||
}
|
||||
|
||||
public @NonNull CallState getState() {
|
||||
return callState;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getRecipient() {
|
||||
return Recipient.resolved(recipientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "recipientId: " + this.recipientId +
|
||||
", callId: " + this.callId +
|
||||
", state: " + this.callState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recipientEquals(Remote obj) {
|
||||
if (obj != null && this.getClass() == obj.getClass()) {
|
||||
RemotePeer that = (RemotePeer)obj;
|
||||
return this.recipientId.equals(that.recipientId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean callIdEquals(RemotePeer remotePeer) {
|
||||
return remotePeer != null && this.callId.equals(remotePeer.callId);
|
||||
}
|
||||
|
||||
public void dialing(@NonNull CallId callId) {
|
||||
if (callState != CallState.IDLE) {
|
||||
throw new IllegalStateException("Cannot transition to DIALING from state: " + callState);
|
||||
}
|
||||
|
||||
this.callId = callId;
|
||||
this.callState = CallState.DIALING;
|
||||
}
|
||||
|
||||
public void answering(@NonNull CallId callId) {
|
||||
if (callState != CallState.IDLE) {
|
||||
throw new IllegalStateException("Cannot transition to ANSWERING from state: " + callState);
|
||||
}
|
||||
|
||||
this.callId = callId;
|
||||
this.callState = CallState.ANSWERING;
|
||||
}
|
||||
|
||||
public void remoteRinging() {
|
||||
if (callState != CallState.DIALING) {
|
||||
throw new IllegalStateException("Cannot transition to REMOTE_RINGING from state: " + callState);
|
||||
}
|
||||
|
||||
this.callState = CallState.REMOTE_RINGING;
|
||||
}
|
||||
|
||||
public void receivedBusy() {
|
||||
if (callState != CallState.DIALING) {
|
||||
throw new IllegalStateException("Cannot transition to RECEIVED_BUSY from state: " + callState);
|
||||
}
|
||||
|
||||
this.callState = CallState.RECEIVED_BUSY;
|
||||
}
|
||||
|
||||
public void localRinging() {
|
||||
if (callState != CallState.ANSWERING) {
|
||||
throw new IllegalStateException("Cannot transition to LOCAL_RINGING from state: " + callState);
|
||||
}
|
||||
|
||||
this.callState = CallState.LOCAL_RINGING;
|
||||
}
|
||||
|
||||
public void connected() {
|
||||
if (callState != CallState.REMOTE_RINGING && callState != CallState.LOCAL_RINGING) {
|
||||
throw new IllegalStateException("Cannot transition outgoing call to CONNECTED from state: " + callState);
|
||||
}
|
||||
|
||||
this.callState = CallState.CONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
recipientId.writeToParcel(dest, flags);
|
||||
dest.writeInt(callState.ordinal());
|
||||
dest.writeLong(callId.longValue());
|
||||
}
|
||||
|
||||
public static final Creator<RemotePeer> CREATOR = new Creator<RemotePeer>() {
|
||||
@Override
|
||||
public RemotePeer createFromParcel(@NonNull Parcel in) {
|
||||
return new RemotePeer(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemotePeer[] newArray(int size) {
|
||||
return new RemotePeer[size];
|
||||
}
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.WebRtcCallActivity;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
|
@ -145,8 +146,8 @@ public class CommunicationActions {
|
|||
.withPermanentDenialDialog(activity.getString(R.string.ConversationActivity_signal_needs_the_microphone_and_camera_permissions_in_order_to_call_s, recipient.getDisplayName(activity)))
|
||||
.onAllGranted(() -> {
|
||||
Intent intent = new Intent(activity, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
|
||||
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId()));
|
||||
activity.startService(intent);
|
||||
|
||||
Intent activityIntent = new Intent(activity, WebRtcCallActivity.class);
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.text.TextUtils;
|
|||
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
|
@ -32,8 +33,8 @@ public class VoiceCallShare extends Activity {
|
|||
SimpleTask.run(() -> Recipient.external(this, destination), recipient -> {
|
||||
if (!TextUtils.isEmpty(destination)) {
|
||||
Intent serviceIntent = new Intent(this, WebRtcCallService.class);
|
||||
serviceIntent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
serviceIntent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
|
||||
serviceIntent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
|
||||
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId()));
|
||||
startService(serviceIntent);
|
||||
|
||||
Intent activityIntent = new Intent(this, WebRtcCallActivity.class);
|
||||
|
|
|
@ -354,8 +354,8 @@ dependencyVerification {
|
|||
['org.signal:argon2:13.1',
|
||||
'0f686ccff0d4842bfcc74d92e8dc780a5f159b9376e37a1189fabbcdac458bef'],
|
||||
|
||||
['org.signal:ringrtc-android:0.3.3',
|
||||
'3f57fe74a1c5d35185e5a8b6e76ada88fa2c5cfc5bd68f00c8fbe97d2bf7f8f9'],
|
||||
['org.signal:ringrtc-android:1.0.1',
|
||||
'eec2bdd933bdb0a5fc310a884713d2ebd89cb8060ed00b372b2f9bcb17375c0e'],
|
||||
|
||||
['org.signal:signal-metadata-java:0.1.0',
|
||||
'f3faa23b7d9b5096d12979c35679d1e3b5e007522d8bef167a28e456f2a7c7d9'],
|
||||
|
|
Loading…
Add table
Reference in a new issue