diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e2d9a872e..7f7755f4d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -581,7 +581,7 @@ android:screenOrientation="portrait" android:theme="@style/Theme.Signal.WallpaperCropper" /> - + diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index da14a2b916..224c4c5642 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -41,22 +41,22 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.TooltipPopup; +import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState; -import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil; import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel; import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog; import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; +import org.thoughtcrime.securesms.service.webrtc.SignalCallManager; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.EllapsedTimeFormatter; import org.thoughtcrime.securesms.util.FullscreenHelper; @@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.signalservice.api.messages.calls.HangupMessage; -import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import java.util.List; @@ -163,9 +162,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan if (!viewModel.isCallStarting()) { CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL); - startService(intent); + ApplicationDependencies.getSignalCallManager().cancelPreJoin(); } } } @@ -258,19 +255,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); if (state != null) { if (state.needsNewRequestSizes()) { - Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS); - startService(intent); + ApplicationDependencies.getSignalCallManager().updateRenderedResolutions(); } } }); viewModel.getOrientation().observe(this, orientation -> { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_ORIENTATION_CHANGED) - .putExtra(WebRtcCallService.EXTRA_ORIENTATION_DEGREES, orientation.getDegrees()); - - startService(intent); + ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees()); switch (orientation) { case LANDSCAPE_LEFT_EDGE: @@ -329,30 +320,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan } private void handleSetAudioHandset() { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER); - startService(intent); + ApplicationDependencies.getSignalCallManager().setAudioSpeaker(false); } private void handleSetAudioSpeaker() { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_SPEAKER); - intent.putExtra(WebRtcCallService.EXTRA_SPEAKER, true); - startService(intent); + ApplicationDependencies.getSignalCallManager().setAudioSpeaker(true); } private void handleSetAudioBluetooth() { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH); - intent.putExtra(WebRtcCallService.EXTRA_BLUETOOTH, true); - startService(intent); + ApplicationDependencies.getSignalCallManager().setAudioBluetooth(true); } private void handleSetMuteAudio(boolean enabled) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO); - intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled); - startService(intent); + ApplicationDependencies.getSignalCallManager().setMuteAudio(enabled); } private void handleSetMuteVideo(boolean muted) { @@ -366,20 +346,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan .ifNecessary() .withRationaleDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName), R.drawable.ic_video_solid_24_tinted) .withPermanentDenialDialog(getString(R.string.WebRtcCallActivity__to_call_s_signal_needs_access_to_your_camera, recipientDisplayName)) - .onAllGranted(() -> { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_SET_ENABLE_VIDEO); - intent.putExtra(WebRtcCallService.EXTRA_ENABLE, !muted); - startService(intent); - }) + .onAllGranted(() -> ApplicationDependencies.getSignalCallManager().setMuteVideo(!muted)) .execute(); } } private void handleFlipCamera() { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA); - startService(intent); + ApplicationDependencies.getSignalCallManager().flipCamera(); } private void handleAnswerWithAudio() { @@ -396,9 +369,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan callScreen.setRecipient(recipient); callScreen.setStatus(getString(R.string.RedPhone_answering)); - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL); - startService(intent); + ApplicationDependencies.getSignalCallManager().acceptCall(false); }) .onAnyDenied(this::handleDenyCall) .execute(); @@ -419,10 +390,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan callScreen.setRecipient(recipient); callScreen.setStatus(getString(R.string.RedPhone_answering)); - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_ACCEPT_CALL); - intent.putExtra(WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO, true); - startService(intent); + ApplicationDependencies.getSignalCallManager().acceptCall(true); handleSetMuteVideo(false); }) @@ -435,9 +403,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan Recipient recipient = viewModel.getRecipient().get(); if (!recipient.equals(Recipient.UNKNOWN)) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_DENY_CALL); - startService(intent); + ApplicationDependencies.getSignalCallManager().denyCall(); callScreen.setRecipient(recipient); callScreen.setStatus(getString(R.string.RedPhone_ending_call)); @@ -447,9 +413,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan private void handleEndCall() { Log.i(TAG, "Hangup pressed, handling termination now..."); - Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP); - startService(intent); + ApplicationDependencies.getSignalCallManager().localHangup(); } private void handleOutgoingCall(@NonNull WebRtcViewModel event) { @@ -480,7 +444,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan private void handleCallBusy() { EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); callScreen.setStatus(getString(R.string.RedPhone_busy)); - delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH); + delayedFinish(SignalCallManager.BUSY_TONE_LENGTH); } private void handleCallConnected(@NonNull WebRtcViewModel event) { @@ -537,7 +501,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan } private void updateGroupMembersForGroupCall() { - startService(new Intent(this, WebRtcCallService.class).setAction(WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS)); + ApplicationDependencies.getSignalCallManager().requestUpdateGroupMembers(); } private void updateSpeakerHint(boolean showSpeakerHint) { @@ -551,11 +515,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan @Override public void onSendAnywayAfterSafetyNumberChange(@NonNull List changedRecipients) { CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); + + if (state == null) { + return; + } + if (state.getGroupCallState().isConnected()) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE) - .putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients)); - startService(intent); + ApplicationDependencies.getSignalCallManager().groupApproveSafetyChange(changedRecipients); } else { viewModel.startCall(state.getLocalParticipant().isVideoEnabled()); } @@ -569,9 +535,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); if (state != null && state.getGroupCallState().isNotIdle()) { if (state.getCallState().isPreJoinOrNetworkUnavailable()) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL); - startService(intent); + ApplicationDependencies.getSignalCallManager().cancelPreJoin(); finish(); } else { handleEndCall(); @@ -637,11 +601,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan private void startCall(boolean isVideoCall) { enableVideoIfAvailable = isVideoCall; - Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId())) - .putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, (isVideoCall ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL).getCode()); - startService(intent); + if (isVideoCall) { + ApplicationDependencies.getSignalCallManager().startOutgoingVideoCall(viewModel.getRecipient().get()); + } else { + ApplicationDependencies.getSignalCallManager().startOutgoingAudioCall(viewModel.getRecipient().get()); + } MessageSender.onMessageSent(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 9a84693577..cb5215eabe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -571,7 +571,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } if (groupCallViewModel != null) { - groupCallViewModel.peekGroupCall(this); + groupCallViewModel.peekGroupCall(); } setVisibleThread(threadId); @@ -2210,7 +2210,7 @@ public class ConversationActivity extends PassphraseRequiredActivity groupCallViewModel = ViewModelProviders.of(this, new GroupCallViewModel.Factory()).get(GroupCallViewModel.class); recipient.observe(this, r -> { - groupCallViewModel.onRecipientChange(this, r); + groupCallViewModel.onRecipientChange(r); }); groupCallViewModel.hasActiveGroupCall().observe(this, hasActiveCall -> { @@ -2361,7 +2361,7 @@ public class ConversationActivity extends PassphraseRequiredActivity } if (groupCallViewModel != null) { - groupCallViewModel.onRecipientChange(this, recipient); + groupCallViewModel.onRecipientChange(recipient); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/groupcall/GroupCallViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/groupcall/GroupCallViewModel.java index b19795c097..3487ca48c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/groupcall/GroupCallViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/groupcall/GroupCallViewModel.java @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.conversation.ui.groupcall; -import android.content.Context; -import android.content.Intent; import android.os.Build; import androidx.annotation.NonNull; @@ -12,10 +10,9 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.GroupCallPeekEvent; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.Objects; @@ -46,7 +43,7 @@ public class GroupCallViewModel extends ViewModel { return groupCallHasCapacity; } - public void onRecipientChange(@NonNull Context context, @Nullable Recipient recipient) { + public void onRecipientChange(@Nullable Recipient recipient) { activeGroup.postValue(recipient != null && recipient.isActiveGroup()); if (Objects.equals(currentRecipient, recipient)) { @@ -58,17 +55,13 @@ public class GroupCallViewModel extends ViewModel { currentRecipient = recipient; - peekGroupCall(context); + peekGroupCall(); } - public void peekGroupCall(@NonNull Context context) { + public void peekGroupCall() { if (isGroupCallCapable(currentRecipient)) { Log.i(TAG, "peek call for " + currentRecipient.getId()); - Intent intent = new Intent(context, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_GROUP_CALL_PEEK) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(currentRecipient.getId())); - - context.startService(intent); + ApplicationDependencies.getSignalCallManager().peekGroupCall(currentRecipient.getId()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 218b6b6fa7..fc15f63709 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.service.TrimThreadsByDateManager; +import org.thoughtcrime.securesms.service.webrtc.SignalCallManager; import org.thoughtcrime.securesms.shakereport.ShakeToReport; import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.EarlyMessageCache; @@ -74,6 +75,7 @@ public class ApplicationDependencies { private static volatile DatabaseObserver databaseObserver; private static volatile TrimThreadsByDateManager trimThreadsByDateManager; private static volatile ShakeToReport shakeToReport; + private static volatile SignalCallManager signalCallManager; @MainThread public static void init(@NonNull Application application, @NonNull Provider provider) { @@ -383,6 +385,18 @@ public class ApplicationDependencies { return shakeToReport; } + public static @NonNull SignalCallManager getSignalCallManager() { + if (signalCallManager == null) { + synchronized (LOCK) { + if (signalCallManager == null) { + signalCallManager = provider.provideSignalCallManager(); + } + } + } + + return signalCallManager; + } + public static @NonNull AppForegroundObserver getAppForegroundObserver() { return appForegroundObserver; } @@ -410,5 +424,6 @@ public class ApplicationDependencies { @NonNull DatabaseObserver provideDatabaseObserver(); @NonNull ShakeToReport provideShakeToReport(); @NonNull AppForegroundObserver provideAppForegroundObserver(); + @NonNull SignalCallManager provideSignalCallManager(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 44ca781a63..411c1d0086 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -10,8 +10,8 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; -import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; +import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.jobmanager.JobManager; @@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.service.TrimThreadsByDateManager; +import org.thoughtcrime.securesms.service.webrtc.SignalCallManager; import org.thoughtcrime.securesms.shakereport.ShakeToReport; import org.thoughtcrime.securesms.util.AlarmSleepTimer; import org.thoughtcrime.securesms.util.AppForegroundObserver; @@ -218,6 +219,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr return new AppForegroundObserver(); } + @Override + public @NonNull SignalCallManager provideSignalCallManager() { + return new SignalCallManager(context); + } + private static class DynamicCredentialsProvider implements CredentialsProvider { private final Context context; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java index 8b3cb7e93a..4b221bd01e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallPeekWorkerJob.java @@ -1,14 +1,11 @@ package org.thoughtcrime.securesms.jobs; -import android.content.Intent; - import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; /** * Runs in the same queue as messages for the group. @@ -36,11 +33,7 @@ final class GroupCallPeekWorkerJob extends BaseJob { @Override protected void onRun() { - Intent intent = new Intent(context, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_GROUP_CALL_PEEK) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(groupRecipientId)); - - context.startService(intent); + ApplicationDependencies.getSignalCallManager().peekGroupCall(groupRecipientId); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index aea03c6223..f0fb3ec618 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.messages; import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.os.Build; import android.text.TextUtils; @@ -13,6 +12,7 @@ import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import org.signal.core.util.logging.Log; +import org.signal.ringrtc.CallId; import org.signal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; @@ -80,9 +80,8 @@ import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; +import org.thoughtcrime.securesms.service.webrtc.WebRtcData; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -91,7 +90,6 @@ import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.storage.StorageSyncHelper; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.IdentityUtil; @@ -389,25 +387,17 @@ public final class MessageContentProcessor { MessageDatabase database = DatabaseFactory.getSmsDatabase(context); database.markAsMissedCall(smsMessageId.get(), message.getType() == OfferMessage.Type.VIDEO_CALL); } else { - Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); - intent.setAction(WebRtcCallService.ACTION_RECEIVE_OFFER) - .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer) - .putExtra(WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY, remoteIdentityKey) - .putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice()) - .putExtra(WebRtcCallService.EXTRA_OFFER_OPAQUE, message.getOpaque()) - .putExtra(WebRtcCallService.EXTRA_OFFER_SDP, message.getSdp()) - .putExtra(WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP, content.getServerReceivedTimestamp()) - .putExtra(WebRtcCallService.EXTRA_SERVER_DELIVERED_TIMESTAMP, content.getServerDeliveredTimestamp()) - .putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, message.getType().getCode()) - .putExtra(WebRtcCallService.EXTRA_MULTI_RING, content.getCallMessage().get().isMultiRing()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent); - else context.startService(intent); + ApplicationDependencies.getSignalCallManager() + .receivedOffer(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice()), + new WebRtcData.OfferMetadata(message.getOpaque(), message.getSdp(), message.getType()), + new WebRtcData.ReceivedOfferMetadata(remoteIdentityKey, + content.getServerReceivedTimestamp(), + content.getServerDeliveredTimestamp(), + content.getCallMessage().get().isMultiRing())); } } @@ -415,21 +405,14 @@ public final class MessageContentProcessor { @NonNull AnswerMessage message) { log(String.valueOf(content), "handleCallAnswerMessage..."); - Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); - intent.setAction(WebRtcCallService.ACTION_RECEIVE_ANSWER) - .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, remotePeer) - .putExtra(WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY, remoteIdentityKey) - .putExtra(WebRtcCallService.EXTRA_REMOTE_DEVICE, content.getSenderDevice()) - .putExtra(WebRtcCallService.EXTRA_ANSWER_OPAQUE, message.getOpaque()) - .putExtra(WebRtcCallService.EXTRA_ANSWER_SDP, message.getSdp()) - .putExtra(WebRtcCallService.EXTRA_MULTI_RING, content.getCallMessage().get().isMultiRing()); - - context.startService(intent); + ApplicationDependencies.getSignalCallManager() + .receivedAnswer(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice()), + new WebRtcData.AnswerMetadata(message.getOpaque(), message.getSdp()), + new WebRtcData.ReceivedAnswerMetadata(remoteIdentityKey, content.getCallMessage().get().isMultiRing())); } private void handleCallIceUpdateMessage(@NonNull SignalServiceContent content, @@ -437,23 +420,19 @@ public final class MessageContentProcessor { { log(String.valueOf(content), "handleCallIceUpdateMessage... " + messages.size()); - ArrayList iceCandidates = new ArrayList<>(messages.size()); - long callId = -1; + List iceCandidates = new ArrayList<>(messages.size()); + long callId = -1; + for (IceUpdateMessage iceMessage : messages) { - iceCandidates.add(new IceCandidateParcel(iceMessage)); + iceCandidates.add(iceMessage.getOpaque()); callId = iceMessage.getId(); } - Intent intent = new Intent(context, WebRtcCallService.class); RemotePeer remotePeer = new RemotePeer(Recipient.externalHighTrustPush(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); + ApplicationDependencies.getSignalCallManager() + .receivedIceCandidates(new WebRtcData.CallMetadata(remotePeer, new CallId(callId), content.getSenderDevice()), + iceCandidates); } private void handleCallHangupMessage(@NonNull SignalServiceContent content, @@ -464,18 +443,11 @@ public final class MessageContentProcessor { if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).markAsMissedCall(smsMessageId.get(), false); } else { - Intent intent = new Intent(context, WebRtcCallService.class); RemotePeer remotePeer = new RemotePeer(Recipient.externalHighTrustPush(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()) - .putExtra(WebRtcCallService.EXTRA_HANGUP_IS_LEGACY, message.isLegacy()) - .putExtra(WebRtcCallService.EXTRA_HANGUP_DEVICE_ID, message.getDeviceId()) - .putExtra(WebRtcCallService.EXTRA_HANGUP_TYPE, message.getType().getCode()); - - context.startService(intent); + ApplicationDependencies.getSignalCallManager() + .receivedCallHangup(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice()), + new WebRtcData.HangupMetadata(message.getType(), message.isLegacy(), message.getDeviceId())); } } @@ -484,15 +456,10 @@ public final class MessageContentProcessor { { log(String.valueOf(content.getTimestamp()), "handleCallBusyMessage"); - Intent intent = new Intent(context, WebRtcCallService.class); RemotePeer remotePeer = new RemotePeer(Recipient.externalHighTrustPush(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); + ApplicationDependencies.getSignalCallManager() + .receivedCallBusy(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice())); } private void handleCallOpaqueMessage(@NonNull SignalServiceContent content, @@ -500,20 +467,16 @@ public final class MessageContentProcessor { { log(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); + ApplicationDependencies.getSignalCallManager() + .receivedOpaqueMessage(new WebRtcData.OpaqueMessageMetadata(Recipient.externalHighTrustPush(context, content.getSender()).requireUuid(), + message.getOpaque(), + content.getSenderDevice(), + messageAgeSeconds)); } private void handleGroupCallUpdateMessage(@NonNull SignalServiceContent content, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java index f7b0b0459e..2a6b5d1c9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java @@ -10,8 +10,8 @@ import androidx.preference.Preference; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.webrtc.CallBandwidthMode; @@ -130,7 +130,7 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm throw new AssertionError(); } - WebRtcCallService.notifyBandwidthModeUpdated(requireContext()); + ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate(); return super.onPreferenceChange(preference, value); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/IceCandidateParcel.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/IceCandidateParcel.java deleted file mode 100644 index ef7e93c2c2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/IceCandidateParcel.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.thoughtcrime.securesms.ringrtc; - -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; - -import org.signal.ringrtc.CallId; -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 byte[] iceCandidate; - - public IceCandidateParcel(@NonNull byte[] iceCandidate) { - this.iceCandidate = iceCandidate; - } - - public IceCandidateParcel(@NonNull IceUpdateMessage iceUpdateMessage) { - this.iceCandidate = iceUpdateMessage.getOpaque(); - } - - private IceCandidateParcel(@NonNull Parcel in) { - this.iceCandidate = in.createByteArray(); - } - - public @NonNull byte[] getIceCandidate() { - return iceCandidate; - } - - public @NonNull IceUpdateMessage getIceUpdateMessage(@NonNull CallId callId) { - return new IceUpdateMessage(callId.longValue(), - iceCandidate, - null); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeByteArray(iceCandidate); - } - - public static final Creator CREATOR = new Creator() { - @Override - public IceCandidateParcel createFromParcel(@NonNull Parcel in) { - return new IceCandidateParcel(in); - } - - @Override - public IceCandidateParcel[] newArray(int size) { - return new IceCandidateParcel[size]; - } - }; -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/TurnServerInfoParcel.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/TurnServerInfoParcel.java deleted file mode 100644 index 472a420682..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/TurnServerInfoParcel.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.thoughtcrime.securesms.ringrtc; - -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo; - -import java.util.ArrayList; -import java.util.List; - -/** - * Wrap turn server info so it can be sent via an intent. - */ -public class TurnServerInfoParcel implements Parcelable { - - private final String username; - private final String password; - private final List urls; - - public TurnServerInfoParcel(@NonNull TurnServerInfo turnServerInfo) { - urls = new ArrayList<>(turnServerInfo.getUrls()); - username = turnServerInfo.getUsername(); - password = turnServerInfo.getPassword(); - } - - private TurnServerInfoParcel(@NonNull Parcel in) { - username = in.readString(); - password = in.readString(); - urls = in.createStringArrayList(); - } - - public @Nullable String getUsername() { - return username; - } - - public @Nullable String getPassword() { - return password; - } - - public @NonNull List getUrls() { - return urls; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(username); - dest.writeString(password); - dest.writeStringList(urls); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = new Creator() { - @Override - public TurnServerInfoParcel createFromParcel(Parcel in) { - return new TurnServerInfoParcel(in); - } - - @Override - public TurnServerInfoParcel[] newArray(int size) { - return new TurnServerInfoParcel[size]; - } - }; -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java deleted file mode 100644 index f709ad932a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ /dev/null @@ -1,1164 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; -import android.os.IBinder; -import android.os.ResultReceiver; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -import org.greenrobot.eventbus.EventBus; -import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; -import org.signal.ringrtc.CallException; -import org.signal.ringrtc.CallId; -import org.signal.ringrtc.CallManager; -import org.signal.ringrtc.CallManager.CallEvent; -import org.signal.ringrtc.GroupCall; -import org.signal.ringrtc.HttpHeader; -import org.signal.ringrtc.Remote; -import org.signal.storageservice.protos.groups.GroupExternalCredential; -import org.signal.zkgroup.VerificationFailedException; -import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.WebRtcCallActivity; -import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.events.GroupCallPeekEvent; -import org.thoughtcrime.securesms.events.WebRtcViewModel; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.groups.GroupManager; -import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.ringrtc.CallState; -import org.thoughtcrime.securesms.ringrtc.CameraEventListener; -import org.thoughtcrime.securesms.ringrtc.CameraState; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel; -import org.thoughtcrime.securesms.service.webrtc.IdleActionProcessor; -import org.thoughtcrime.securesms.service.webrtc.WebRtcInteractor; -import org.thoughtcrime.securesms.service.webrtc.WebRtcUtil; -import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; -import org.thoughtcrime.securesms.util.AppForegroundObserver; -import org.thoughtcrime.securesms.util.BubbleUtil; -import org.thoughtcrime.securesms.util.FutureTaskListener; -import org.thoughtcrime.securesms.util.ListenableFutureTask; -import org.thoughtcrime.securesms.util.TelephonyUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; -import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager; -import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager; -import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; -import org.thoughtcrime.securesms.webrtc.locks.LockManager; -import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -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.OfferMessage; -import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE; -import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING; - -public class WebRtcCallService extends Service implements CallManager.Observer, - BluetoothStateManager.BluetoothStateListener, - CameraEventListener, - GroupCall.Observer, - AppForegroundObserver.Listener -{ - - private static final String TAG = Log.tag(WebRtcCallService.class); - - public static final String EXTRA_MUTE = "mute_value"; - public static final String EXTRA_AVAILABLE = "enabled_value"; - public static final String EXTRA_SERVER_RECEIVED_TIMESTAMP = "server_received_timestamp"; - public static final String EXTRA_SERVER_DELIVERED_TIMESTAMP = "server_delivered_timestamp"; - public static final String EXTRA_CALL_ID = "call_id"; - public static final String EXTRA_RESULT_RECEIVER = "result_receiver"; - public static final String EXTRA_SPEAKER = "audio_speaker"; - public static final String EXTRA_BLUETOOTH = "audio_bluetooth"; - public static final String EXTRA_REMOTE_PEER = "remote_peer"; - public static final String EXTRA_REMOTE_PEER_KEY = "remote_peer_key"; - public static final String EXTRA_REMOTE_DEVICE = "remote_device"; - public static final String EXTRA_REMOTE_IDENTITY_KEY = "remote_identity_key"; - public static final String EXTRA_OFFER_OPAQUE = "offer_opaque"; - public static final String EXTRA_OFFER_SDP = "offer_sdp"; - public static final String EXTRA_OFFER_TYPE = "offer_type"; - public static final String EXTRA_MULTI_RING = "multi_ring"; - public static final String EXTRA_HANGUP_TYPE = "hangup_type"; - public static final String EXTRA_HANGUP_IS_LEGACY = "hangup_is_legacy"; - public static final String EXTRA_HANGUP_DEVICE_ID = "hangup_device_id"; - public static final String EXTRA_ANSWER_OPAQUE = "answer_opaque"; - public static final String EXTRA_ANSWER_SDP = "answer_sdp"; - public static final String EXTRA_ICE_CANDIDATES = "ice_candidates"; - public static final String EXTRA_ENABLE = "enable_value"; - public static final String EXTRA_BROADCAST = "broadcast"; - public static final String EXTRA_ANSWER_WITH_VIDEO = "enable_video"; - public static final String EXTRA_ERROR_CALL_STATE = "error_call_state"; - public static final String EXTRA_ERROR_IDENTITY_KEY = "remote_identity_key"; - 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_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 EXTRA_GROUP_CALL_END_REASON = "group_call_end_reason"; - public static final String EXTRA_GROUP_CALL_HASH = "group_call_hash"; - public static final String EXTRA_GROUP_CALL_UPDATE_SENDER = "group_call_update_sender"; - public static final String EXTRA_GROUP_CALL_UPDATE_GROUP = "group_call_update_group"; - public static final String EXTRA_GROUP_CALL_ERA_ID = "era_id"; - public static final String EXTRA_RECIPIENT_IDS = "recipient_ids"; - public static final String EXTRA_ORIENTATION_DEGREES = "orientation_degrees"; - - 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_OUTGOING_CALL = "CALL_OUTGOING"; - public static final String ACTION_DENY_CALL = "DENY_CALL"; - public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP"; - public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO"; - public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA"; - public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE"; - public static final String ACTION_NETWORK_CHANGE = "NETWORK_CHANGE"; - public static final String ACTION_BANDWIDTH_MODE_UPDATE = "BANDWIDTH_MODE_UPDATE"; - public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE"; - public static final String ACTION_SCREEN_OFF = "SCREEN_OFF"; - public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"; - public static final String ACTION_SET_AUDIO_SPEAKER = "SET_AUDIO_SPEAKER"; - public static final String ACTION_SET_AUDIO_BLUETOOTH = "SET_AUDIO_BLUETOOTH"; - public static final String ACTION_CALL_CONNECTED = "CALL_CONNECTED"; - public static final String ACTION_START_OUTGOING_CALL = "START_OUTGOING_CALL"; - public static final String ACTION_START_INCOMING_CALL = "START_INCOMING_CALL"; - public static final String ACTION_LOCAL_RINGING = "LOCAL_RINGING"; - public static final String ACTION_REMOTE_RINGING = "REMOTE_RINGING"; - public static final String ACTION_ACCEPT_CALL = "ACCEPT_CALL"; - public static final String ACTION_SEND_OFFER = "SEND_OFFER"; - public static final String ACTION_SEND_ANSWER = "SEND_ANSWER"; - public static final String ACTION_SEND_ICE_CANDIDATES = "SEND_ICE_CANDIDATES"; - public static final String ACTION_SEND_HANGUP = "SEND_HANGUP"; - public static final String ACTION_SEND_BUSY = "SEND_BUSY"; - public static final String ACTION_RECEIVE_OFFER = "RECEIVE_OFFER"; - public static final String ACTION_RECEIVE_ANSWER = "RECEIVE_ANSWER"; - public static final String ACTION_RECEIVE_ICE_CANDIDATES = "RECEIVE_ICE_CANDIDATES"; - public static final String ACTION_RECEIVE_HANGUP = "RECEIVE_HANGUP"; - public static final String ACTION_RECEIVE_BUSY = "RECEIVE_BUSY"; - public static final String ACTION_REMOTE_VIDEO_ENABLE = "REMOTE_VIDEO_ENABLE"; - public static final String ACTION_SET_ENABLE_VIDEO = "SET_ENABLE_VIDEO"; - public static final String ACTION_ENDED_REMOTE_HANGUP = "ENDED_REMOTE_HANGUP"; - public static final String ACTION_ENDED_REMOTE_HANGUP_ACCEPTED = "ENDED_REMOTE_HANGUP_ACCEPTED"; - public static final String ACTION_ENDED_REMOTE_HANGUP_DECLINED = "ENDED_REMOTE_HANGUP_DECLINED"; - public static final String ACTION_ENDED_REMOTE_HANGUP_BUSY = "ENDED_REMOTE_HANGUP_BUSY"; - public static final String ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION = "ENDED_REMOTE_HANGUP_NEED_PERMISSION"; - public static final String ACTION_ENDED_REMOTE_BUSY = "ENDED_REMOTE_BUSY"; - public static final String ACTION_ENDED_REMOTE_GLARE = "ENDED_REMOTE_GLARE"; - public static final String ACTION_ENDED_TIMEOUT = "ENDED_TIMEOUT"; - public static final String ACTION_ENDED_INTERNAL_FAILURE = "ENDED_INTERNAL_FAILURE"; - public static final String ACTION_ENDED_SIGNALING_FAILURE = "ENDED_SIGNALING_FAILURE"; - public static final String ACTION_ENDED_CONNECTION_FAILURE = "ENDED_CONNECTION_FAILURE"; - public static final String ACTION_RECEIVED_OFFER_EXPIRED = "RECEIVED_OFFER_EXPIRED"; - public static final String ACTION_RECEIVED_OFFER_WHILE_ACTIVE = "RECEIVED_OFFER_WHILE_ACTIVE"; - public static final String ACTION_CALL_CONCLUDED = "CALL_CONCLUDED"; - public static final String ACTION_MESSAGE_SENT_SUCCESS = "MESSAGE_SENT_SUCCESS"; - public static final String ACTION_MESSAGE_SENT_ERROR = "MESSAGE_SENT_ERROR"; - 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_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_ORIENTATION_CHANGED = "ORIENTATION_CHANGED"; - - 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 String ACTION_GROUP_CALL_ENDED = "GROUP_CALL_ENDED"; - public static final String ACTION_GROUP_CALL_PEEK = "GROUP_CALL_PEEK"; - public static final String ACTION_GROUP_MESSAGE_SENT_ERROR = "GROUP_MESSAGE_SENT_ERROR"; - public static final String ACTION_GROUP_APPROVE_SAFETY_CHANGE = "GROUP_APPROVE_SAFETY_CHANGE"; - - public static final int BUSY_TONE_LENGTH = 2000; - - private SignalServiceMessageSender messageSender; - private SignalServiceAccountManager accountManager; - private BluetoothStateManager bluetoothStateManager; - private WiredHeadsetStateReceiver wiredHeadsetStateReceiver; - private NetworkReceiver networkReceiver; - private PowerButtonReceiver powerButtonReceiver; - private LockManager lockManager; - private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; - private WebRtcInteractor webRtcInteractor; - - @Nullable private CallManager callManager; - - private final ExecutorService serviceExecutor = Executors.newSingleThreadExecutor(); - private final ExecutorService networkExecutor = Executors.newSingleThreadExecutor(); - - private final PhoneStateListener hangUpRtcOnDeviceCallAnswered = new HangUpRtcOnPstnCallAnsweredListener(); - - private WebRtcServiceState serviceState; - - @Override - public void onCreate() { - super.onCreate(); - Log.i(TAG, "onCreate"); - - boolean successful = initializeResources(); - if (!successful) { - stopSelf(); - return; - } - - serviceState = new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor)); - - registerUncaughtExceptionHandler(); - registerWiredHeadsetStateReceiver(); - registerNetworkReceiver(); - - TelephonyUtil.getManager(this) - .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE); - } - - private boolean initializeResources() { - this.messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - this.accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - this.lockManager = new LockManager(this); - this.bluetoothStateManager = new BluetoothStateManager(this, this); - - this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); - this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); - - try { - this.callManager = Objects.requireNonNull(CallManager.createCallManager(this)); - } catch (NullPointerException | CallException e) { - Log.e(TAG, "Unable to create Call Manager: ", e); - return false; - } - - webRtcInteractor = new WebRtcInteractor(this, - callManager, - lockManager, - new SignalAudioManager(this), - bluetoothStateManager, - this, - this, - this); - return true; - } - - @Override - public int onStartCommand(final @Nullable Intent intent, int flags, int startId) { - Log.i(TAG, "onStartCommand... action: " + (intent == null ? "NA" : intent.getAction())); - if (intent == null || intent.getAction() == null) return START_NOT_STICKY; - - serviceExecutor.execute(() -> { - Log.d(TAG, "action: " + intent.getAction() + " action handler: " + serviceState.getActionProcessor().getTag()); - try { - WebRtcServiceState previous = serviceState; - serviceState = serviceState.getActionProcessor().processAction(intent.getAction(), intent, serviceState); - - if (previous != serviceState) { - if (serviceState.getCallInfoState().getCallState() != WebRtcViewModel.State.IDLE) { - sendMessage(); - } - } - } catch (AssertionError e) { - throw new AssertionError("Invalid state for action: " + intent.getAction() + " processor: " + serviceState.getActionProcessor().getTag(), e); - } - }); - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.i(TAG, "onDestroy"); - - if (callManager != null) { - try { - callManager.close(); - } catch (CallException e) { - Log.w(TAG, "Unable to close call manager: ", e); - } - callManager = null; - } - - if (uncaughtExceptionHandlerManager != null) { - uncaughtExceptionHandlerManager.unregister(); - } - - if (bluetoothStateManager != null) { - bluetoothStateManager.onDestroy(); - } - - if (wiredHeadsetStateReceiver != null) { - unregisterReceiver(wiredHeadsetStateReceiver); - wiredHeadsetStateReceiver = null; - } - - if (powerButtonReceiver != null) { - unregisterReceiver(powerButtonReceiver); - powerButtonReceiver = null; - } - - unregisterNetworkReceiver(); - - TelephonyUtil.getManager(this) - .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE); - } - - @Override - public void onBluetoothStateChanged(boolean isAvailable) { - Log.i(TAG, "onBluetoothStateChanged: " + isAvailable); - - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(ACTION_BLUETOOTH_CHANGE); - intent.putExtra(EXTRA_AVAILABLE, isAvailable); - - startService(intent); - } - - @Override - public void onCameraSwitchCompleted(@NonNull CameraState newCameraState) { - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(ACTION_CAMERA_SWITCH_COMPLETED) - .putExtra(EXTRA_CAMERA_STATE, newCameraState); - - startService(intent); - } - - private void registerUncaughtExceptionHandler() { - uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager(); - uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager)); - } - - private void registerWiredHeadsetStateReceiver() { - wiredHeadsetStateReceiver = new WiredHeadsetStateReceiver(); - - String action; - - if (Build.VERSION.SDK_INT >= 21) { - action = AudioManager.ACTION_HEADSET_PLUG; - } else { - action = Intent.ACTION_HEADSET_PLUG; - } - - registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action)); - } - - private void registerNetworkReceiver() { - if (networkReceiver == null) { - networkReceiver = new NetworkReceiver(); - - registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } - } - - private void unregisterNetworkReceiver() { - if (networkReceiver != null) { - unregisterReceiver(networkReceiver); - - networkReceiver = null; - } - } - - public void registerPowerButtonReceiver() { - if (powerButtonReceiver == null) { - powerButtonReceiver = new PowerButtonReceiver(); - - registerReceiver(powerButtonReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - } - } - - public void unregisterPowerButtonReceiver() { - if (powerButtonReceiver != null) { - unregisterReceiver(powerButtonReceiver); - - powerButtonReceiver = null; - } - } - - public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) { - Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer); - ApplicationDependencies.getMessageNotifier().updateNotification(this, messageAndThreadId.second(), signal); - } - - public void retrieveTurnServers(@NonNull RemotePeer remotePeer) { - retrieveTurnServers().addListener(new FutureTaskListener() { - @Override - public void onSuccess(@Nullable TurnServerInfoParcel result) { - boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); - - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_TURN_SERVER_UPDATE) - .putExtra(EXTRA_IS_ALWAYS_TURN, isAlwaysTurn) - .putExtra(EXTRA_TURN_SERVER_INFO, result); - - startService(intent); - } - - @Override - public void onFailure(@NonNull ExecutionException exception) { - Log.w(TAG, "Unable to retrieve turn servers: ", exception); - - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_SETUP_FAILURE) - .putExtra(EXTRA_CALL_ID, remotePeer.getCallId().longValue()); - - startService(intent); - } - }); - } - - public void setCallInProgressNotification(int type, @NonNull Recipient recipient) { - startForeground(CallNotificationBuilder.getNotificationId(type), - CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)); - } - - public void sendMessage() { - sendMessage(serviceState); - } - - public void sendMessage(@NonNull WebRtcServiceState state) { - EventBus.getDefault().postSticky(new WebRtcViewModel(state)); - } - - private @NonNull ListenableFutureTask sendMessage(@NonNull final RemotePeer remotePeer, - @NonNull final SignalServiceCallMessage callMessage) - { - Callable callable = () -> { - Recipient recipient = remotePeer.getRecipient(); - if (recipient.isBlocked()) { - return true; - } - - messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(WebRtcCallService.this, recipient), - UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient), - callMessage); - return true; - }; - - ListenableFutureTask listenableFutureTask = new ListenableFutureTask<>(callable, null, serviceExecutor); - networkExecutor.execute(listenableFutureTask); - - return listenableFutureTask; - } - - public boolean startCallCardActivityIfPossible() { - if (Build.VERSION.SDK_INT >= 29 && !ApplicationDependencies.getAppForegroundObserver().isForegrounded()) { - return false; - } - - Intent activityIntent = new Intent(); - activityIntent.setClass(this, WebRtcCallActivity.class); - activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(activityIntent); - return true; - } - - private static @NonNull OfferMessage.Type getOfferTypeFromCallMediaType(@NonNull CallManager.CallMediaType mediaType) { - return mediaType == CallManager.CallMediaType.VIDEO_CALL ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL; - } - - private static @NonNull HangupMessage.Type getHangupTypeFromCallHangupType(@NonNull CallManager.HangupType hangupType) { - switch (hangupType) { - case ACCEPTED: - return HangupMessage.Type.ACCEPTED; - case BUSY: - return HangupMessage.Type.BUSY; - case NORMAL: - return HangupMessage.Type.NORMAL; - case DECLINED: - return HangupMessage.Type.DECLINED; - case NEED_PERMISSION: - return HangupMessage.Type.NEED_PERMISSION; - default: - throw new IllegalArgumentException("Unexpected hangup type: " + hangupType); - } - } - - @Override - public @Nullable IBinder onBind(@NonNull Intent intent) { - return null; - } - - @Override - public void onForeground() { - WebRtcViewModel.State callState = serviceState.getCallInfoState().getCallState(); - if (callState == CALL_INCOMING && serviceState.getCallInfoState().getGroupCallState() == IDLE) { - startCallCardActivityIfPossible(); - } - ApplicationDependencies.getAppForegroundObserver().removeListener(this); - } - - @Override - public void onBackground() { } - - private static class WiredHeadsetStateReceiver extends BroadcastReceiver { - @Override - public void onReceive(@NonNull Context context, @NonNull Intent intent) { - int state = intent.getIntExtra("state", -1); - - Intent serviceIntent = new Intent(context, WebRtcCallService.class); - serviceIntent.setAction(ACTION_WIRED_HEADSET_CHANGE); - serviceIntent.putExtra(WebRtcCallService.EXTRA_AVAILABLE, state != 0); - context.startService(serviceIntent); - } - } - - private static class NetworkReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - Intent serviceIntent = new Intent(context, WebRtcCallService.class); - - serviceIntent.setAction(ACTION_NETWORK_CHANGE); - serviceIntent.putExtra(EXTRA_AVAILABLE, activeNetworkInfo != null && activeNetworkInfo.isConnected()); - context.startService(serviceIntent); - - serviceIntent.setAction(ACTION_BANDWIDTH_MODE_UPDATE); - context.startService(serviceIntent); - } - } - - private static class PowerButtonReceiver extends BroadcastReceiver { - @Override - public void onReceive(@NonNull Context context, @NonNull Intent intent) { - if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - Intent serviceIntent = new Intent(context, WebRtcCallService.class); - serviceIntent.setAction(ACTION_SCREEN_OFF); - context.startService(serviceIntent); - } - } - } - - private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler { - private final LockManager lockManager; - - private ProximityLockRelease(@NonNull LockManager lockManager) { - this.lockManager = lockManager; - } - - @Override - public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { - Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable); - lockManager.updatePhoneState(LockManager.PhoneState.IDLE); - } - } - - public static void isCallActive(@NonNull Context context, @NonNull ResultReceiver resultReceiver) { - Intent intent = new Intent(context, WebRtcCallService.class); - intent.setAction(ACTION_IS_IN_CALL_QUERY); - intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver); - - context.startService(intent); - } - - public static void notifyBandwidthModeUpdated(@NonNull Context context) { - Intent intent = new Intent(context, WebRtcCallService.class); - intent.setAction(ACTION_BANDWIDTH_MODE_UPDATE); - - context.startService(intent); - } - - private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener { - @Override - public void onCallStateChanged(int state, @NonNull String phoneNumber) { - super.onCallStateChanged(state, phoneNumber); - if (state == TelephonyManager.CALL_STATE_OFFHOOK) { - hangup(); - Log.i(TAG, "Device phone call ended Signal call."); - } - } - - private void hangup() { - if (serviceState != null && serviceState.getCallInfoState().getActivePeer() != null) { - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_LOCAL_HANGUP); - - startService(intent); - } - } - } - - private @NonNull ListenableFutureTask retrieveTurnServers() { - Callable callable = () -> new TurnServerInfoParcel(accountManager.getTurnServerInfo()); - - ListenableFutureTask futureTask = new ListenableFutureTask<>(callable, null, serviceExecutor); - networkExecutor.execute(futureTask); - - return futureTask; - } - - private abstract class StateAwareListener implements FutureTaskListener { - private final CallState expectedState; - private final CallId expectedCallId; - - StateAwareListener(@NonNull CallState expectedState, @NonNull CallId expectedCallId) { - this.expectedState = expectedState; - this.expectedCallId = expectedCallId; - } - - public @NonNull CallId getCallId() { - return this.expectedCallId; - } - - @Override - public void onSuccess(@Nullable V result) { - if (!isConsistentState()) { - Log.i(TAG, "State has changed since request, skipping success callback..."); - onStateChangeContinue(); - } else { - onSuccessContinue(result); - } - } - - @Override - public void onFailure(@NonNull ExecutionException throwable) { - if (!isConsistentState()) { - Log.w(TAG, throwable); - Log.w(TAG, "State has changed since request, skipping failure callback..."); - onStateChangeContinue(); - } else { - onFailureContinue(throwable.getCause()); - } - } - - public void onStateChangeContinue() {} - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean isConsistentState() { - RemotePeer activePeer = serviceState.getCallInfoState().getActivePeer(); - return activePeer != null && expectedState == activePeer.getState() && expectedCallId.equals(activePeer.getCallId()); - } - - public abstract void onSuccessContinue(@Nullable V result); - public abstract void onFailureContinue(@Nullable Throwable throwable); - } - - private class SendCallMessageListener extends StateAwareListener { - SendCallMessageListener(@NonNull RemotePeer expectedRemotePeer) { - super(expectedRemotePeer.getState(), expectedRemotePeer.getCallId()); - } - - @Override - public void onSuccessContinue(@Nullable V result) { - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_MESSAGE_SENT_SUCCESS); - intent.putExtra(EXTRA_CALL_ID, getCallId().longValue()); - - startService(intent); - } - - @Override - public void onStateChangeContinue() { - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_MESSAGE_SENT_SUCCESS) - .putExtra(EXTRA_CALL_ID, getCallId().longValue()); - - startService(intent); - } - - @Override - public void onFailureContinue(@Nullable Throwable error) { - Log.i(TAG, "onFailureContinue: ", error); - - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_MESSAGE_SENT_ERROR) - .putExtra(EXTRA_CALL_ID, getCallId().longValue()); - - WebRtcViewModel.State state = WebRtcViewModel.State.NETWORK_FAILURE; - - if (error instanceof UntrustedIdentityException) { - intent.putExtra(EXTRA_ERROR_IDENTITY_KEY, new IdentityKeyParcelable(((UntrustedIdentityException) error).getIdentityKey())); - state = WebRtcViewModel.State.UNTRUSTED_IDENTITY; - } else if (error instanceof UnregisteredUserException) { - state = WebRtcViewModel.State.NO_SUCH_USER; - } - - intent.putExtra(EXTRA_ERROR_CALL_STATE, state); - - startService(intent); - } - } - - public void sendCallMessage(@NonNull RemotePeer remotePeer, @NonNull SignalServiceCallMessage callMessage) { - ListenableFutureTask listenableFutureTask = sendMessage(remotePeer, callMessage); - listenableFutureTask.addListener(new SendCallMessageListener<>(remotePeer)); - } - - public void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage opaqueMessage) { - RecipientId recipientId = RecipientId.from(uuid, null); - ListenableFutureTask listenableFutureTask = sendMessage(new RemotePeer(recipientId), opaqueMessage); - listenableFutureTask.addListener(new FutureTaskListener() { - @Override - public void onSuccess(Boolean result) { - // intentionally left blank - } - - @Override - public void onFailure(ExecutionException exception) { - Throwable error = exception.getCause(); - - Log.i(TAG, "sendOpaqueCallMessage onFailure: ", error); - - Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); - intent.setAction(ACTION_GROUP_MESSAGE_SENT_ERROR); - - WebRtcViewModel.State state = WebRtcViewModel.State.NETWORK_FAILURE; - - if (error instanceof UntrustedIdentityException) { - intent.putExtra(EXTRA_ERROR_IDENTITY_KEY, new IdentityKeyParcelable(((UntrustedIdentityException) error).getIdentityKey())); - state = WebRtcViewModel.State.UNTRUSTED_IDENTITY; - } - - intent.putExtra(EXTRA_ERROR_CALL_STATE, state); - intent.putExtra(EXTRA_REMOTE_PEER, new RemotePeer(recipientId)); - - startService(intent); - } - }); - } - - public void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) { - SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId))); - } - - public void peekGroupCall(@NonNull RecipientId id) { - networkExecutor.execute(() -> { - try { - Recipient group = Recipient.resolved(id); - GroupId.V2 groupId = group.requireGroupId().requireV2(); - GroupExternalCredential credential = GroupManager.getGroupExternalCredential(this, groupId); - - List members = Stream.of(GroupManager.getUuidCipherTexts(this, groupId)) - .map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize())) - .toList(); - - //noinspection ConstantConditions - callManager.peekGroupCall(BuildConfig.SIGNAL_SFU_URL, credential.getTokenBytes().toByteArray(), members, peekInfo -> { - long threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(group); - - DatabaseFactory.getSmsDatabase(this).updatePreviousGroupCall(threadId, - peekInfo.getEraId(), - peekInfo.getJoinedMembers(), - WebRtcUtil.isCallFull(peekInfo)); - - ApplicationDependencies.getMessageNotifier().updateNotification(this, threadId, true, 0, BubbleUtil.BubbleState.HIDDEN); - - EventBus.getDefault().postSticky(new GroupCallPeekEvent(id, peekInfo.getEraId(), peekInfo.getDeviceCount(), peekInfo.getMaxDevices())); - }); - - } catch (IOException | VerificationFailedException | CallException e) { - Log.e(TAG, "error peeking from active conversation", e); - } - }); - } - - public void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection joinedMembers, boolean isCallFull) { - SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(groupId, - Recipient.self().getId(), - System.currentTimeMillis(), - groupCallEraId, - joinedMembers, - isCallFull)); - } - - @Override - 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); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer) remote; - if (serviceState.getCallInfoState().getPeer(remotePeer.hashCode()) == null) { - Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping."); - try { - callManager.drop(callId); - } catch (CallException e) { - serviceState = serviceState.getActionProcessor().callFailure(serviceState, "callManager.drop() failed: ", e); - } - } - - remotePeer.setCallId(callId); - - Intent intent = new Intent(this, WebRtcCallService.class); - - if (isOutgoing) { - intent.setAction(ACTION_START_OUTGOING_CALL); - } else { - intent.setAction(ACTION_START_INCOMING_CALL); - } - - intent.putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onCallEvent(@Nullable Remote remote, @NonNull CallEvent event) { - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer) remote; - if (serviceState.getCallInfoState().getPeer(remotePeer.hashCode()) == null) { - throw new AssertionError("remotePeer not found in map!"); - } - - Log.i(TAG, "onCallEvent(): call_id: " + remotePeer.getCallId() + ", state: " + remotePeer.getState() + ", event: " + event); - - Intent intent = new Intent(this, WebRtcCallService.class); - intent.putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); - - switch (event) { - case LOCAL_RINGING: - intent.setAction(ACTION_LOCAL_RINGING); - break; - case REMOTE_RINGING: - intent.setAction(ACTION_REMOTE_RINGING); - break; - case RECONNECTING: - Log.i(TAG, "Reconnecting: NOT IMPLEMENTED"); - break; - case RECONNECTED: - Log.i(TAG, "Reconnected: NOT IMPLEMENTED"); - break; - case LOCAL_CONNECTED: - case REMOTE_CONNECTED: - intent.setAction(ACTION_CALL_CONNECTED); - break; - case REMOTE_VIDEO_ENABLE: - intent.setAction(ACTION_REMOTE_VIDEO_ENABLE) - .putExtra(EXTRA_ENABLE, true); - break; - case REMOTE_VIDEO_DISABLE: - intent.setAction(ACTION_REMOTE_VIDEO_ENABLE) - .putExtra(EXTRA_ENABLE, false); - break; - case ENDED_REMOTE_HANGUP: - intent.setAction(ACTION_ENDED_REMOTE_HANGUP); - break; - case ENDED_REMOTE_HANGUP_NEED_PERMISSION: - intent.setAction(ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION); - break; - case ENDED_REMOTE_HANGUP_ACCEPTED: - intent.setAction(ACTION_ENDED_REMOTE_HANGUP_ACCEPTED); - break; - case ENDED_REMOTE_HANGUP_BUSY: - intent.setAction(ACTION_ENDED_REMOTE_HANGUP_BUSY); - break; - case ENDED_REMOTE_HANGUP_DECLINED: - intent.setAction(ACTION_ENDED_REMOTE_HANGUP_DECLINED); - break; - case ENDED_REMOTE_BUSY: - intent.setAction(ACTION_ENDED_REMOTE_BUSY); - break; - case ENDED_REMOTE_GLARE: - intent.setAction(ACTION_ENDED_REMOTE_GLARE); - break; - case ENDED_TIMEOUT: - intent.setAction(ACTION_ENDED_TIMEOUT); - break; - case ENDED_INTERNAL_FAILURE: - intent.setAction(ACTION_ENDED_INTERNAL_FAILURE); - break; - case ENDED_SIGNALING_FAILURE: - intent.setAction(ACTION_ENDED_SIGNALING_FAILURE); - break; - case ENDED_CONNECTION_FAILURE: - intent.setAction(ACTION_ENDED_CONNECTION_FAILURE); - break; - case RECEIVED_OFFER_EXPIRED: - intent.setAction(ACTION_RECEIVED_OFFER_EXPIRED); - break; - case RECEIVED_OFFER_WHILE_ACTIVE: - case RECEIVED_OFFER_WITH_GLARE: - intent.setAction(ACTION_RECEIVED_OFFER_WHILE_ACTIVE); - break; - case ENDED_LOCAL_HANGUP: - case ENDED_APP_DROPPED_CALL: - case IGNORE_CALLS_FROM_NON_MULTIRING_CALLERS: - Log.i(TAG, "Ignoring event: " + event); - return; - default: - throw new AssertionError("Unexpected event: " + event.toString()); - } - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onCallConcluded(@Nullable Remote remote) { - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - - Log.i(TAG, "onCallConcluded: call_id: " + remotePeer.getCallId()); - - Intent intent = new Intent(this, WebRtcCallService.class); - intent.setAction(ACTION_CALL_CONCLUDED) - .putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendOffer(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull byte[] opaque, @NonNull CallManager.CallMediaType callMediaType) { - Log.i(TAG, "onSendOffer: id: " + callId.format(remoteDevice) + " type: " + callMediaType.name()); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - String offerType = getOfferTypeFromCallMediaType(callMediaType).getCode(); - Intent intent = new Intent(this, WebRtcCallService.class); - - intent.setAction(ACTION_SEND_OFFER) - .putExtra(EXTRA_CALL_ID, callId.longValue()) - .putExtra(EXTRA_REMOTE_PEER, remotePeer) - .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) - .putExtra(EXTRA_BROADCAST, broadcast) - .putExtra(EXTRA_OFFER_OPAQUE, opaque) - .putExtra(EXTRA_OFFER_TYPE, offerType); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendAnswer(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull byte[] opaque) { - Log.i(TAG, "onSendAnswer: id: " + callId.format(remoteDevice)); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); - - intent.setAction(ACTION_SEND_ANSWER) - .putExtra(EXTRA_CALL_ID, callId.longValue()) - .putExtra(EXTRA_REMOTE_PEER, remotePeer) - .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) - .putExtra(EXTRA_BROADCAST, broadcast) - .putExtra(EXTRA_ANSWER_OPAQUE, opaque); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendIceCandidates(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull List iceCandidates) { - Log.i(TAG, "onSendIceCandidates: id: " + callId.format(remoteDevice)); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); - - ArrayList iceCandidateParcels = new ArrayList<>(iceCandidates.size()); - for (byte[] iceCandidate : iceCandidates) { - iceCandidateParcels.add(new IceCandidateParcel(iceCandidate)); - } - - intent.setAction(ACTION_SEND_ICE_CANDIDATES) - .putExtra(EXTRA_CALL_ID, callId.longValue()) - .putExtra(EXTRA_REMOTE_PEER, remotePeer) - .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) - .putExtra(EXTRA_BROADCAST, broadcast) - .putParcelableArrayListExtra(EXTRA_ICE_CANDIDATES, iceCandidateParcels); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendHangup(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull CallManager.HangupType hangupType, @NonNull Integer deviceId, @NonNull Boolean useLegacyHangupMessage) { - Log.i(TAG, "onSendHangup: id: " + callId.format(remoteDevice) + " type: " + hangupType.name() + " isLegacy: " + useLegacyHangupMessage); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); - - intent.setAction(ACTION_SEND_HANGUP) - .putExtra(EXTRA_CALL_ID, callId.longValue()) - .putExtra(EXTRA_REMOTE_PEER, remotePeer) - .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) - .putExtra(EXTRA_BROADCAST, broadcast) - .putExtra(EXTRA_HANGUP_DEVICE_ID, deviceId.intValue()) - .putExtra(EXTRA_HANGUP_IS_LEGACY, useLegacyHangupMessage.booleanValue()) - .putExtra(EXTRA_HANGUP_TYPE, getHangupTypeFromCallHangupType(hangupType).getCode()); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendBusy(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast) { - Log.i(TAG, "onSendBusy: id: " + callId.format(remoteDevice)); - - if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); - - intent.setAction(ACTION_SEND_BUSY) - .putExtra(EXTRA_CALL_ID, callId.longValue()) - .putExtra(EXTRA_REMOTE_PEER, remotePeer) - .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) - .putExtra(EXTRA_BROADCAST, broadcast); - - startService(intent); - } else { - throw new AssertionError("Received remote is not instanceof RemotePeer"); - } - } - - @Override - public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] opaque) { - 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 - public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull CallManager.HttpMethod httpMethod, @Nullable List headers, @Nullable byte[] body) { - Log.i(TAG, "onSendHttpRequest(): request_id: " + requestId); - networkExecutor.execute(() -> { - List> 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()) - .putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode()); - - startService(intent); - } catch (IOException e) { - Log.w(TAG, "Unable to get group membership proof from service", e); - onEnded(groupCall, GroupCall.GroupCallEndReason.SFU_CLIENT_FAILED_TO_JOIN); - } catch (VerificationFailedException e) { - Log.w(TAG, "Unable to verify group membership proof", e); - onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED); - } - }); - } - - @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 onPeekChanged(@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) { - Intent intent = new Intent(this, WebRtcCallService.class); - - intent.setAction(ACTION_GROUP_CALL_ENDED) - .putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode()) - .putExtra(EXTRA_GROUP_CALL_END_REASON, groupCallEndReason.ordinal()); - - startService(intent); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java index 132a3e6d26..a66b47ee45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java @@ -5,35 +5,28 @@ import android.os.ResultReceiver; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.annimon.stream.Stream; + import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; +import org.signal.ringrtc.CallManager.CallEvent; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.ringrtc.CallState; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger; import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Objects; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_BUSY; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_GLARE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_ACCEPTED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_BUSY; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_DECLINED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION; -import static org.thoughtcrime.securesms.service.WebRtcCallService.BUSY_TONE_LENGTH; import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED; import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_CONNECTING; import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING; @@ -46,13 +39,13 @@ import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUT */ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { - private static final Map ENDED_ACTION_TO_STATE = new HashMap() {{ - put(ACTION_ENDED_REMOTE_HANGUP_ACCEPTED, WebRtcViewModel.State.CALL_ACCEPTED_ELSEWHERE); - put(ACTION_ENDED_REMOTE_HANGUP_BUSY, WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE); - put(ACTION_ENDED_REMOTE_HANGUP_DECLINED, WebRtcViewModel.State.CALL_DECLINED_ELSEWHERE); - put(ACTION_ENDED_REMOTE_BUSY, WebRtcViewModel.State.CALL_BUSY); - put(ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION, WebRtcViewModel.State.CALL_NEEDS_PERMISSION); - put(ACTION_ENDED_REMOTE_GLARE, WebRtcViewModel.State.CALL_DISCONNECTED); + private static final Map ENDED_REMOTE_EVENT_TO_STATE = new HashMap() {{ + put(CallEvent.ENDED_REMOTE_HANGUP_ACCEPTED, WebRtcViewModel.State.CALL_ACCEPTED_ELSEWHERE); + put(CallEvent.ENDED_REMOTE_HANGUP_BUSY, WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE); + put(CallEvent.ENDED_REMOTE_HANGUP_DECLINED, WebRtcViewModel.State.CALL_DECLINED_ELSEWHERE); + put(CallEvent.ENDED_REMOTE_BUSY, WebRtcViewModel.State.CALL_BUSY); + put(CallEvent.ENDED_REMOTE_HANGUP_NEED_PERMISSION, WebRtcViewModel.State.CALL_NEEDS_PERMISSION); + put(CallEvent.ENDED_REMOTE_GLARE, WebRtcViewModel.State.CALL_DISCONNECTED); }}; public ActiveCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) { @@ -71,14 +64,13 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast, - @NonNull ArrayList iceCandidates) + @NonNull List iceCandidates) { Log.i(tag, "handleSendIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice())); - LinkedList iceUpdateMessages = new LinkedList<>(); - for (IceCandidateParcel parcel : iceCandidates) { - iceUpdateMessages.add(parcel.getIceUpdateMessage(callMetadata.getCallId())); - } + List iceUpdateMessages = Stream.of(iceCandidates) + .map(c -> new IceUpdateMessage(callMetadata.getCallId().longValue(), c, null)) + .toList(); Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice(); SignalServiceCallMessage callMessage = SignalServiceCallMessage.forIceUpdates(iceUpdateMessages, true, destinationDeviceId); @@ -89,7 +81,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { } @Override - protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) { + protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) { RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer(); Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId()); @@ -118,7 +110,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { .callState(WebRtcViewModel.State.CALL_DISCONNECTED) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return terminate(currentState, currentState.getCallInfoState().getActivePeer()); } catch (CallException e) { @@ -142,36 +134,45 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { } @Override - protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) { + protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) { RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer(); Log.i(tag, "handleReceivedOfferWhileActive(): call_id: " + remotePeer.getCallId()); switch (activePeer.getState()) { case DIALING: - case REMOTE_RINGING: webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); break; + case REMOTE_RINGING: + webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); + break; case IDLE: - case ANSWERING: webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); break; - case LOCAL_RINGING: webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); break; - case CONNECTED: webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, activePeer); break; - default: throw new IllegalStateException(); + case ANSWERING: + webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); + break; + case LOCAL_RINGING: + webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); + break; + case CONNECTED: + webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, activePeer); + break; + default: + throw new IllegalStateException(); } if (activePeer.getState() == CallState.IDLE) { webRtcInteractor.stopForegroundService(); } - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); return terminate(currentState, remotePeer); } @Override protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, - @NonNull String action, + @NonNull CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) { - Log.i(tag, "handleEndedRemote(): call_id: " + remotePeer.getCallId() + " action: " + action); + Log.i(tag, "handleEndedRemote(): call_id: " + remotePeer.getCallId() + " action: " + endedRemoteEvent); WebRtcViewModel.State state = currentState.getCallInfoState().getCallState(); RemotePeer activePeer = currentState.getCallInfoState().getActivePeer(); @@ -179,26 +180,26 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { boolean outgoingBeforeAccept = remotePeer.getState() == CallState.DIALING || remotePeer.getState() == CallState.REMOTE_RINGING; boolean incomingBeforeAccept = remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING; - if (remotePeerIsActive && ENDED_ACTION_TO_STATE.containsKey(action)) { - state = Objects.requireNonNull(ENDED_ACTION_TO_STATE.get(action)); + if (remotePeerIsActive && ENDED_REMOTE_EVENT_TO_STATE.containsKey(endedRemoteEvent)) { + state = Objects.requireNonNull(ENDED_REMOTE_EVENT_TO_STATE.get(endedRemoteEvent)); } - if (action.equals(ACTION_ENDED_REMOTE_HANGUP)) { + if (endedRemoteEvent == CallEvent.ENDED_REMOTE_HANGUP) { if (remotePeerIsActive) { state = outgoingBeforeAccept ? WebRtcViewModel.State.RECIPIENT_UNAVAILABLE : WebRtcViewModel.State.CALL_DISCONNECTED; } if (incomingBeforeAccept) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } - } else if (action.equals(ACTION_ENDED_REMOTE_BUSY) && remotePeerIsActive) { + } else if (endedRemoteEvent == CallEvent.ENDED_REMOTE_BUSY && remotePeerIsActive) { activePeer.receivedBusy(); OutgoingRinger ringer = new OutgoingRinger(context); ringer.start(OutgoingRinger.Type.BUSY); - ThreadUtil.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH); - } else if (action.equals(ACTION_ENDED_REMOTE_GLARE) && incomingBeforeAccept) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + ThreadUtil.runOnMainDelayed(ringer::stop, SignalCallManager.BUSY_TONE_LENGTH); + } else if (endedRemoteEvent == CallEvent.ENDED_REMOTE_GLARE && incomingBeforeAccept) { + webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } currentState = currentState.builder() @@ -206,33 +207,33 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { .callState(state) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return terminate(currentState, remotePeer); } @Override - protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - Log.i(tag, "handleEnded(): call_id: " + remotePeer.getCallId() + " action: " + action); + protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallEvent endedEvent, @NonNull RemotePeer remotePeer) { + Log.i(tag, "handleEnded(): call_id: " + remotePeer.getCallId() + " action: " + endedEvent); if (remotePeer.callIdEquals(currentState.getCallInfoState().getActivePeer()) && !currentState.getCallInfoState().getCallState().isErrorState()) { currentState = currentState.builder() .changeCallInfoState() - .callState(WebRtcViewModel.State.NETWORK_FAILURE) + .callState(endedEvent == CallEvent.ENDED_TIMEOUT ? WebRtcViewModel.State.RECIPIENT_UNAVAILABLE : WebRtcViewModel.State.NETWORK_FAILURE) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); } if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) { - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } return terminate(currentState, remotePeer); } @Override - protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) { + protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) { Log.i(tag, "handleSetupFailure(): call_id: " + callId); RemotePeer activePeer = currentState.getCallInfoState().getActivePeer(); @@ -253,10 +254,10 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { .callState(WebRtcViewModel.State.NETWORK_FAILURE) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); if (activePeer.getState() == CallState.ANSWERING || activePeer.getState() == CallState.LOCAL_RINGING) { - webRtcInteractor.insertMissedCall(activePeer, true, activePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + webRtcInteractor.insertMissedCall(activePeer, activePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } return terminate(currentState, activePeer); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ConnectedCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ConnectedCallActionProcessor.java index 430f092189..786ccc8ade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ConnectedCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ConnectedCallActionProcessor.java @@ -7,12 +7,12 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; +import org.signal.ringrtc.CallManager; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.webrtc.locks.LockManager; -import java.util.ArrayList; +import java.util.List; /** * Handles action for a connected/ongoing call. At this point it's mostly responding @@ -85,7 +85,7 @@ public class ConnectedCallActionProcessor extends DeviceAwareActionProcessor { protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast, - @NonNull ArrayList iceCandidates) + @NonNull List iceCandidates) { return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates); } @@ -96,13 +96,13 @@ public class ConnectedCallActionProcessor extends DeviceAwareActionProcessor { } @Override - protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer); } @Override - protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEnded(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/DeviceAwareActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/DeviceAwareActionProcessor.java index 0f7b5bf2cc..8ca8a14771 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/DeviceAwareActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/DeviceAwareActionProcessor.java @@ -33,7 +33,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor { androidAudioManager.setSpeakerphoneOn(true); } - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return currentState; } @@ -61,7 +61,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor { webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context)); } - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return currentState; } @@ -76,7 +76,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor { webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context)); } - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return currentState; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java index 4484fe9b37..fd05776230 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java @@ -26,8 +26,6 @@ import org.webrtc.VideoTrack; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.util.guava.Optional; 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 java.util.ArrayList; import java.util.Collections; @@ -54,7 +52,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { Log.i(tag, "In a group call, send busy back to 1:1 call offer."); currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } @@ -183,36 +181,6 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { 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():"); @@ -315,7 +283,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { .groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return terminateGroupCall(currentState); } @@ -336,7 +304,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { .groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); try { if (groupCall != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java index 2999602b35..d31db31ff4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java @@ -84,7 +84,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor { .cameraState(camera.getCameraState()) .build(); - WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate()); + WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getCallSetupState().isEnableVideoOnCreate()); return currentState; } @@ -157,7 +157,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor { .groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return terminateGroupCall(currentState); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java index c1481b779b..adff8325a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupJoiningActionProcessor.java @@ -7,7 +7,6 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; -import org.signal.ringrtc.CallManager; import org.signal.ringrtc.GroupCall; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.ringrtc.Camera; @@ -120,7 +119,7 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor { .groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return terminateGroupCall(currentState); } @@ -142,7 +141,7 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor { .cameraState(camera.getCameraState()) .build(); - WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate()); + WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getCallSetupState().isEnableVideoOnCreate()); return currentState; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index 59ff052d49..467c3c3933 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -9,6 +9,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; +import org.signal.ringrtc.CallManager; import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; @@ -18,7 +19,6 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.ringrtc.CallState; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.VideoState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; @@ -29,7 +29,6 @@ import org.webrtc.PeerConnection; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -101,7 +100,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { } webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return currentState; } @@ -204,13 +203,13 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { } @Override - protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer); } @Override - protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEnded(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer); } @Override @@ -227,7 +226,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast, - @NonNull ArrayList iceCandidates) + @NonNull List iceCandidates) { return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java index 5148fbdbe1..06966c090a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java @@ -9,12 +9,12 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; +import org.signal.ringrtc.CallManager; import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.WebRtcData.CallMetadata; import org.thoughtcrime.securesms.service.webrtc.WebRtcData.OfferMetadata; @@ -23,13 +23,11 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; import org.thoughtcrime.securesms.util.NetworkUtil; import org.thoughtcrime.securesms.util.ServiceUtil; -import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger; import org.webrtc.PeerConnection; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -73,7 +71,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context)); webRtcInteractor.initializeAudioForCall(); - webRtcInteractor.startOutgoingRinger(OutgoingRinger.Type.RINGING); + webRtcInteractor.startOutgoingRinger(); webRtcInteractor.setWantsBluetoothConnection(true); webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, remotePeer); @@ -203,13 +201,13 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { } @Override - protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer); } @Override - protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { - return activeCallDelegate.handleEnded(currentState, action, remotePeer); + protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) { + return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer); } @Override @@ -224,9 +222,9 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { @Override protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, - @NonNull WebRtcData.CallMetadata callMetadata, + @NonNull CallMetadata callMetadata, boolean broadcast, - @NonNull ArrayList iceCandidates) + @NonNull List iceCandidates) { return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/PreJoinActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/PreJoinActionProcessor.java index 4ab7cec7be..413d38fe99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/PreJoinActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/PreJoinActionProcessor.java @@ -41,7 +41,7 @@ public class PreJoinActionProcessor extends DeviceAwareActionProcessor { .callState(WebRtcViewModel.State.CALL_INCOMING) .build(); - webRtcInteractor.sendMessage(currentState); + webRtcInteractor.postStateUpdate(currentState); return beginCallDelegate.handleStartIncomingCall(currentState, remotePeer); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java new file mode 100644 index 0000000000..0fa75fab56 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -0,0 +1,760 @@ +package org.thoughtcrime.securesms.service.webrtc; + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.ResultReceiver; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.annimon.stream.Stream; + +import org.greenrobot.eventbus.EventBus; +import org.signal.core.util.concurrent.SignalExecutors; +import org.signal.core.util.logging.Log; +import org.signal.ringrtc.CallException; +import org.signal.ringrtc.CallId; +import org.signal.ringrtc.CallManager; +import org.signal.ringrtc.GroupCall; +import org.signal.ringrtc.HttpHeader; +import org.signal.ringrtc.Remote; +import org.signal.storageservice.protos.groups.GroupExternalCredential; +import org.signal.zkgroup.VerificationFailedException; +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.WebRtcCallActivity; +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.events.GroupCallPeekEvent; +import org.thoughtcrime.securesms.events.WebRtcViewModel; +import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupManager; +import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientUtil; +import org.thoughtcrime.securesms.ringrtc.CameraEventListener; +import org.thoughtcrime.securesms.ringrtc.CameraState; +import org.thoughtcrime.securesms.ringrtc.RemotePeer; +import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; +import org.thoughtcrime.securesms.util.AppForegroundObserver; +import org.thoughtcrime.securesms.util.BubbleUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; +import org.thoughtcrime.securesms.webrtc.locks.LockManager; +import org.webrtc.PeerConnection; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.calls.CallingResponse; +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.TurnServerInfo; +import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NETWORK_FAILURE; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NO_SUCH_USER; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.UNTRUSTED_IDENTITY; + +/** + * Entry point for all things calling. Lives for the life of the app instance and will spin up a foreground service when needed to + * handle "active" calls. + */ +public final class SignalCallManager implements CallManager.Observer, GroupCall.Observer, CameraEventListener, AppForegroundObserver.Listener { + + private static final String TAG = Log.tag(SignalCallManager.class); + + public static final int BUSY_TONE_LENGTH = 2000; + + @Nullable private final CallManager callManager; + + private final Context context; + private final SignalServiceMessageSender messageSender; + private final SignalServiceAccountManager accountManager; + private final ExecutorService serviceExecutor; + private final Executor networkExecutor; + private final LockManager lockManager; + + private WebRtcServiceState serviceState; + + public SignalCallManager(@NonNull Application application) { + this.context = application.getApplicationContext(); + this.messageSender = ApplicationDependencies.getSignalServiceMessageSender(); + this.accountManager = ApplicationDependencies.getSignalServiceAccountManager(); + this.lockManager = new LockManager(this.context); + this.serviceExecutor = Executors.newSingleThreadExecutor(); + this.networkExecutor = Executors.newSingleThreadExecutor(); + + CallManager callManager = null; + try { + callManager = CallManager.createCallManager(this); + } catch (CallException e) { + Log.w(TAG, "Unable to create CallManager", e); + } + this.callManager = callManager; + + this.serviceState = new WebRtcServiceState(new IdleActionProcessor(new WebRtcInteractor(this.context, + this, + lockManager, + new SignalAudioManager(context), + this, + this, + this))); + } + + @NonNull CallManager getRingRtcCallManager() { + //noinspection ConstantConditions + return callManager; + } + + @NonNull LockManager getLockManager() { + return lockManager; + } + + private void process(@NonNull ProcessAction action) { + if (callManager == null) { + Log.w(TAG, "Unable to process action, call manager is not initialized"); + return; + } + + serviceExecutor.execute(() -> { + Log.v(TAG, "Processing action, handler: " + serviceState.getActionProcessor().getTag()); + WebRtcServiceState previous = serviceState; + serviceState = action.process(previous, previous.getActionProcessor()); + + if (previous != serviceState) { + if (serviceState.getCallInfoState().getCallState() != WebRtcViewModel.State.IDLE) { + postStateUpdate(serviceState); + } + } + }); + } + + public void startPreJoinCall(@NonNull Recipient recipient) { + process((s, p) -> p.handlePreJoinCall(s, new RemotePeer(recipient.getId()))); + } + + public void startOutgoingAudioCall(@NonNull Recipient recipient) { + process((s, p) -> p.handleOutgoingCall(s, new RemotePeer(recipient.getId()), OfferMessage.Type.AUDIO_CALL)); + } + + public void startOutgoingVideoCall(@NonNull Recipient recipient) { + process((s, p) -> p.handleOutgoingCall(s, new RemotePeer(recipient.getId()), OfferMessage.Type.VIDEO_CALL)); + } + + public void cancelPreJoin() { + process((s, p) -> p.handleCancelPreJoinCall(s)); + } + + public void updateRenderedResolutions() { + process((s, p) -> p.handleUpdateRenderedResolutions(s)); + } + + public void orientationChanged(int degrees) { + process((s, p) -> p.handleOrientationChanged(s, degrees)); + } + + public void setAudioSpeaker(boolean isSpeaker) { + process((s, p) -> p.handleSetSpeakerAudio(s, isSpeaker)); + } + + public void setAudioBluetooth(boolean isBluetooth) { + process((s, p) -> p.handleSetBluetoothAudio(s, isBluetooth)); + } + + public void setMuteAudio(boolean enabled) { + process((s, p) -> p.handleSetMuteAudio(s, enabled)); + } + + public void setMuteVideo(boolean enabled) { + process((s, p) -> p.handleSetEnableVideo(s, enabled)); + } + + public void flipCamera() { + process((s, p) -> p.handleSetCameraFlip(s)); + } + + public void acceptCall(boolean answerWithVideo) { + process((s, p) -> p.handleAcceptCall(s, answerWithVideo)); + } + + public void denyCall() { + process((s, p) -> p.handleDenyCall(s)); + } + + public void localHangup() { + process((s, p) -> p.handleLocalHangup(s)); + } + + public void requestUpdateGroupMembers() { + process((s, p) -> p.handleGroupRequestUpdateMembers(s)); + } + + public void groupApproveSafetyChange(@NonNull List changedRecipients) { + process((s, p) -> p.handleGroupApproveSafetyNumberChange(s, changedRecipients)); + } + + public void isCallActive(@Nullable ResultReceiver resultReceiver) { + process((s, p) -> p.handleIsInCallQuery(s, resultReceiver)); + } + + public void wiredHeadsetChange(boolean available) { + process((s, p) -> p.handleWiredHeadsetChange(s, available)); + } + + public void networkChange(boolean available) { + process((s, p) -> p.handleNetworkChanged(s, available)); + } + + public void bandwidthModeUpdate() { + process((s, p) -> p.handleBandwidthModeUpdate(s)); + } + + public void screenOff() { + process((s, p) -> p.handleScreenOffChange(s)); + } + + public void bluetoothChange(boolean available) { + process((s, p) -> p.handleBluetoothChange(s, available)); + } + + public void postStateUpdate(@NonNull WebRtcServiceState state) { + EventBus.getDefault().postSticky(new WebRtcViewModel(state)); + } + + public void receivedOffer(@NonNull WebRtcData.CallMetadata callMetadata, + @NonNull WebRtcData.OfferMetadata offerMetadata, + @NonNull WebRtcData.ReceivedOfferMetadata receivedOfferMetadata) + { + process((s, p) -> p.handleReceivedOffer(s, callMetadata, offerMetadata, receivedOfferMetadata)); + } + + public void receivedAnswer(@NonNull WebRtcData.CallMetadata callMetadata, + @NonNull WebRtcData.AnswerMetadata answerMetadata, + @NonNull WebRtcData.ReceivedAnswerMetadata receivedAnswerMetadata) + { + process((s, p) -> p.handleReceivedAnswer(s, callMetadata, answerMetadata, receivedAnswerMetadata)); + } + + public void receivedIceCandidates(@NonNull WebRtcData.CallMetadata callMetadata, @NonNull List iceCandidates) { + process((s, p) -> p.handleReceivedIceCandidates(s, callMetadata, iceCandidates)); + } + + public void receivedCallHangup(@NonNull WebRtcData.CallMetadata callMetadata, @NonNull WebRtcData.HangupMetadata hangupMetadata) { + process((s, p) -> p.handleReceivedHangup(s, callMetadata, hangupMetadata)); + } + + public void receivedCallBusy(@NonNull WebRtcData.CallMetadata callMetadata) { + process((s, p) -> p.handleReceivedBusy(s, callMetadata)); + } + + public void receivedOpaqueMessage(@NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) { + process((s, p) -> p.handleReceivedOpaqueMessage(s, opaqueMessageMetadata)); + } + + public void peekGroupCall(@NonNull RecipientId id) { + if (callManager == null) { + Log.i(TAG, "Unable to peekGroupCall, call manager is null"); + return; + } + + networkExecutor.execute(() -> { + try { + Recipient group = Recipient.resolved(id); + GroupId.V2 groupId = group.requireGroupId().requireV2(); + GroupExternalCredential credential = GroupManager.getGroupExternalCredential(context, groupId); + + List members = Stream.of(GroupManager.getUuidCipherTexts(context, groupId)) + .map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize())) + .toList(); + + callManager.peekGroupCall(BuildConfig.SIGNAL_SFU_URL, credential.getTokenBytes().toByteArray(), members, peekInfo -> { + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(group); + + DatabaseFactory.getSmsDatabase(context) + .updatePreviousGroupCall(threadId, + peekInfo.getEraId(), + peekInfo.getJoinedMembers(), + WebRtcUtil.isCallFull(peekInfo)); + + ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, true, 0, BubbleUtil.BubbleState.HIDDEN); + + EventBus.getDefault().postSticky(new GroupCallPeekEvent(id, peekInfo.getEraId(), peekInfo.getDeviceCount(), peekInfo.getMaxDevices())); + }); + } catch (IOException | VerificationFailedException | CallException e) { + Log.e(TAG, "error peeking from active conversation", e); + } + }); + } + + public boolean startCallCardActivityIfPossible() { + if (Build.VERSION.SDK_INT >= 29 && !ApplicationDependencies.getAppForegroundObserver().isForegrounded()) { + return false; + } + + context.startActivity(new Intent(context, WebRtcCallActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + return true; + } + + @Override + 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); + + if (callManager == null) { + Log.w(TAG, "Unable to start call, call manager is not initialized"); + return; + } + + if (remote == null) { + return; + } + + process((s, p) -> { + RemotePeer remotePeer = (RemotePeer) remote; + if (s.getCallInfoState().getPeer(remotePeer.hashCode()) == null) { + Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping."); + try { + callManager.drop(callId); + } catch (CallException e) { + s = p.callFailure(s, "callManager.drop() failed: ", e); + } + } + + remotePeer.setCallId(callId); + + if (isOutgoing) { + return p.handleStartOutgoingCall(s, remotePeer); + } else { + return p.handleStartIncomingCall(s, remotePeer); + } + }); + } + + @Override + public void onCallEvent(@Nullable Remote remote, @NonNull CallManager.CallEvent event) { + if (callManager == null) { + Log.w(TAG, "Unable to process call event, call manager is not initialized"); + return; + } + + if (!(remote instanceof RemotePeer)) { + return; + } + + process((s, p) -> { + RemotePeer remotePeer = (RemotePeer) remote; + if (s.getCallInfoState().getPeer(remotePeer.hashCode()) == null) { + Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping."); + try { + callManager.drop(remotePeer.getCallId()); + } catch (CallException e) { + return p.callFailure(s, "callManager.drop() failed: ", e); + } + return s; + } + + Log.i(TAG, "onCallEvent(): call_id: " + remotePeer.getCallId() + ", state: " + remotePeer.getState() + ", event: " + event); + + switch (event) { + case LOCAL_RINGING: + return p.handleLocalRinging(s, remotePeer); + case REMOTE_RINGING: + return p.handleRemoteRinging(s, remotePeer); + case RECONNECTING: + Log.i(TAG, "Reconnecting: NOT IMPLEMENTED"); + break; + case RECONNECTED: + Log.i(TAG, "Reconnected: NOT IMPLEMENTED"); + break; + case LOCAL_CONNECTED: + case REMOTE_CONNECTED: + return p.handleCallConnected(s, remotePeer); + case REMOTE_VIDEO_ENABLE: + return p.handleRemoteVideoEnable(s, true); + case REMOTE_VIDEO_DISABLE: + return p.handleRemoteVideoEnable(s, false); + case ENDED_REMOTE_HANGUP: + case ENDED_REMOTE_HANGUP_NEED_PERMISSION: + case ENDED_REMOTE_HANGUP_ACCEPTED: + case ENDED_REMOTE_HANGUP_BUSY: + case ENDED_REMOTE_HANGUP_DECLINED: + case ENDED_REMOTE_BUSY: + case ENDED_REMOTE_GLARE: + return p.handleEndedRemote(s, event, remotePeer); + case ENDED_TIMEOUT: + case ENDED_INTERNAL_FAILURE: + case ENDED_SIGNALING_FAILURE: + case ENDED_CONNECTION_FAILURE: + return p.handleEnded(s, event, remotePeer); + case RECEIVED_OFFER_EXPIRED: + return p.handleReceivedOfferExpired(s, remotePeer); + case RECEIVED_OFFER_WHILE_ACTIVE: + case RECEIVED_OFFER_WITH_GLARE: + return p.handleReceivedOfferWhileActive(s, remotePeer); + case ENDED_LOCAL_HANGUP: + case ENDED_APP_DROPPED_CALL: + case IGNORE_CALLS_FROM_NON_MULTIRING_CALLERS: + Log.i(TAG, "Ignoring event: " + event); + break; + default: + throw new AssertionError("Unexpected event: " + event.toString()); + } + + return s; + }); + } + + @Override + public void onCallConcluded(@Nullable Remote remote) { + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + Log.i(TAG, "onCallConcluded: call_id: " + remotePeer.getCallId()); + process((s, p) -> p.handleCallConcluded(s, remotePeer)); + } + + @Override + public void onSendOffer(@NonNull CallId callId, + @Nullable Remote remote, + @NonNull Integer remoteDevice, + @NonNull Boolean broadcast, + @NonNull byte[] opaque, + @NonNull CallManager.CallMediaType callMediaType) + { + Log.i(TAG, "onSendOffer: id: " + callId.format(remoteDevice) + " type: " + callMediaType.name()); + + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + OfferMessage.Type offerType = WebRtcUtil.getOfferTypeFromCallMediaType(callMediaType); + + WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice); + WebRtcData.OfferMetadata offerMetadata = new WebRtcData.OfferMetadata(opaque, null, offerType); + + process((s, p) -> p.handleSendOffer(s, callMetadata, offerMetadata, broadcast)); + } + + @Override + public void onSendAnswer(@NonNull CallId callId, + @Nullable Remote remote, + @NonNull Integer remoteDevice, + @NonNull Boolean broadcast, + @NonNull byte[] opaque) + { + Log.i(TAG, "onSendAnswer: id: " + callId.format(remoteDevice)); + + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice); + WebRtcData.AnswerMetadata answerMetadata = new WebRtcData.AnswerMetadata(opaque, null); + + process((s, p) -> p.handleSendAnswer(s, callMetadata, answerMetadata, broadcast)); + } + + @Override + public void onSendIceCandidates(@NonNull CallId callId, + @Nullable Remote remote, + @NonNull Integer remoteDevice, + @NonNull Boolean broadcast, + @NonNull List iceCandidates) + { + Log.i(TAG, "onSendIceCandidates: id: " + callId.format(remoteDevice)); + + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice); + + process((s, p) -> p.handleSendIceCandidates(s, callMetadata, broadcast, iceCandidates)); + } + + @Override + public void onSendHangup(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull CallManager.HangupType hangupType, @NonNull Integer deviceId, @NonNull Boolean useLegacyHangupMessage) { + Log.i(TAG, "onSendHangup: id: " + callId.format(remoteDevice) + " type: " + hangupType.name() + " isLegacy: " + useLegacyHangupMessage); + + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice); + WebRtcData.HangupMetadata hangupMetadata = new WebRtcData.HangupMetadata(WebRtcUtil.getHangupTypeFromCallHangupType(hangupType), useLegacyHangupMessage, deviceId); + + process((s, p) -> p.handleSendHangup(s, callMetadata, hangupMetadata, broadcast)); + } + + @Override + public void onSendBusy(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast) { + Log.i(TAG, "onSendBusy: id: " + callId.format(remoteDevice)); + + if (!(remote instanceof RemotePeer)) { + return; + } + + RemotePeer remotePeer = (RemotePeer) remote; + WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice); + + process((s, p) -> p.handleSendBusy(s, callMetadata, broadcast)); + } + + @Override + public void onSendCallMessage(@NonNull final UUID uuid, @NonNull final byte[] bytes) { + Log.i(TAG, "onSendCallMessage():"); + + OpaqueMessage opaqueMessage = new OpaqueMessage(bytes); + SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOpaque(opaqueMessage, true, null); + + networkExecutor.execute(() -> { + Recipient recipient = Recipient.resolved(RecipientId.from(uuid, null)); + if (recipient.isBlocked()) { + return; + } + try { + messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient), + UnidentifiedAccessUtil.getAccessFor(context, recipient), + callMessage); + } catch (UntrustedIdentityException e) { + Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e); + process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), UNTRUSTED_IDENTITY, Optional.fromNullable(e.getIdentityKey()))); + } catch (IOException e) { + Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e); + process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), NETWORK_FAILURE, Optional.absent())); + } + }); + } + + @Override + public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull CallManager.HttpMethod httpMethod, @Nullable List headers, @Nullable byte[] body) { + if (callManager == null) { + Log.w(TAG, "Unable to send http request, call manager is not initialized"); + return; + } + + Log.i(TAG, "onSendHttpRequest(): request_id: " + requestId); + networkExecutor.execute(() -> { + List> 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); + + try { + if (response instanceof CallingResponse.Success) { + CallingResponse.Success success = (CallingResponse.Success) response; + callManager.receivedHttpResponse(requestId, success.getResponseStatus(), success.getResponseBody()); + } else { + callManager.httpRequestFailed(requestId); + } + } catch (CallException e) { + Log.i(TAG, "Failed to process HTTP response/failure", e); + } + }); + } + + @Override + public void requestMembershipProof(@NonNull final GroupCall groupCall) { + Log.i(TAG, "requestMembershipProof():"); + + Recipient recipient = serviceState.getCallInfoState().getCallRecipient(); + if (!recipient.isPushV2Group()) { + Log.i(TAG, "Request membership proof for non-group"); + return; + } + + GroupCall currentGroupCall = serviceState.getCallInfoState().getGroupCall(); + if (currentGroupCall == null || currentGroupCall.hashCode() != groupCall.hashCode()) { + Log.i(TAG, "Skipping group membership proof request, requested group call does not match current group call"); + return; + } + + networkExecutor.execute(() -> { + try { + GroupExternalCredential credential = GroupManager.getGroupExternalCredential(context, recipient.getGroupId().get().requireV2()); + process((s, p) -> p.handleGroupRequestMembershipProof(s, groupCall.hashCode(), credential.getTokenBytes().toByteArray())); + } catch (IOException e) { + Log.w(TAG, "Unable to get group membership proof from service", e); + onEnded(groupCall, GroupCall.GroupCallEndReason.SFU_CLIENT_FAILED_TO_JOIN); + } catch (VerificationFailedException e) { + Log.w(TAG, "Unable to verify group membership proof", e); + onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED); + } + }); + } + + @Override + public void requestGroupMembers(@NonNull GroupCall groupCall) { + process((s, p) -> p.handleGroupRequestUpdateMembers(s)); + } + + @Override + public void onLocalDeviceStateChanged(@NonNull GroupCall groupCall) { + process((s, p) -> p.handleGroupLocalDeviceStateChanged(s)); + } + + @Override + public void onRemoteDeviceStatesChanged(@NonNull GroupCall groupCall) { + process((s, p) -> p.handleGroupRemoteDeviceStateChanged(s)); + } + + @Override + public void onPeekChanged(@NonNull GroupCall groupCall) { + process((s, p) -> p.handleGroupJoinedMembershipChanged(s)); + } + + @Override + public void onEnded(@NonNull GroupCall groupCall, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) { + process((s, p) -> p.handleGroupCallEnded(s, groupCall.hashCode(), groupCallEndReason)); + } + + @Override + public void onCameraSwitchCompleted(@NonNull final CameraState newCameraState) { + process((s, p) -> p.handleCameraSwitchCompleted(s, newCameraState)); + } + + @Override + public void onForeground() { + process((s, p) -> { + WebRtcViewModel.State callState = s.getCallInfoState().getCallState(); + if (callState == CALL_INCOMING && s.getCallInfoState().getGroupCallState() == IDLE) { + startCallCardActivityIfPossible(); + } + ApplicationDependencies.getAppForegroundObserver().removeListener(this); + return s; + }); + } + + public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) { + Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(context) + .insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer); + + ApplicationDependencies.getMessageNotifier() + .updateNotification(context, messageAndThreadId.second(), signal); + } + + public void retrieveTurnServers(@NonNull RemotePeer remotePeer) { + networkExecutor.execute(() -> { + try { + TurnServerInfo turnServerInfo = accountManager.getTurnServerInfo(); + + List iceServers = new LinkedList<>(); + iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer()); + for (String url : turnServerInfo.getUrls()) { + Log.i(TAG, "ice_server: " + url); + if (url.startsWith("turn")) { + iceServers.add(PeerConnection.IceServer.builder(url) + .setUsername(turnServerInfo.getUsername()) + .setPassword(turnServerInfo.getPassword()) + .createIceServer()); + } else { + iceServers.add(PeerConnection.IceServer.builder(url).createIceServer()); + } + } + + process((s, p) -> p.handleTurnServerUpdate(s, iceServers, TextSecurePreferences.isTurnOnly(context))); + } catch (IOException e) { + Log.w(TAG, "Unable to retrieve turn servers: ", e); + process((s, p) -> p.handleSetupFailure(s, remotePeer.getCallId())); + } + }); + } + + public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) { + SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId))); + } + + public void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection joinedMembers, boolean isCallFull) { + SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getSmsDatabase(context).insertOrUpdateGroupCall(groupId, + Recipient.self().getId(), + System.currentTimeMillis(), + groupCallEraId, + joinedMembers, + isCallFull)); + } + + public void sendCallMessage(@NonNull final RemotePeer remotePeer, + @NonNull final SignalServiceCallMessage callMessage) + { + networkExecutor.execute(() -> { + Recipient recipient = Recipient.resolved(remotePeer.getId()); + if (recipient.isBlocked()) { + return; + } + + try { + messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient), + UnidentifiedAccessUtil.getAccessFor(context, recipient), + callMessage); + process((s, p) -> p.handleMessageSentSuccess(s, remotePeer.getCallId())); + } catch (UntrustedIdentityException e) { + processSendMessageFailureWithChangeDetection(remotePeer, + (s, p) -> p.handleMessageSentError(s, + remotePeer.getCallId(), + UNTRUSTED_IDENTITY, + Optional.fromNullable(e.getIdentityKey()))); + } catch (IOException e) { + processSendMessageFailureWithChangeDetection(remotePeer, + (s, p) -> p.handleMessageSentError(s, + remotePeer.getCallId(), + e instanceof UnregisteredUserException ? NO_SUCH_USER : NETWORK_FAILURE, + Optional.absent())); + } + }); + } + + private void processSendMessageFailureWithChangeDetection(@NonNull RemotePeer remotePeer, + @NonNull ProcessAction failureProcessAction) + { + process((s, p) -> { + RemotePeer activePeer = s.getCallInfoState().getActivePeer(); + + boolean stateChanged = activePeer == null || + remotePeer.getState() != activePeer.getState() || + !remotePeer.getCallId().equals(activePeer.getCallId()); + + if (stateChanged) { + return p.handleMessageSentSuccess(s, remotePeer.getCallId()); + } else { + return failureProcessAction.process(s, p); + } + }); + } + + interface ProcessAction { + @NonNull WebRtcServiceState process(@NonNull WebRtcServiceState currentState, @NonNull WebRtcActionProcessor processor); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index 9456a42863..20ab3b8f12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.service.webrtc; import android.content.Context; -import android.content.Intent; import android.os.ResultReceiver; import androidx.annotation.NonNull; @@ -10,6 +9,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; +import org.signal.ringrtc.CallManager; import org.signal.ringrtc.GroupCall; import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; @@ -21,10 +21,8 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.ringrtc.CallState; import org.thoughtcrime.securesms.ringrtc.Camera; import org.thoughtcrime.securesms.ringrtc.CameraState; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; 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.ReceivedOfferMetadata; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; @@ -41,116 +39,23 @@ 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 java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.Objects; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ACCEPT_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_BANDWIDTH_MODE_UPDATE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_BLUETOOTH_CHANGE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CALL_CONCLUDED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CALL_CONNECTED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CAMERA_SWITCH_COMPLETED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_DENY_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_CONNECTION_FAILURE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_INTERNAL_FAILURE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_BUSY; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_GLARE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_ACCEPTED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_BUSY; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_DECLINED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION; -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_FLIP_CAMERA; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_ENDED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_PEEK; -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_MESSAGE_SENT_ERROR; -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_LOCAL_HANGUP; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_ERROR; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_SUCCESS; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_NETWORK_CHANGE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ORIENTATION_CHANGED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_OUTGOING_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_PRE_JOIN_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_EXPIRED; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_WHILE_ACTIVE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_ANSWER; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_BUSY; -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_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_VIDEO_ENABLE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SCREEN_OFF; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_ANSWER; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_BUSY; -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_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_SET_AUDIO_BLUETOOTH; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_SPEAKER; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_ENABLE_VIDEO; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_MUTE_AUDIO; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_START_INCOMING_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_START_OUTGOING_CALL; -import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_TURN_SERVER_UPDATE; -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_BLUETOOTH; -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_RECIPIENT_IDS; -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.webrtc.WebRtcData.AnswerMetadata; 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.WebRtcIntentParser.getAvailable; -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.getCameraState; -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.getErrorIdentityKey; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupCallEndReason; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupCallHash; -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.getIceServers; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getNullableRemotePeerFromMap; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOrientationDegrees; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeer; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeerFromMap; /** * Base WebRTC action processor and core of the calling state machine. As actions (as intents) * are sent to the service, they are passed to an instance of the current state's action processor. * Based on the state of the system, the action processor will either handle the event or do nothing. - * + *

* For example, the {@link OutgoingCallActionProcessor} responds to the the * {@link #handleReceivedBusy(WebRtcServiceState, CallMetadata)} event but no others do. - * - * Processing of the actions occur in {@link #processAction(String, Intent, WebRtcServiceState)} and + *

+ * Processing of the actions occur in by calls from {@link SignalCallManager} and * result in atomic state updates that are returned to the caller. Part of the state change can be * the replacement of the current action processor. */ @@ -161,7 +66,7 @@ public abstract class WebRtcActionProcessor { protected final String tag; public WebRtcActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) { - this.context = webRtcInteractor.getWebRtcCallService(); + this.context = webRtcInteractor.getContext(); this.webRtcInteractor = webRtcInteractor; this.tag = tag; } @@ -170,102 +75,6 @@ public abstract class WebRtcActionProcessor { return tag; } - public @NonNull WebRtcServiceState processAction(@NonNull String action, @NonNull Intent intent, @NonNull WebRtcServiceState currentState) { - switch (action) { - case ACTION_IS_IN_CALL_QUERY: return handleIsInCallQuery(currentState, intent.getParcelableExtra(EXTRA_RESULT_RECEIVER)); - - // Pre-Join Actions - case ACTION_PRE_JOIN_CALL: return handlePreJoinCall(currentState, getRemotePeer(intent)); - case ACTION_CANCEL_PRE_JOIN_CALL: return handleCancelPreJoinCall(currentState); - - // Outgoing Call Actions - case ACTION_OUTGOING_CALL: return handleOutgoingCall(currentState, getRemotePeer(intent), getOfferMessageType(intent)); - case ACTION_START_OUTGOING_CALL: return handleStartOutgoingCall(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_SEND_OFFER: return handleSendOffer(currentState, CallMetadata.fromIntent(intent), OfferMetadata.fromIntent(intent), getBroadcastFlag(intent)); - case ACTION_REMOTE_RINGING: return handleRemoteRinging(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_RECEIVE_ANSWER: return handleReceivedAnswer(currentState, CallMetadata.fromIntent(intent), AnswerMetadata.fromIntent(intent), ReceivedAnswerMetadata.fromIntent(intent)); - case ACTION_RECEIVE_BUSY: return handleReceivedBusy(currentState, CallMetadata.fromIntent(intent)); - - // Incoming Call Actions - case ACTION_RECEIVE_OFFER: return handleReceivedOffer(currentState, CallMetadata.fromIntent(intent), OfferMetadata.fromIntent(intent), ReceivedOfferMetadata.fromIntent(intent)); - case ACTION_RECEIVED_OFFER_EXPIRED: return handleReceivedOfferExpired(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_START_INCOMING_CALL: return handleStartIncomingCall(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_ACCEPT_CALL: return handleAcceptCall(currentState, intent.getBooleanExtra(EXTRA_ANSWER_WITH_VIDEO, false)); - case ACTION_LOCAL_RINGING: return handleLocalRinging(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_DENY_CALL: return handleDenyCall(currentState); - case ACTION_SEND_ANSWER: return handleSendAnswer(currentState, CallMetadata.fromIntent(intent), AnswerMetadata.fromIntent(intent), getBroadcastFlag(intent)); - - // Active Call Actions - case ACTION_CALL_CONNECTED: return handleCallConnected(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_RECEIVED_OFFER_WHILE_ACTIVE: return handleReceivedOfferWhileActive(currentState, getRemotePeerFromMap(intent, currentState)); - case ACTION_SEND_BUSY: return handleSendBusy(currentState, CallMetadata.fromIntent(intent), getBroadcastFlag(intent)); - case ACTION_CALL_CONCLUDED: return handleCallConcluded(currentState, getNullableRemotePeerFromMap(intent, currentState)); - case ACTION_REMOTE_VIDEO_ENABLE: return handleRemoteVideoEnable(currentState, getEnable(intent)); - case ACTION_RECEIVE_HANGUP: return handleReceivedHangup(currentState, CallMetadata.fromIntent(intent), HangupMetadata.fromIntent(intent)); - case ACTION_LOCAL_HANGUP: return handleLocalHangup(currentState); - case ACTION_SEND_HANGUP: return handleSendHangup(currentState, CallMetadata.fromIntent(intent), HangupMetadata.fromIntent(intent), getBroadcastFlag(intent)); - case ACTION_MESSAGE_SENT_SUCCESS: return handleMessageSentSuccess(currentState, getCallId(intent)); - case ACTION_MESSAGE_SENT_ERROR: return handleMessageSentError(currentState, getCallId(intent), getErrorCallState(intent), getErrorIdentityKey(intent)); - - // Call Setup Actions - case ACTION_RECEIVE_ICE_CANDIDATES: return handleReceivedIceCandidates(currentState, CallMetadata.fromIntent(intent), getIceCandidates(intent)); - case ACTION_SEND_ICE_CANDIDATES: return handleSendIceCandidates(currentState, CallMetadata.fromIntent(intent), getBroadcastFlag(intent), getIceCandidates(intent)); - case ACTION_TURN_SERVER_UPDATE: return handleTurnServerUpdate(currentState, getIceServers(intent), intent.getBooleanExtra(EXTRA_IS_ALWAYS_TURN, false)); - - // Local Device Actions - case ACTION_SET_ENABLE_VIDEO: return handleSetEnableVideo(currentState, getEnable(intent)); - case ACTION_SET_MUTE_AUDIO: return handleSetMuteAudio(currentState, intent.getBooleanExtra(EXTRA_MUTE, false)); - case ACTION_FLIP_CAMERA: return handleSetCameraFlip(currentState); - case ACTION_SCREEN_OFF: return handleScreenOffChange(currentState); - case ACTION_WIRED_HEADSET_CHANGE: return handleWiredHeadsetChange(currentState, getAvailable(intent)); - 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_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent)); - case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent)); - case ACTION_NETWORK_CHANGE: return handleNetworkChanged(currentState, getAvailable(intent)); - case ACTION_BANDWIDTH_MODE_UPDATE: return handleBandwidthModeUpdate(currentState); - case ACTION_ORIENTATION_CHANGED: return handleOrientationChanged(currentState, getOrientationDegrees(intent)); - - // End Call Actions - case ACTION_ENDED_REMOTE_HANGUP: - case ACTION_ENDED_REMOTE_HANGUP_ACCEPTED: - case ACTION_ENDED_REMOTE_HANGUP_BUSY: - case ACTION_ENDED_REMOTE_HANGUP_DECLINED: - case ACTION_ENDED_REMOTE_BUSY: - case ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION: - case ACTION_ENDED_REMOTE_GLARE: return handleEndedRemote(currentState, action, getRemotePeerFromMap(intent, currentState)); - - // End Call Failure Actions - case ACTION_ENDED_TIMEOUT: - case ACTION_ENDED_INTERNAL_FAILURE: - case ACTION_ENDED_SIGNALING_FAILURE: - case ACTION_ENDED_CONNECTION_FAILURE: return handleEnded(currentState, action, getRemotePeerFromMap(intent, currentState)); - - // Local Call Failure Actions - 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, getGroupCallHash(intent), getGroupMembershipToken(intent)); - case ACTION_GROUP_REQUEST_UPDATE_MEMBERS: return handleGroupRequestUpdateMembers(currentState); - case ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS: return handleUpdateRenderedResolutions(currentState); - case ACTION_GROUP_CALL_ENDED: return handleGroupCallEnded(currentState, getGroupCallHash(intent), getGroupCallEndReason(intent)); - case ACTION_GROUP_CALL_PEEK: return handleGroupCallPeek(currentState, getRemotePeer(intent)); - case ACTION_GROUP_MESSAGE_SENT_ERROR: return handleGroupMessageSentError(currentState, getRemotePeer(intent), getErrorCallState(intent), getErrorIdentityKey(intent)); - case ACTION_GROUP_APPROVE_SAFETY_CHANGE: return handleGroupApproveSafetyNumberChange(currentState, RecipientId.fromSerializedList(intent.getStringExtra(EXTRA_RECIPIENT_IDS))); - - 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; - } - protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) { if (resultReceiver != null) { resultReceiver.send(0, null); @@ -337,21 +146,21 @@ public abstract class WebRtcActionProcessor { if (TelephonyUtil.isAnyPstnLineBusy(context)) { Log.i(tag, "PSTN line is busy."); currentState = currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } if (!RecipientUtil.isCallRequestAccepted(context.getApplicationContext(), callMetadata.getRemotePeer().getRecipient())) { Log.w(tag, "Caller is untrusted."); currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NEED_PERMISSION), true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } if (offerMetadata.getOpaque() == null) { Log.w(tag, "Opaque data is required."); currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NORMAL), true); - webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); + webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL); return currentState; } @@ -397,7 +206,7 @@ public abstract class WebRtcActionProcessor { { Log.i(tag, "handleReceivedOfferExpired(): call_id: " + remotePeer.getCallId()); - webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); + webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); return terminate(currentState, remotePeer); } @@ -475,7 +284,7 @@ public abstract class WebRtcActionProcessor { try { webRtcInteractor.getCallManager().receivedHangup(callMetadata.getCallId(), callMetadata.getRemoteDevice(), hangupMetadata.getCallHangupType(), hangupMetadata.getDeviceId()); - } catch (CallException e) { + } catch (CallException e) { return callFailure(currentState, "receivedHangup() failed: ", e); } @@ -515,7 +324,8 @@ public abstract class WebRtcActionProcessor { protected @NonNull WebRtcServiceState handleMessageSentError(@NonNull WebRtcServiceState currentState, @NonNull CallId callId, @NonNull WebRtcViewModel.State errorCallState, - @NonNull Optional identityKey) { + @NonNull Optional identityKey) + { Log.w(tag, "handleMessageSentError():"); try { @@ -552,18 +362,13 @@ public abstract class WebRtcActionProcessor { //region Call setup - protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast, @NonNull ArrayList iceCandidates) { + protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast, @NonNull List iceCandidates) { Log.i(tag, "handleSendIceCandidates not processed"); return currentState; } - protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull ArrayList iceCandidateParcels) { - Log.i(tag, "handleReceivedIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + ", count: " + iceCandidateParcels.size()); - - LinkedList iceCandidates = new LinkedList<>(); - for (IceCandidateParcel parcel : iceCandidateParcels) { - iceCandidates.add(parcel.getIceCandidate()); - } + protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull List iceCandidates) { + Log.i(tag, "handleReceivedIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + ", count: " + iceCandidates.size()); try { webRtcInteractor.getCallManager().receivedIceCandidates(callMetadata.getCallId(), callMetadata.getRemoteDevice(), iceCandidates); @@ -659,7 +464,7 @@ public abstract class WebRtcActionProcessor { //region End call - protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { + protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) { Log.i(tag, "handleEndedRemote not processed"); return currentState; } @@ -668,7 +473,7 @@ public abstract class WebRtcActionProcessor { //region End call failure - protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) { + protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) { Log.i(tag, "handleEnded not processed"); return currentState; } @@ -677,7 +482,7 @@ public abstract class WebRtcActionProcessor { //region Local call failure - protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) { + protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) { Log.i(tag, "handleSetupFailure not processed"); return currentState; } @@ -786,11 +591,6 @@ public abstract class WebRtcActionProcessor { return currentState; } - protected @NonNull WebRtcServiceState handleGroupCallPeek(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) { - webRtcInteractor.peekGroupCall(remotePeer.getId()); - return currentState; - } - protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer, @NonNull WebRtcViewModel.State errorCallState, @@ -809,30 +609,6 @@ public abstract class WebRtcActionProcessor { //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"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java new file mode 100644 index 0000000000..c967214765 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java @@ -0,0 +1,304 @@ +package org.thoughtcrime.securesms.service.webrtc; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.IBinder; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.TelephonyUtil; +import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; +import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager; +import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager; +import org.thoughtcrime.securesms.webrtc.locks.LockManager; + +import java.util.Objects; + +/** + * Provide a foreground service for {@link SignalCallManager} to leverage to run in the background when necessary. Also + * provides devices listeners needed for during a call (i.e., bluetooth, power button). + */ +public final class WebRtcCallService extends Service implements BluetoothStateManager.BluetoothStateListener { + + private static final String TAG = Log.tag(WebRtcCallService.class); + + private static final String ACTION_UPDATE = "UPDATE"; + private static final String ACTION_STOP = "STOP"; + private static final String ACTION_DENY_CALL = "DENY_CALL"; + private static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP"; + private static final String ACTION_WANTS_BLUETOOTH = "WANTS_BLUETOOTH"; + private static final String ACTION_CHANGE_POWER_BUTTON = "CHANGE_POWER_BUTTON"; + + private static final String EXTRA_UPDATE_TYPE = "UPDATE_TYPE"; + private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID"; + private static final String EXTRA_ENABLED = "ENABLED"; + + private SignalCallManager callManager; + + private WiredHeadsetStateReceiver wiredHeadsetStateReceiver; + private NetworkReceiver networkReceiver; + private PowerButtonReceiver powerButtonReceiver; + private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; + private PhoneStateListener hangUpRtcOnDeviceCallAnswered; + private BluetoothStateManager bluetoothStateManager; + + public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId) { + Intent intent = new Intent(context, WebRtcCallService.class); + intent.setAction(ACTION_UPDATE) + .putExtra(EXTRA_UPDATE_TYPE, type) + .putExtra(EXTRA_RECIPIENT_ID, recipientId); + + ContextCompat.startForegroundService(context, intent); + } + + public static void stop(@NonNull Context context) { + Intent intent = new Intent(context, WebRtcCallService.class); + intent.setAction(ACTION_STOP); + + context.startService(intent); + } + + public static @NonNull Intent denyCallIntent(@NonNull Context context) { + return new Intent(context, WebRtcCallService.class).setAction(ACTION_DENY_CALL); + } + + public static @NonNull Intent hangupIntent(@NonNull Context context) { + return new Intent(context, WebRtcCallService.class).setAction(ACTION_LOCAL_HANGUP); + } + + public static void setWantsBluetoothConnection(@NonNull Context context, boolean enabled) { + Intent intent = new Intent(context, WebRtcCallService.class); + intent.setAction(ACTION_WANTS_BLUETOOTH) + .putExtra(EXTRA_ENABLED, enabled); + + context.startService(intent); + } + + public static void changePowerButtonReceiver(@NonNull Context context, boolean register) { + Intent intent = new Intent(context, WebRtcCallService.class); + intent.setAction(ACTION_CHANGE_POWER_BUTTON) + .putExtra(EXTRA_ENABLED, register); + + context.startService(intent); + } + + @Override + public void onCreate() { + Log.v(TAG, "onCreate"); + super.onCreate(); + this.callManager = ApplicationDependencies.getSignalCallManager(); + this.bluetoothStateManager = new BluetoothStateManager(this, this); + this.hangUpRtcOnDeviceCallAnswered = new HangUpRtcOnPstnCallAnsweredListener(); + + registerUncaughtExceptionHandler(); + registerWiredHeadsetStateReceiver(); + registerNetworkReceiver(); + + TelephonyUtil.getManager(this) + .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE); + } + + @Override + public void onDestroy() { + Log.v(TAG, "onDestroy"); + super.onDestroy(); + + if (uncaughtExceptionHandlerManager != null) { + uncaughtExceptionHandlerManager.unregister(); + } + + if (bluetoothStateManager != null) { + bluetoothStateManager.onDestroy(); + } + + if (wiredHeadsetStateReceiver != null) { + unregisterReceiver(wiredHeadsetStateReceiver); + wiredHeadsetStateReceiver = null; + } + + unregisterNetworkReceiver(); + unregisterNetworkReceiver(); + + TelephonyUtil.getManager(this) + .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent == null || intent.getAction() == null) { + return START_NOT_STICKY; + } + + Log.i(TAG, "action: " + intent.getAction()); + + switch (intent.getAction()) { + case ACTION_UPDATE: + setCallInProgressNotification(intent.getIntExtra(EXTRA_UPDATE_TYPE, 0), + Objects.requireNonNull(intent.getParcelableExtra(EXTRA_RECIPIENT_ID))); + return START_STICKY; + case ACTION_WANTS_BLUETOOTH: + if (bluetoothStateManager != null) { + bluetoothStateManager.setWantsConnection(intent.getBooleanExtra(EXTRA_ENABLED, false)); + } + return START_STICKY; + case ACTION_CHANGE_POWER_BUTTON: + if (intent.getBooleanExtra(EXTRA_ENABLED, false)) { + registerPowerButtonReceiver(); + } else { + unregisterPowerButtonReceiver(); + } + return START_STICKY; + case ACTION_STOP: + stopSelf(); + stopForeground(true); + return START_NOT_STICKY; + case ACTION_DENY_CALL: + callManager.denyCall(); + return START_NOT_STICKY; + case ACTION_LOCAL_HANGUP: + callManager.localHangup(); + return START_NOT_STICKY; + default: + throw new AssertionError("Unknown action: " + intent.getAction()); + } + } + + public void setCallInProgressNotification(int type, @NonNull RecipientId id) { + startForeground(CallNotificationBuilder.getNotificationId(type), + CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id))); + } + + private void registerUncaughtExceptionHandler() { + uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager(); + uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(callManager.getLockManager())); + } + + private void registerWiredHeadsetStateReceiver() { + wiredHeadsetStateReceiver = new WiredHeadsetStateReceiver(); + + String action; + + if (Build.VERSION.SDK_INT >= 21) { + action = AudioManager.ACTION_HEADSET_PLUG; + } else { + action = Intent.ACTION_HEADSET_PLUG; + } + + registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action)); + } + + private void registerNetworkReceiver() { + if (networkReceiver == null) { + networkReceiver = new NetworkReceiver(); + + registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + } + + private void unregisterNetworkReceiver() { + if (networkReceiver != null) { + unregisterReceiver(networkReceiver); + + networkReceiver = null; + } + } + + public void registerPowerButtonReceiver() { + if (powerButtonReceiver == null) { + powerButtonReceiver = new PowerButtonReceiver(); + + registerReceiver(powerButtonReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + } + } + + public void unregisterPowerButtonReceiver() { + if (powerButtonReceiver != null) { + unregisterReceiver(powerButtonReceiver); + + powerButtonReceiver = null; + } + } + + @Override + public @Nullable IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onBluetoothStateChanged(boolean isAvailable) { + callManager.bluetoothChange(isAvailable); + } + + private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener { + @Override + public void onCallStateChanged(int state, @NonNull String phoneNumber) { + super.onCallStateChanged(state, phoneNumber); + if (state == TelephonyManager.CALL_STATE_OFFHOOK) { + hangup(); + Log.i(TAG, "Device phone call ended Signal call."); + } + } + + private void hangup() { + callManager.localHangup(); + } + } + + private static class WiredHeadsetStateReceiver extends BroadcastReceiver { + @Override + public void onReceive(@NonNull Context context, @NonNull Intent intent) { + int state = intent.getIntExtra("state", -1); + + ApplicationDependencies.getSignalCallManager().wiredHeadsetChange(state != 0); + } + } + + private static class NetworkReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + + ApplicationDependencies.getSignalCallManager().networkChange(activeNetworkInfo != null && activeNetworkInfo.isConnected()); + ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate(); + } + } + + private static class PowerButtonReceiver extends BroadcastReceiver { + @Override + public void onReceive(@NonNull Context context, @NonNull Intent intent) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + ApplicationDependencies.getSignalCallManager().screenOff(); + } + } + } + + private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler { + private final LockManager lockManager; + + private ProximityLockRelease(@NonNull LockManager lockManager) { + this.lockManager = lockManager; + } + + @Override + public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { + Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable); + lockManager.updatePhoneState(LockManager.PhoneState.IDLE); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcData.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcData.java index 2c04559ce2..e67a2383d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcData.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcData.java @@ -1,53 +1,30 @@ package org.thoughtcrime.securesms.service.webrtc; -import android.content.Intent; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallManager; -import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import java.util.UUID; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_ERA_ID; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_GROUP; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_SENDER; -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_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_RECEIVED_TIMESTAMP; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRecipientId; -import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemoteDevice; - /** - * Collection of classes to ease parsing data from intents and passing said data - * around. + * Collection of classes to ease passing calling data around. */ public class WebRtcData { /** * Low-level metadata Information about the call. */ - static class CallMetadata { + public static class CallMetadata { private final @NonNull RemotePeer remotePeer; private final @NonNull CallId callId; private final int remoteDevice; - public static @NonNull CallMetadata fromIntent(@NonNull Intent intent) { - return new CallMetadata(WebRtcIntentParser.getRemotePeer(intent), WebRtcIntentParser.getCallId(intent), WebRtcIntentParser.getRemoteDevice(intent)); - } - - private CallMetadata(@NonNull RemotePeer remotePeer, @NonNull CallId callId, int remoteDevice) { + public CallMetadata(@NonNull RemotePeer remotePeer, @NonNull CallId callId, int remoteDevice) { this.remotePeer = remotePeer; this.callId = callId; this.remoteDevice = remoteDevice; @@ -69,18 +46,12 @@ public class WebRtcData { /** * Metadata for a call offer to be sent or received. */ - static class OfferMetadata { + public static class OfferMetadata { private final @Nullable byte[] opaque; private final @Nullable String sdp; private final @NonNull OfferMessage.Type offerType; - static @NonNull OfferMetadata fromIntent(@NonNull Intent intent) { - return new OfferMetadata(WebRtcIntentParser.getOfferOpaque(intent), - WebRtcIntentParser.getOfferSdp(intent), - WebRtcIntentParser.getOfferMessageType(intent)); - } - - private OfferMetadata(@Nullable byte[] opaque, @Nullable String sdp, @NonNull OfferMessage.Type offerType) { + public OfferMetadata(@Nullable byte[] opaque, @Nullable String sdp, @NonNull OfferMessage.Type offerType) { this.opaque = opaque; this.sdp = sdp; this.offerType = offerType; @@ -102,20 +73,13 @@ public class WebRtcData { /** * Additional metadata for a received call. */ - static class ReceivedOfferMetadata { + public static class ReceivedOfferMetadata { private final @NonNull byte[] remoteIdentityKey; private final long serverReceivedTimestamp; private final long serverDeliveredTimestamp; private final boolean isMultiRing; - static @NonNull ReceivedOfferMetadata fromIntent(@NonNull Intent intent) { - return new ReceivedOfferMetadata(WebRtcIntentParser.getRemoteIdentityKey(intent), - intent.getLongExtra(EXTRA_SERVER_RECEIVED_TIMESTAMP, -1), - intent.getLongExtra(EXTRA_SERVER_DELIVERED_TIMESTAMP, -1), - WebRtcIntentParser.getMultiRingFlag(intent)); - } - - ReceivedOfferMetadata(@NonNull byte[] remoteIdentityKey, long serverReceivedTimestamp, long serverDeliveredTimestamp, boolean isMultiRing) { + public ReceivedOfferMetadata(@NonNull byte[] remoteIdentityKey, long serverReceivedTimestamp, long serverDeliveredTimestamp, boolean isMultiRing) { this.remoteIdentityKey = remoteIdentityKey; this.serverReceivedTimestamp = serverReceivedTimestamp; this.serverDeliveredTimestamp = serverDeliveredTimestamp; @@ -142,17 +106,13 @@ public class WebRtcData { /** * Metadata for an answer to be sent or received. */ - static class AnswerMetadata { - private final @Nullable byte[] opaque; - private final @Nullable String sdp; + public static class AnswerMetadata { + private final @Nullable byte[] opaque; + private final @Nullable String sdp; - static @NonNull AnswerMetadata fromIntent(@NonNull Intent intent) { - return new AnswerMetadata(WebRtcIntentParser.getAnswerOpaque(intent), WebRtcIntentParser.getAnswerSdp(intent)); - } - - private AnswerMetadata(@Nullable byte[] opaque, @Nullable String sdp) { - this.opaque = opaque; - this.sdp = sdp; + public AnswerMetadata(@Nullable byte[] opaque, @Nullable String sdp) { + this.opaque = opaque; + this.sdp = sdp; } @Nullable byte[] getOpaque() { @@ -167,17 +127,13 @@ public class WebRtcData { /** * Additional metadata for a received answer. */ - static class ReceivedAnswerMetadata { + public static class ReceivedAnswerMetadata { private final @NonNull byte[] remoteIdentityKey; private final boolean isMultiRing; - static @NonNull ReceivedAnswerMetadata fromIntent(@NonNull Intent intent) { - return new ReceivedAnswerMetadata(WebRtcIntentParser.getRemoteIdentityKey(intent), WebRtcIntentParser.getMultiRingFlag(intent)); - } - - ReceivedAnswerMetadata(@NonNull byte[] remoteIdentityKey, boolean isMultiRing) { - this.remoteIdentityKey = remoteIdentityKey; - this.isMultiRing = isMultiRing; + public ReceivedAnswerMetadata(@NonNull byte[] remoteIdentityKey, boolean isMultiRing) { + this.remoteIdentityKey = remoteIdentityKey; + this.isMultiRing = isMultiRing; } @NonNull byte[] getRemoteIdentityKey() { @@ -192,22 +148,16 @@ public class WebRtcData { /** * Metadata for a remote or local hangup. */ - static class HangupMetadata { + public static class HangupMetadata { private final @NonNull HangupMessage.Type type; private final boolean isLegacy; private final int deviceId; - static @NonNull HangupMetadata fromIntent(@NonNull Intent intent) { - return new HangupMetadata(HangupMessage.Type.fromCode(intent.getStringExtra(EXTRA_HANGUP_TYPE)), - intent.getBooleanExtra(EXTRA_HANGUP_IS_LEGACY, true), - intent.getIntExtra(EXTRA_HANGUP_DEVICE_ID, 0)); - } - static @NonNull HangupMetadata fromType(@NonNull HangupMessage.Type type) { return new HangupMetadata(type, true, 0); } - HangupMetadata(@NonNull HangupMessage.Type type, boolean isLegacy, int deviceId) { + public HangupMetadata(@NonNull HangupMessage.Type type, boolean isLegacy, int deviceId) { this.type = type; this.isLegacy = isLegacy; this.deviceId = deviceId; @@ -237,56 +187,16 @@ public class WebRtcData { } } - /** - * 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 { + public 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) { + public OpaqueMessageMetadata(@NonNull UUID uuid, @NonNull byte[] opaque, int remoteDeviceId, long messageAgeSeconds) { this.uuid = uuid; this.opaque = opaque; this.remoteDeviceId = remoteDeviceId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java deleted file mode 100644 index 9da8a2f41b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.thoughtcrime.securesms.service.webrtc; - -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.signal.ringrtc.CallId; -import org.signal.ringrtc.GroupCall; -import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.events.WebRtcViewModel; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.ringrtc.CameraState; -import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel; -import org.thoughtcrime.securesms.service.WebRtcCallService; -import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; -import org.webrtc.PeerConnection; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.calls.OfferMessage; -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -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_SDP; -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_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_ERROR_CALL_STATE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_IDENTITY_KEY; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_END_REASON; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_HASH; -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_MULTI_RING; -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_TYPE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OPAQUE_MESSAGE; -import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ORIENTATION_DEGREES; -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_PEER_KEY; -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. - */ -public final class WebRtcIntentParser { - - private static final String TAG = Log.tag(WebRtcIntentParser.class); - - private WebRtcIntentParser() {} - - public static @NonNull CallId getCallId(@NonNull Intent intent) { - return new CallId(intent.getLongExtra(EXTRA_CALL_ID, -1)); - } - - public static int getRemoteDevice(@NonNull Intent intent) { - return intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); - } - - public static @NonNull RemotePeer getRemotePeer(@NonNull Intent intent) { - RemotePeer remotePeer = intent.getParcelableExtra(WebRtcCallService.EXTRA_REMOTE_PEER); - if (remotePeer == null) { - throw new AssertionError("No RemotePeer in intent!"); - } - return remotePeer; - } - - public static @NonNull RemotePeer getRemotePeerFromMap(@NonNull Intent intent, @NonNull WebRtcServiceState currentState) { - RemotePeer remotePeer = getNullableRemotePeerFromMap(intent, currentState); - - if (remotePeer == null) { - throw new AssertionError("No RemotePeer in map for key: " + getRemotePeerKey(intent) + "!"); - } - - return remotePeer; - } - - public static @Nullable RemotePeer getNullableRemotePeerFromMap(@NonNull Intent intent, @NonNull WebRtcServiceState currentState) { - return currentState.getCallInfoState().getPeer(getRemotePeerKey(intent)); - } - - public static int getRemotePeerKey(@NonNull Intent intent) { - if (!intent.hasExtra(EXTRA_REMOTE_PEER_KEY)) { - throw new AssertionError("No RemotePeer key in intent!"); - } - - // The default of -1 should never be applied since the key exists. - return intent.getIntExtra(EXTRA_REMOTE_PEER_KEY, -1); - } - - public static boolean getMultiRingFlag(@NonNull Intent intent) { - return intent.getBooleanExtra(EXTRA_MULTI_RING, false); - } - - public static @NonNull byte[] getRemoteIdentityKey(@NonNull Intent intent) { - return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_REMOTE_IDENTITY_KEY)); - } - - public static @Nullable String getAnswerSdp(@NonNull Intent intent) { - return intent.getStringExtra(EXTRA_ANSWER_SDP); - } - - public static @Nullable String getOfferSdp(@NonNull Intent intent) { - return intent.getStringExtra(EXTRA_OFFER_SDP); - } - - public static @Nullable byte[] getAnswerOpaque(@NonNull Intent intent) { - return intent.getByteArrayExtra(EXTRA_ANSWER_OPAQUE); - } - - public static @Nullable byte[] getOfferOpaque(@NonNull Intent intent) { - 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) { - return intent.getBooleanExtra(EXTRA_BROADCAST, false); - } - - public static boolean getAvailable(@NonNull Intent intent) { - return intent.getBooleanExtra(EXTRA_AVAILABLE, false); - } - - public static int getOrientationDegrees(@NonNull Intent intent) { - return intent.getIntExtra(EXTRA_ORIENTATION_DEGREES, 0); - } - - public static @NonNull ArrayList getIceCandidates(@NonNull Intent intent) { - return Objects.requireNonNull(intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES)); - } - - public static @NonNull List getIceServers(@NonNull Intent intent) { - TurnServerInfoParcel turnServerInfoParcel = Objects.requireNonNull(intent.getParcelableExtra(EXTRA_TURN_SERVER_INFO)); - List iceServers = new LinkedList<>(); - iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer()); - for (String url : turnServerInfoParcel.getUrls()) { - Log.i(TAG, "ice_server: " + url); - if (url.startsWith("turn")) { - iceServers.add(PeerConnection.IceServer.builder(url) - .setUsername(turnServerInfoParcel.getUsername()) - .setPassword(turnServerInfoParcel.getPassword()) - .createIceServer()); - } else { - iceServers.add(PeerConnection.IceServer.builder(url).createIceServer()); - } - } - return iceServers; - } - - public static @NonNull OfferMessage.Type getOfferMessageType(@NonNull Intent intent) { - return OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE)); - } - - public static boolean getEnable(@NonNull Intent intent) { - 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) { - return (WebRtcViewModel.State) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_ERROR_CALL_STATE)); - } - - public static @NonNull Optional getErrorIdentityKey(@NonNull Intent intent) { - IdentityKeyParcelable identityKeyParcelable = intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY); - if (identityKeyParcelable != null) { - return Optional.fromNullable(identityKeyParcelable.get()); - } - return Optional.absent(); - } - - public static int getGroupCallHash(@NonNull Intent intent) { - return intent.getIntExtra(EXTRA_GROUP_CALL_HASH, 0); - } - - public static @NonNull GroupCall.GroupCallEndReason getGroupCallEndReason(@NonNull Intent intent) { - int ordinal = intent.getIntExtra(EXTRA_GROUP_CALL_END_REASON, -1); - - if (ordinal >= 0 && ordinal < GroupCall.GroupCallEndReason.values().length) { - return GroupCall.GroupCallEndReason.values()[ordinal]; - } - - return GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED; - } - - public static @NonNull RecipientId getRecipientId(@NonNull Intent intent, @NonNull String name) { - return RecipientId.from(Objects.requireNonNull(intent.getStringExtra(name))); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java index 8dd7693a72..64695ca9dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.service.webrtc; +import android.content.Context; import android.net.Uri; import androidx.annotation.NonNull; @@ -11,10 +12,8 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.ringrtc.CameraEventListener; import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.util.AppForegroundObserver; -import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager; import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger; import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; import org.thoughtcrime.securesms.webrtc.locks.LockManager; @@ -30,32 +29,33 @@ import java.util.UUID; */ public class WebRtcInteractor { - @NonNull private final WebRtcCallService webRtcCallService; - @NonNull private final CallManager callManager; + @NonNull private final Context context; + @NonNull private final SignalCallManager signalCallManager; @NonNull private final LockManager lockManager; @NonNull private final SignalAudioManager audioManager; - @NonNull private final BluetoothStateManager bluetoothStateManager; @NonNull private final CameraEventListener cameraEventListener; @NonNull private final GroupCall.Observer groupCallObserver; @NonNull private final AppForegroundObserver.Listener foregroundListener; - public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService, - @NonNull CallManager callManager, + public WebRtcInteractor(@NonNull Context context, + @NonNull SignalCallManager signalCallManager, @NonNull LockManager lockManager, @NonNull SignalAudioManager audioManager, - @NonNull BluetoothStateManager bluetoothStateManager, @NonNull CameraEventListener cameraEventListener, @NonNull GroupCall.Observer groupCallObserver, @NonNull AppForegroundObserver.Listener foregroundListener) { - this.webRtcCallService = webRtcCallService; - this.callManager = callManager; - this.lockManager = lockManager; - this.audioManager = audioManager; - this.bluetoothStateManager = bluetoothStateManager; - this.cameraEventListener = cameraEventListener; - this.groupCallObserver = groupCallObserver; - this.foregroundListener = foregroundListener; + this.context = context; + this.signalCallManager = signalCallManager; + this.lockManager = lockManager; + this.audioManager = audioManager; + this.cameraEventListener = cameraEventListener; + this.groupCallObserver = groupCallObserver; + this.foregroundListener = foregroundListener; + } + + @NonNull Context getContext() { + return context; } @NonNull CameraEventListener getCameraEventListener() { @@ -63,11 +63,7 @@ public class WebRtcInteractor { } @NonNull CallManager getCallManager() { - return callManager; - } - - @NonNull WebRtcCallService getWebRtcCallService() { - return webRtcCallService; + return signalCallManager.getRingRtcCallManager(); } @NonNull GroupCall.Observer getGroupCallObserver() { @@ -79,63 +75,59 @@ public class WebRtcInteractor { } void setWantsBluetoothConnection(boolean enabled) { - bluetoothStateManager.setWantsConnection(enabled); + WebRtcCallService.setWantsBluetoothConnection(context, enabled); } void updatePhoneState(@NonNull LockManager.PhoneState phoneState) { lockManager.updatePhoneState(phoneState); } - void sendMessage(@NonNull WebRtcServiceState state) { - webRtcCallService.sendMessage(state); + void postStateUpdate(@NonNull WebRtcServiceState state) { + signalCallManager.postStateUpdate(state); } void sendCallMessage(@NonNull RemotePeer remotePeer, @NonNull SignalServiceCallMessage callMessage) { - webRtcCallService.sendCallMessage(remotePeer, callMessage); - } - - void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage callMessage) { - webRtcCallService.sendOpaqueCallMessage(uuid, callMessage); + signalCallManager.sendCallMessage(remotePeer, callMessage); } void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) { - webRtcCallService.sendGroupCallMessage(recipient, groupCallEraId); + signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId); } void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection joinedMembers, boolean isCallFull) { - webRtcCallService.updateGroupCallUpdateMessage(groupId, groupCallEraId, joinedMembers, isCallFull); + signalCallManager.updateGroupCallUpdateMessage(groupId, groupCallEraId, joinedMembers, isCallFull); } void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) { - webRtcCallService.setCallInProgressNotification(type, remotePeer.getRecipient()); + WebRtcCallService.update(context, type, remotePeer.getRecipient().getId()); } void setCallInProgressNotification(int type, @NonNull Recipient recipient) { - webRtcCallService.setCallInProgressNotification(type, recipient); + WebRtcCallService.update(context, type, recipient.getId()); } void retrieveTurnServers(@NonNull RemotePeer remotePeer) { - webRtcCallService.retrieveTurnServers(remotePeer); + signalCallManager.retrieveTurnServers(remotePeer); } void stopForegroundService() { - webRtcCallService.stopForeground(true); + WebRtcCallService.stop(context); } - void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) { - webRtcCallService.insertMissedCall(remotePeer, signal, timestamp, isVideoOffer); + void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) { + signalCallManager.insertMissedCall(remotePeer, true, timestamp, isVideoOffer); } boolean startWebRtcCallActivityIfPossible() { - return webRtcCallService.startCallCardActivityIfPossible(); + return signalCallManager.startCallCardActivityIfPossible(); } void registerPowerButtonReceiver() { - webRtcCallService.registerPowerButtonReceiver(); + WebRtcCallService.changePowerButtonReceiver(context, true); } void unregisterPowerButtonReceiver() { - webRtcCallService.unregisterPowerButtonReceiver(); + WebRtcCallService.changePowerButtonReceiver(context, false); } void silenceIncomingRinger() { @@ -150,8 +142,8 @@ public class WebRtcInteractor { audioManager.startIncomingRinger(ringtoneUri, vibrate); } - void startOutgoingRinger(@NonNull OutgoingRinger.Type type) { - audioManager.startOutgoingRinger(type); + void startOutgoingRinger() { + audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING); } void stopAudio(boolean playDisconnect) { @@ -163,6 +155,6 @@ public class WebRtcInteractor { } void peekGroupCall(@NonNull RecipientId recipientId) { - webRtcCallService.peekGroupCall(recipientId); + signalCallManager.peekGroupCall(recipientId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcUtil.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcUtil.java index 52d07a43b0..989553a747 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcUtil.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; /** @@ -42,6 +43,27 @@ public final class WebRtcUtil { return offerType == OfferMessage.Type.VIDEO_CALL ? CallManager.CallMediaType.VIDEO_CALL : CallManager.CallMediaType.AUDIO_CALL; } + public static @NonNull OfferMessage.Type getOfferTypeFromCallMediaType(@NonNull CallManager.CallMediaType callMediaType) { + return callMediaType == CallManager.CallMediaType.VIDEO_CALL ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL; + } + + public static @NonNull HangupMessage.Type getHangupTypeFromCallHangupType(@NonNull CallManager.HangupType hangupType) { + switch (hangupType) { + case ACCEPTED: + return HangupMessage.Type.ACCEPTED; + case BUSY: + return HangupMessage.Type.BUSY; + case NORMAL: + return HangupMessage.Type.NORMAL; + case DECLINED: + return HangupMessage.Type.DECLINED; + case NEED_PERMISSION: + return HangupMessage.Type.NEED_PERMISSION; + default: + throw new IllegalArgumentException("Unexpected hangup type: " + hangupType); + } + } + public static void enableSpeakerPhoneIfNeeded(@NonNull Context context, boolean enable) { if (!enable) { return; @@ -49,7 +71,7 @@ public final class WebRtcUtil { AudioManager androidAudioManager = ServiceUtil.getAudioManager(context); //noinspection deprecation - boolean shouldEnable = !(androidAudioManager.isSpeakerphoneOn() || androidAudioManager.isBluetoothScoOn() || androidAudioManager.isWiredHeadsetOn()); + boolean shouldEnable = !(androidAudioManager.isSpeakerphoneOn() || androidAudioManager.isBluetoothScoOn() || androidAudioManager.isWiredHeadsetOn()); if (shouldEnable) { androidAudioManager.setSpeakerphoneOn(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java b/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java index f077f8afb8..55ca1057fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java @@ -86,7 +86,7 @@ public final class AppForegroundObserver { } public interface Listener { - void onForeground(); - void onBackground(); + default void onForeground() {} + default void onBackground() {} } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index e876785628..3d14f34ab3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinBottomSheetDialogFragment; import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinUpdateRequiredBottomSheetDialogFragment; @@ -34,11 +35,8 @@ import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.proxy.ProxyBottomSheetFragment; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.ringrtc.RemotePeer; -import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; -import org.whispersystems.signalservice.api.messages.calls.OfferMessage; public class CommunicationActions { @@ -53,7 +51,7 @@ public class CommunicationActions { return; } - WebRtcCallService.isCallActive(activity, new ResultReceiver(new Handler(Looper.getMainLooper())) { + ApplicationDependencies.getSignalCallManager().isCallActive(new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == 1) { @@ -79,7 +77,7 @@ public class CommunicationActions { return; } - WebRtcCallService.isCallActive(activity, new ResultReceiver(new Handler(Looper.getMainLooper())) { + ApplicationDependencies.getSignalCallManager().isCallActive(new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { startCallInternal(activity, recipient, resultCode != 1); @@ -248,11 +246,7 @@ public class CommunicationActions { R.drawable.ic_mic_solid_24) .withPermanentDenialDialog(activity.getString(R.string.ConversationActivity__to_call_s_signal_needs_access_to_your_microphone, recipient.getDisplayName(activity))) .onAllGranted(() -> { - Intent intent = new Intent(activity, WebRtcCallService.class); - intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId())) - .putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, OfferMessage.Type.AUDIO_CALL.getCode()); - activity.startService(intent); + ApplicationDependencies.getSignalCallManager().startOutgoingAudioCall(recipient); MessageSender.onMessageSent(); @@ -274,11 +268,7 @@ public class CommunicationActions { R.drawable.ic_video_solid_24_tinted) .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_PRE_JOIN_CALL) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId())) - .putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, OfferMessage.Type.VIDEO_CALL.getCode()); - activity.startService(intent); + ApplicationDependencies.getSignalCallManager().startPreJoinCall(recipient); Intent activityIntent = new Intent(activity, WebRtcCallActivity.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java index a2c682d503..96b1d52b5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java @@ -11,13 +11,12 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.WebRtcCallService; +import org.thoughtcrime.securesms.service.webrtc.WebRtcCallService; /** * Manages the state of the WebRtc items in the Android notification bar. @@ -53,7 +52,7 @@ public class CallNotificationBuilder { builder.setPriority(NotificationCompat.PRIORITY_MIN); } else if (type == TYPE_INCOMING_RINGING) { builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call)); - builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_DENY_CALL, R.drawable.ic_close_grey600_32dp, R.string.NotificationBarManager__deny_call)); + builder.addAction(getServiceNotificationAction(context, WebRtcCallService.denyCallIntent(context), R.drawable.ic_close_grey600_32dp, R.string.NotificationBarManager__deny_call)); builder.addAction(getActivityNotificationAction(context, WebRtcCallActivity.ANSWER_ACTION, R.drawable.ic_phone_grey600_32dp, R.string.NotificationBarManager__answer_call)); if (callActivityRestricted()) { @@ -63,10 +62,10 @@ public class CallNotificationBuilder { } } else if (type == TYPE_OUTGOING_RINGING) { builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call)); - builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call)); + builder.addAction(getServiceNotificationAction(context, WebRtcCallService.hangupIntent(context), R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call)); } else { builder.setContentText(context.getString(R.string.NotificationBarManager_signal_call_in_progress)); - builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__end_call)); + builder.addAction(getServiceNotificationAction(context, WebRtcCallService.hangupIntent(context), R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__end_call)); } return builder.build(); @@ -93,10 +92,7 @@ public class CallNotificationBuilder { } } - private static NotificationCompat.Action getServiceNotificationAction(Context context, String action, int iconResId, int titleResId) { - Intent intent = new Intent(context, WebRtcCallService.class); - intent.setAction(action); - + private static NotificationCompat.Action getServiceNotificationAction(Context context, Intent intent, int iconResId, int titleResId) { PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent); diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/VoiceCallShare.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/VoiceCallShare.java index 5f0b3d210f..6d05e712de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/VoiceCallShare.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/VoiceCallShare.java @@ -9,11 +9,9 @@ import android.text.TextUtils; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.WebRtcCallActivity; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; 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; -import org.whispersystems.signalservice.api.messages.calls.OfferMessage; public class VoiceCallShare extends Activity { @@ -34,11 +32,7 @@ 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) - .putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(recipient.getId())) - .putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, OfferMessage.Type.AUDIO_CALL.getCode()); - startService(serviceIntent); + ApplicationDependencies.getSignalCallManager().startOutgoingAudioCall(recipient); Intent activityIntent = new Intent(this, WebRtcCallActivity.class); activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);