From e1b6bb7e56dfaf19135918c468f4dfc1a0bec73d Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 19 Apr 2022 14:25:40 +1000 Subject: [PATCH] Add one on one calls over clearnet (#864) * feat: adding basic webrtc deps and test activity * more testing code * feat: add protos and bump version * feat: added basic call functionality * feat: adding UI and flipping cameras * feat: add stats and starting call bottom sheet * feat: hanging up and bottom sheet behaviors should work now * feat: add call stats report on frontend * feat: add relay toggle for answer and offer * fix: add keep screen on and more end call message on back pressed / on finish * refactor: removing and replacing dagger 1 dep with android hilt * feat: include latest proto * feat: update to utilise call ID * feat: add stun and turn * refactor: playing around with deps and transport types * feat: adding call service functionality and permissions for calls * feat: add call manager and more static intent building functions for WebRtcCallService.kt * feat: adding ringers and more audio boilerplate * feat: audio manager call service boilerplate * feat: update kotlin and add in call view model and more management functions * refactor: moving call code around to service and viewmodel interactions * feat: plugging CallManager.kt into view model and service, fixing up dependencies * feat: implementing more WebRtcCallService.kt functions and handlers for actions as well as lifecycle * feat: adding more lifecycle vm and callmanager / call service functionality * feat: adding more command handlers in WebRtcCallService.kt * feat: more commands handled, adding lock manager and bluetooth permissions * feat: adding remainder of basic functionality to services and CallManager.kt * feat: hooking up calls and fixing broken dependencies and compile errors * fix: add timestamp to incoming call * feat: some connection and service launching / ring lifecycle * feat: call establishing and displaying * fix: fixing call connect flows * feat: ringers and better state handling * feat: updating call layout * feat: add fixes to bluetooth and begin the network renegotiation * feat: add call related permissions and more network handover tests * fix: don't display call option in conversation and don't show notification if option not enabled * fix: incoming ringer fix on receiving call, call notification priorities and notification channel update * build: update build number for testing * fix: bluetooth auto-connection and re-connection fixes, removing finished todos, allowing self-send call messages for deduping answers * feat: add pre-offer information and action handling in web rtc call service * refactor: discard offer messages from non-matching pre-offers we are already expecting * build: build numbers and version name update * feat: handle discarding pending calls from linked devices * feat: add signing props to release config build * docs: fix comment on time being 300s (5m) instead of 30s * feat: adding call messages for incoming/outgoing/missed * refactor: handle in-thread call notifications better and replace deny button intent with denyCallIntent instead of hangup * feat: add a hangup via data channel message * feat: process microphone enabled events and remove debuggable from build.gradle * feat: add first call notification * refactor: set the buttons to match iOS in terms of enable disable and colours * refactor: change the call logos in control messages * refactor: more bluetooth improvements * refactor: move start ringer and init of audio manager to CallManager.kt and string fix up * build: remove debuggable for release build * refactor: replace call icons * feat: adding a call time display * refactor: change the call time to update every second * refactor: testing out the full screen intents * refactor: wrapper use corrected session description, set title to recipient displayName, indicate session calls * fix: crash on view with a parent already attached * refactor: aspect ratio fit preserved * refactor: add wantsToAnswer ability in pre-init for fullscreenintent * refactor: prevent calls from non hasSent participants * build: update gradle code * refactor: replace timeout schedule with a seconds count * fix: various bug fixes for calls * fix: remove end call from busy * refactor: use answerCall instead of manual intent building again * build: new version * feat: add silenced notifications for call notification builder. check pre-offer and connecting state for pending connection * build: update build number * fix: text color uses overridden style value * fix: remove wrap content for renderers and look more at recovering from network switches * build: update build number * refactor: remove whitespace * build: update build number * refactor: used shared number for BatchMessageReceiveJob.kt parameter across pollers * fix: glide in update crash * fix: bug fixes for self-send answer / hangup messages * build: update build number * build: update build.gradle number * refactor: compile errors and refactoring to view binding * fix: set the content to binding.root view * build: increase build number * build: update build numbers * feat: adding base for rotation and picking random subset of turn servers * feat: starting the screen rotation processing * feat: setting up rotation for the remote render view * refactor: applying rotation and mirroring based on front / rear cameras that wraps nicely, only scale reworking needed * refactor: calls video stretching but consistent * refactor: state machine and tests for the transition events * feat: new call state processing * refactor: adding reconnecting logic and visuals * feat: state machine reconnect logic wip * feat: add reconnecting and merge fixes * feat: check new session based off current state * feat: reconnection logic works correctly now * refactor: reduce TIMEOUT_SECONDS to 30 from 90 * feat: reset peer connection on DC to prevent ICE messages from old connection or stale state in reconnecting * refactor: add null case * fix: set approved on new outgoing threads, use approved more deeply and invalidate the options menu on recipient modified. Add approvedMe flag toggles for visible message receive * fix: add name update in action bar on modified, change where approvedMe is set * build: increment build number * build: update build number * fix: merge compile errors and increment build number * refactor: remove negotiation based on which party dropped connection * refactor: call reconnection improvement tested cross platform to re-establish * refactor: failed and disconnect events only handled if either the reconnect or the timeout runnables are not set * build: update version number * fix: reduce timeout * fix: fixes the incoming hangup logic for linked devices * refactor: match iOS styling for call activity closer * chore: upgrade build numbers * feat: add in call settings dialog for if calls is disabled in conversation * feat: add a first call missed control message and info popup with link to privacy settings * fix: looking at crash for specific large transaction in NotificationManager * refactor: removing the people in case transaction size reduces to fix notif crash * fix: comment out the entire send multiple to see if it fixes the issue * refactor: revert to including the full notification process in a try/catch to handle weird responses from NotificationManager * fix: add in notification settings prompt for calls and try to fall back to dirty full screen intent / start activity if we're allowed * build: upgrade build number --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 17 +- .../securesms/ApplicationContext.java | 4 + .../securesms/calls/WebRtcCallActivity.kt | 377 ++++ .../components/ProfilePictureView.kt | 9 +- .../conversation/v2/ConversationActivityV2.kt | 10 +- .../conversation/v2/ConversationAdapter.kt | 25 +- .../v2/menus/ConversationMenuHelper.kt | 43 +- .../v2/messages/ControlMessageView.kt | 11 + .../securesms/database/MmsSmsColumns.java | 15 +- .../securesms/database/SmsDatabase.java | 25 +- .../securesms/database/Storage.kt | 17 + .../securesms/database/ThreadDatabase.java | 20 +- .../database/model/DisplayRecord.java | 5 +- .../database/model/MessageRecord.java | 13 + .../securesms/dependencies/CallModule.kt | 31 + .../securesms/home/HomeActivity.kt | 2 + .../linkpreview/LinkPreviewRepository.java | 2 +- .../AbstractNotificationBuilder.java | 2 +- .../notifications/DefaultMessageNotifier.java | 43 +- .../MultipleRecipientNotificationBuilder.java | 2 +- .../notifications/NotificationChannels.java | 11 +- .../SingleRecipientNotificationBuilder.java | 4 +- .../AppProtectionPreferenceFragment.java | 100 + .../securesms/service/WebRtcCallService.kt | 861 ++++++++ .../securesms/util/CallNotificationBuilder.kt | 163 ++ .../securesms/webrtc/AudioEvent.kt | 5 + .../securesms/webrtc/AudioManagerCommand.kt | 37 + .../securesms/webrtc/CallManager.kt | 718 +++++++ .../securesms/webrtc/CallMessageProcessor.kt | 151 ++ .../securesms/webrtc/CallViewModel.kt | 88 + .../webrtc/IncomingPstnCallReceiver.java | 74 + .../securesms/webrtc/NetworkChangeReceiver.kt | 75 + .../webrtc/PeerConnectionException.kt | 6 + .../securesms/webrtc/PeerConnectionWrapper.kt | 335 +++ .../thoughtcrime/securesms/webrtc/PreOffer.kt | 6 + .../UncaughtExceptionHandlerManager.java | 42 + .../webrtc/WebRtcCallServiceReceivers.kt | 57 + .../webrtc/audio/AudioManagerCompat.java | 223 ++ .../securesms/webrtc/audio/IncomingRinger.kt | 94 + .../securesms/webrtc/audio/OutgoingRinger.kt | 56 + .../webrtc/audio/SignalAudioHandler.kt | 21 + .../webrtc/audio/SignalAudioManager.kt | 393 ++++ .../webrtc/audio/SignalBluetoothManager.kt | 364 ++++ .../securesms/webrtc/data/CallUtils.kt | 11 + .../securesms/webrtc/data/StateMachine.kt | 112 + .../webrtc/locks/AccelerometerListener.java | 162 ++ .../securesms/webrtc/locks/LockManager.java | 148 ++ .../securesms/webrtc/locks/ProximityLock.java | 50 + .../securesms/webrtc/video/Camera.kt | 78 + .../webrtc/video/CameraEventListener.kt | 18 + .../video/RemoteRotationVideoProxySink.kt | 32 + .../webrtc/video/RotationVideoSink.kt | 50 + .../state_list_call_action_background.xml | 5 + .../state_list_call_action_foreground.xml | 5 + .../state_list_call_action_mic_background.xml | 5 + .../ic_call_end_grey600_32dp.webp | Bin 0 -> 834 bytes .../ic_close_grey600_32dp.webp | Bin 0 -> 404 bytes .../ic_phone_grey600_32dp.webp | Bin 0 -> 966 bytes .../res/drawable/call_controls_background.xml | 13 + .../drawable/ic_baseline_arrow_back_24.xml | 3 +- .../main/res/drawable/ic_baseline_call_24.xml | 10 + .../res/drawable/ic_baseline_call_end_24.xml | 10 + .../res/drawable/ic_baseline_call_made_24.xml | 11 + .../drawable/ic_baseline_call_missed_24.xml | 11 + .../drawable/ic_baseline_call_received_24.xml | 11 + .../ic_baseline_flip_camera_android_24.xml | 16 + .../res/drawable/ic_baseline_mic_off_24.xml | 10 + .../res/drawable/ic_baseline_videocam_24.xml | 10 + .../drawable/ic_baseline_videocam_off_24.xml | 10 + .../drawable/ic_baseline_volume_mute_24.xml | 10 + .../res/drawable/ic_baseline_volume_up_24.xml | 10 + .../main/res/drawable/ic_incoming_call.xml | 12 + app/src/main/res/drawable/ic_missed_call.xml | 12 + .../main/res/drawable/ic_outgoing_call.xml | 12 + .../res/drawable/ic_outline_mic_none_24.xml | 10 + .../res/drawable/ic_outline_mic_off_24.xml | 10 + .../res/drawable/ic_outline_videocam_24.xml | 10 + .../drawable/ic_outline_videocam_off_24.xml | 10 + ...utline_button_medium_background_accent.xml | 11 + app/src/main/res/layout/activity_webrtc.xml | 266 +++ .../res/layout/fragment_call_bottom_sheet.xml | 77 + .../main/res/menu/menu_conversation_call.xml | 9 + app/src/main/res/raw/redphone_busy.mp3 | Bin 0 -> 169612 bytes app/src/main/res/raw/redphone_outring.mp3 | Bin 0 -> 97200 bytes app/src/main/res/raw/webrtc_completed.mp3 | Bin 0 -> 12525 bytes app/src/main/res/raw/webrtc_disconnected.mp3 | Bin 0 -> 13884 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 27 +- app/src/main/res/values/styles.xml | 13 + app/src/main/res/values/themes.xml | 7 + .../res/xml/preferences_app_protection.xml | 5 + .../securesms/calls/CallStateMachineTests.kt | 168 ++ .../securesms/calls/TestStateProcessor.kt | 18 + .../CursorRecyclerViewAdapterTest.java | 24 +- .../libsession/database/CallDataProvider.kt | 7 + .../libsession/database/StorageProtocol.kt | 3 + .../messaging/calls/CallMessageType.kt | 8 + .../messaging/jobs/BatchMessageReceiveJob.kt | 2 + .../messaging/messages/control/CallMessage.kt | 119 ++ .../messages/signal/IncomingTextMessage.java | 98 +- .../sending_receiving/MessageReceiver.kt | 1 + .../sending_receiving/MessageSender.kt | 7 +- .../ReceivedMessageHandler.kt | 8 + .../pollers/ClosedGroupPollerV2.kt | 10 +- .../pollers/OpenGroupPollerV2.kt | 2 +- .../sending_receiving/pollers/Poller.kt | 2 +- .../utilities/UpdateMessageBuilder.kt | 16 + .../messaging/utilities/WebRtcUtils.kt | 13 + .../utilities/TextSecurePreferences.kt | 53 + libsession/src/main/res/values/dimens.xml | 1 + libsignal/protobuf/SignalService.proto | 23 + .../libsignal/protos/SignalServiceProtos.java | 1882 +++++++++++++++-- .../session/libsignal/utilities/Retrying.kt | 2 +- 115 files changed, 8054 insertions(+), 286 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioEvent.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioManagerCommand.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/IncomingPstnCallReceiver.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionException.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/UncaughtExceptionHandlerManager.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/data/StateMachine.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/AccelerometerListener.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/ProximityLock.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/video/Camera.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/video/CameraEventListener.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt create mode 100644 app/src/main/res/color/state_list_call_action_background.xml create mode 100644 app/src/main/res/color/state_list_call_action_foreground.xml create mode 100644 app/src/main/res/color/state_list_call_action_mic_background.xml create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_call_end_grey600_32dp.webp create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_close_grey600_32dp.webp create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_phone_grey600_32dp.webp create mode 100644 app/src/main/res/drawable/call_controls_background.xml create mode 100644 app/src/main/res/drawable/ic_baseline_call_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_call_end_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_call_made_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_call_missed_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_call_received_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_mic_off_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_videocam_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_videocam_off_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_volume_mute_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_volume_up_24.xml create mode 100644 app/src/main/res/drawable/ic_incoming_call.xml create mode 100644 app/src/main/res/drawable/ic_missed_call.xml create mode 100644 app/src/main/res/drawable/ic_outgoing_call.xml create mode 100644 app/src/main/res/drawable/ic_outline_mic_none_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_mic_off_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_videocam_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_videocam_off_24.xml create mode 100644 app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml create mode 100644 app/src/main/res/layout/activity_webrtc.xml create mode 100644 app/src/main/res/layout/fragment_call_bottom_sheet.xml create mode 100644 app/src/main/res/menu/menu_conversation_call.xml create mode 100644 app/src/main/res/raw/redphone_busy.mp3 create mode 100644 app/src/main/res/raw/redphone_outring.mp3 create mode 100644 app/src/main/res/raw/webrtc_completed.mp3 create mode 100644 app/src/main/res/raw/webrtc_disconnected.mp3 create mode 100644 app/src/test/java/org/thoughtcrime/securesms/calls/CallStateMachineTests.kt create mode 100644 app/src/test/java/org/thoughtcrime/securesms/calls/TestStateProcessor.kt create mode 100644 libsession/src/main/java/org/session/libsession/database/CallDataProvider.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/calls/CallMessageType.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/utilities/WebRtcUtils.kt diff --git a/app/build.gradle b/app/build.gradle index bdfac64e7e..9e479509df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'witness' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' apply plugin: 'com.google.gms.google-services' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' @@ -54,7 +55,7 @@ dependencies { implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' implementation 'org.conscrypt:conscrypt-android:2.0.0' implementation 'org.signal:aesgcmprovider:0.0.3' - implementation 'org.whispersystems:webrtc-android:M74' + implementation 'org.webrtc:google-webrtc:1.0.32006' implementation "me.leolin:ShortcutBadger:1.1.16" implementation 'se.emilsjolander:stickylistheaders:2.7.0' implementation 'com.jpardogo.materialtabstrip:library:1.0.9' @@ -157,8 +158,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 261 -def canonicalVersionName = "1.11.20" +def canonicalVersionCode = 272 +def canonicalVersionName = "1.12.14" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 54ec5ea7ab..9bbbdbc365 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,7 @@ android:name="android.hardware.touchscreen" android:required="false" /> + @@ -51,9 +52,9 @@ - - + + @@ -300,6 +301,16 @@ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:exported="true" android:theme="@style/Theme.Session.DayNight.NoActionBar" /> + + + + () + private val glide by lazy { GlideApp.with(this) } + private lateinit var binding: ActivityWebrtcBinding + private var uiJob: Job? = null + private var wantsToAnswer = false + set(value) { + field = value + WebRtcCallService.broadcastWantsToAnswer(this, value) + } + private var hangupReceiver: BroadcastReceiver? = null + + private val rotationListener by lazy { + object : OrientationEventListener(this) { + override fun onOrientationChanged(orientation: Int) { + if ((orientation + 15) % 90 < 30) { + viewModel.deviceRotation = orientation +// updateControlsRotation(orientation.quadrantRotation() * -1) + } + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + finish() + return true + } + return super.onOptionsItemSelected(item) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + if (intent?.action == ACTION_ANSWER) { + val answerIntent = WebRtcCallService.acceptCallIntent(this) + ContextCompat.startForegroundService(this, answerIntent) + } + } + + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + super.onCreate(savedInstanceState, ready) + rotationListener.enable() + binding = ActivityWebrtcBinding.inflate(layoutInflater) + setContentView(binding.root) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true) + setTurnScreenOn(true) + } + window.addFlags( + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + ) + volumeControlStream = AudioManager.STREAM_VOICE_CALL + + if (intent.action == ACTION_ANSWER) { + answerCall() + } + if (intent.action == ACTION_PRE_OFFER) { + wantsToAnswer = true + answerCall() // this will do nothing, except update notification state + } + if (intent.action == ACTION_FULL_SCREEN_INTENT) { + supportActionBar?.setDisplayHomeAsUpEnabled(false) + } + + binding.microphoneButton.setOnClickListener { + val audioEnabledIntent = + WebRtcCallService.microphoneIntent(this, !viewModel.microphoneEnabled) + startService(audioEnabledIntent) + } + + binding.speakerPhoneButton.setOnClickListener { + val command = + AudioManagerCommand.SetUserDevice(if (viewModel.isSpeaker) EARPIECE else SPEAKER_PHONE) + WebRtcCallService.sendAudioManagerCommand(this, command) + } + + binding.acceptCallButton.setOnClickListener { + if (viewModel.currentCallState == CALL_PRE_INIT) { + wantsToAnswer = true + updateControls() + } + answerCall() + } + + binding.declineCallButton.setOnClickListener { + val declineIntent = WebRtcCallService.denyCallIntent(this) + startService(declineIntent) + } + + hangupReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + finish() + } + } + + LocalBroadcastManager.getInstance(this) + .registerReceiver(hangupReceiver!!, IntentFilter(ACTION_END)) + + binding.enableCameraButton.setOnClickListener { + Permissions.with(this) + .request(Manifest.permission.CAMERA) + .onAllGranted { + val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoEnabled) + startService(intent) + } + .execute() + } + + binding.switchCameraButton.setOnClickListener { + startService(WebRtcCallService.flipCamera(this)) + } + + binding.endCallButton.setOnClickListener { + startService(WebRtcCallService.hangupIntent(this)) + } + binding.backArrow.setOnClickListener { + onBackPressed() + } + + } + + override fun onDestroy() { + super.onDestroy() + hangupReceiver?.let { receiver -> + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) + } + rotationListener.disable() + } + + private fun answerCall() { + val answerIntent = WebRtcCallService.acceptCallIntent(this) + ContextCompat.startForegroundService(this, answerIntent) + } + + private fun updateControlsRotation(newRotation: Int) { + with (binding) { + val rotation = newRotation.toFloat() + remoteRecipient.rotation = rotation + speakerPhoneButton.rotation = rotation + microphoneButton.rotation = rotation + enableCameraButton.rotation = rotation + switchCameraButton.rotation = rotation + endCallButton.rotation = rotation + } + } + + private fun updateControls(state: CallViewModel.State? = null) { + with(binding) { + if (state == null) { + if (wantsToAnswer) { + controlGroup.isVisible = true + remoteLoadingView.isVisible = true + incomingControlGroup.isVisible = false + } + } else { + controlGroup.isVisible = state in listOf( + CALL_CONNECTED, + CALL_OUTGOING, + CALL_INCOMING + ) || (state == CALL_PRE_INIT && wantsToAnswer) + remoteLoadingView.isVisible = + state !in listOf(CALL_CONNECTED, CALL_RINGING, CALL_PRE_INIT) || wantsToAnswer + incomingControlGroup.isVisible = + state in listOf(CALL_RINGING, CALL_PRE_INIT) && !wantsToAnswer + reconnectingText.isVisible = state == CALL_RECONNECTING + endCallButton.isVisible = endCallButton.isVisible || state == CALL_RECONNECTING + } + } + } + + override fun onStart() { + super.onStart() + + uiJob = lifecycleScope.launch { + + launch { + viewModel.audioDeviceState.collect { state -> + val speakerEnabled = state.selectedDevice == SPEAKER_PHONE + // change drawable background to enabled or not + binding.speakerPhoneButton.isSelected = speakerEnabled + } + } + + launch { + viewModel.callState.collect { state -> + Log.d("Loki", "Consuming view model state $state") + when (state) { + CALL_RINGING -> { + if (wantsToAnswer) { + answerCall() + wantsToAnswer = false + } + } + CALL_OUTGOING -> { + } + CALL_CONNECTED -> { + wantsToAnswer = false + } + } + updateControls(state) + } + } + + launch { + viewModel.recipient.collect { latestRecipient -> + if (latestRecipient.recipient != null) { + val publicKey = latestRecipient.recipient.address.serialize() + val displayName = getUserDisplayName(publicKey) + supportActionBar?.title = displayName + val signalProfilePicture = latestRecipient.recipient.contactPhoto + val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject + val sizeInPX = + resources.getDimensionPixelSize(R.dimen.extra_large_profile_picture_size) + binding.remoteRecipientName.text = displayName + if (signalProfilePicture != null && avatar != "0" && avatar != "") { + glide.clear(binding.remoteRecipient) + glide.load(signalProfilePicture) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .circleCrop() + .error( + AvatarPlaceholderGenerator.generate( + this@WebRtcCallActivity, + sizeInPX, + publicKey, + displayName + ) + ) + .into(binding.remoteRecipient) + } else { + glide.clear(binding.remoteRecipient) + glide.load( + AvatarPlaceholderGenerator.generate( + this@WebRtcCallActivity, + sizeInPX, + publicKey, + displayName + ) + ) + .diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop() + .into(binding.remoteRecipient) + } + } else { + glide.clear(binding.remoteRecipient) + } + } + } + + launch { + while (isActive) { + val startTime = viewModel.callStartTime + if (startTime == -1L) { + binding.callTime.isVisible = false + } else { + binding.callTime.isVisible = true + binding.callTime.text = DurationFormatUtils.formatDuration( + System.currentTimeMillis() - startTime, + CALL_DURATION_FORMAT + ) + } + + delay(1_000) + } + } + + launch { + viewModel.localAudioEnabledState.collect { isEnabled -> + // change drawable background to enabled or not + binding.microphoneButton.isSelected = !isEnabled + } + } + + launch { + viewModel.localVideoEnabledState.collect { isEnabled -> + binding.localRenderer.removeAllViews() + if (isEnabled) { + viewModel.localRenderer?.let { surfaceView -> + surfaceView.setZOrderOnTop(true) + binding.localRenderer.addView(surfaceView) + } + } + binding.localRenderer.isVisible = isEnabled + binding.enableCameraButton.isSelected = isEnabled + } + } + + launch { + viewModel.remoteVideoEnabledState.collect { isEnabled -> + binding.remoteRenderer.removeAllViews() + if (isEnabled) { + viewModel.remoteRenderer?.let { surfaceView -> + binding.remoteRenderer.addView(surfaceView) + } + } + binding.remoteRenderer.isVisible = isEnabled + binding.remoteRecipient.isVisible = !isEnabled + } + } + } + } + + private fun getUserDisplayName(publicKey: String): String { + val contact = + DatabaseComponent.get(this).sessionContactDatabase().getContactWithSessionID(publicKey) + return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey + } + + override fun onStop() { + super.onStop() + uiJob?.cancel() + binding.remoteRenderer.removeAllViews() + binding.localRenderer.removeAllViews() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index df8544a39a..e5419774f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -71,6 +71,7 @@ class ProfilePictureView : RelativeLayout { } fun update() { + if (!this::glide.isInitialized) return val publicKey = publicKey ?: return val additionalPublicKey = additionalPublicKey if (additionalPublicKey != null) { @@ -104,12 +105,16 @@ class ProfilePictureView : RelativeLayout { if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject + val sizeInPX = resources.getDimensionPixelSize(sizeResId) if (signalProfilePicture != null && avatar != "0" && avatar != "") { glide.clear(imageView) - glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView) + glide.load(signalProfilePicture) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .circleCrop() + .error(AvatarPlaceholderGenerator.generate(context,sizeInPX, publicKey, displayName)) + .into(imageView) profilePicturesCache[publicKey] = recipient.profileAvatar } else { - val sizeInPX = resources.getDimensionPixelSize(sizeResId) glide.clear(imageView) glide.load(AvatarPlaceholderGenerator.generate(context, sizeInPX, publicKey, displayName)) .diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index e7c5b7e9e1..67aae625bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -170,7 +170,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private val linkPreviewViewModel: LinkPreviewViewModel by lazy { - ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository(this))) + ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository())) .get(LinkPreviewViewModel::class.java) } private val viewModel: ConversationViewModel by viewModels { @@ -558,7 +558,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onPrepareOptionsMenu(menu: Menu): Boolean { if (!isMessageRequestThread()) { - ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, viewModel.recipient, viewModel.threadId, this) { onOptionsItemSelected(it) } + ConversationMenuHelper.onPrepareOptionsMenu( + menu, + menuInflater, + viewModel.recipient, + viewModel.threadId, + this + ) { onOptionsItemSelected(it) } } super.onPrepareOptionsMenu(menu) return true diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 0c8bda33f5..c90739ab15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -1,10 +1,13 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.app.AlertDialog import android.content.Context +import android.content.Intent import android.database.Cursor import android.view.MotionEvent import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView.ViewHolder +import network.loki.messenger.R import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView @@ -12,6 +15,7 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit, private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit, @@ -76,7 +80,26 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr } view.contentViewDelegate = visibleMessageContentViewDelegate } - is ControlMessageViewHolder -> viewHolder.view.bind(message, messageBefore) + is ControlMessageViewHolder -> { + viewHolder.view.bind(message, messageBefore) + if (message.isCallLog && message.isFirstMissedCall) { + viewHolder.view.setOnClickListener { + AlertDialog.Builder(context) + .setTitle(R.string.CallNotificationBuilder_first_call_title) + .setMessage(R.string.CallNotificationBuilder_first_call_message) + .setPositiveButton(R.string.activity_settings_title) { _, _ -> + val intent = Intent(context, PrivacySettingsActivity::class.java) + context.startActivity(intent) + } + .setNeutralButton(R.string.cancel) { d, _ -> + d.dismiss() + } + .show() + } + } else { + viewHolder.view.setOnClickListener(null) + } + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 5966e765e3..5669cafa4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -39,19 +39,29 @@ import org.thoughtcrime.securesms.MediaOverviewActivity import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.ShortcutLauncherActivity +import org.thoughtcrime.securesms.calls.WebRtcCallActivity import org.thoughtcrime.securesms.contacts.SelectContactsActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.EditClosedGroupActivity import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey +import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity +import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.getColorWithID import java.io.IOException object ConversationMenuHelper { - fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, threadId: Long, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) { + fun onPrepareOptionsMenu( + menu: Menu, + inflater: MenuInflater, + thread: Recipient, + threadId: Long, + context: Context, + onOptionsItemSelected: (MenuItem) -> Unit + ) { // Prepare menu.clear() val isOpenGroup = thread.isOpenGroupRecipient @@ -100,6 +110,10 @@ object ConversationMenuHelper { inflater.inflate(R.menu.menu_conversation_notification_settings, menu) } + if (!thread.isGroupRecipient && thread.hasApprovedMe()) { + inflater.inflate(R.menu.menu_conversation_call, menu) + } + // Search val searchViewItem = menu.findItem(R.id.menu_search) (context as ConversationActivityV2).searchViewItem = searchViewItem @@ -150,6 +164,7 @@ object ConversationMenuHelper { R.id.menu_unmute_notifications -> { unmute(context, thread) } R.id.menu_mute_notifications -> { mute(context, thread) } R.id.menu_notification_settings -> { setNotifyType(context, thread) } + R.id.menu_call -> { call(context, thread) } } return true } @@ -166,6 +181,32 @@ object ConversationMenuHelper { searchViewModel.onSearchOpened() } + private fun call(context: Context, thread: Recipient) { + + if (!TextSecurePreferences.isCallNotificationsEnabled(context)) { + AlertDialog.Builder(context) + .setTitle(R.string.ConversationActivity_call_title) + .setMessage(R.string.ConversationActivity_call_prompt) + .setPositiveButton(R.string.activity_settings_title) { _, _ -> + val intent = Intent(context, PrivacySettingsActivity::class.java) + context.startActivity(intent) + } + .setNeutralButton(R.string.cancel) { d, _ -> + d.dismiss() + }.show() + return + } + + val service = WebRtcCallService.createCall(context, thread) + context.startService(service) + + val activity = Intent(context, WebRtcCallActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + context.startActivity(activity) + + } + @SuppressLint("StaticFieldLeak") private fun addShortcut(context: Context, thread: Recipient) { object : AsyncTask() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index e4247b86ff..a4e4a52d5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -29,6 +29,7 @@ class ControlMessageView : LinearLayout { // region Updating fun bind(message: MessageRecord, previous: MessageRecord?) { binding.dateBreakTextView.showDateBreak(message, previous) + binding.iconImageView.visibility = View.GONE var messageBody: CharSequence = message.getDisplayBody(context) when { message.isExpirationTimerUpdate -> { @@ -46,6 +47,16 @@ class ControlMessageView : LinearLayout { message.isMessageRequestResponse -> { messageBody = context.getString(R.string.message_requests_accepted) } + message.isCallLog -> { + val drawable = when { + message.isIncomingCall -> R.drawable.ic_incoming_call + message.isOutgoingCall -> R.drawable.ic_outgoing_call + message.isFirstMissedCall -> R.drawable.ic_info_outline_light + else -> R.drawable.ic_missed_call + } + binding.iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme)) + binding.iconImageView.visibility = View.VISIBLE + } } binding.textView.text = messageBody diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 92f9e970d7..5905433b93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -32,6 +32,7 @@ public interface MmsSmsColumns { protected static final long OUTGOING_CALL_TYPE = 2; protected static final long MISSED_CALL_TYPE = 3; protected static final long JOINED_TYPE = 4; + protected static final long FIRST_MISSED_CALL_TYPE = 5; protected static final long BASE_INBOX_TYPE = 20; protected static final long BASE_OUTBOX_TYPE = 21; @@ -207,7 +208,8 @@ public interface MmsSmsColumns { } public static boolean isCallLog(long type) { - return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_CALL_TYPE; + long baseType = type & BASE_TYPE_MASK; + return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE || baseType == MISSED_CALL_TYPE || baseType == FIRST_MISSED_CALL_TYPE; } public static boolean isExpirationTimerUpdate(long type) { @@ -227,17 +229,22 @@ public interface MmsSmsColumns { } public static boolean isIncomingCall(long type) { - return type == INCOMING_CALL_TYPE; + return (type & BASE_TYPE_MASK) == INCOMING_CALL_TYPE; } public static boolean isOutgoingCall(long type) { - return type == OUTGOING_CALL_TYPE; + return (type & BASE_TYPE_MASK) == OUTGOING_CALL_TYPE; } public static boolean isMissedCall(long type) { - return type == MISSED_CALL_TYPE; + return (type & BASE_TYPE_MASK) == MISSED_CALL_TYPE; } + public static boolean isFirstMissedCall(long type) { + return (type & BASE_TYPE_MASK) == FIRST_MISSED_CALL_TYPE; + } + + public static boolean isGroupUpdate(long type) { return (type & GROUP_UPDATE_BIT) != 0; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 558957515e..a34de25a98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -28,6 +28,7 @@ import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; +import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; @@ -373,6 +374,24 @@ public class SmsDatabase extends MessagingDatabase { if (message.isOpenGroupInvitation()) type |= Types.OPEN_GROUP_INVITATION_BIT; + CallMessageType callMessageType = message.getCallType(); + if (callMessageType != null) { + switch (callMessageType) { + case CALL_OUTGOING: + type |= Types.OUTGOING_CALL_TYPE; + break; + case CALL_INCOMING: + type |= Types.INCOMING_CALL_TYPE; + break; + case CALL_MISSED: + type |= Types.MISSED_CALL_TYPE; + break; + case CALL_FIRST_MISSED: + type |= Types.FIRST_MISSED_CALL_TYPE; + break; + } + } + Recipient recipient = Recipient.from(context, message.getSender(), true); Recipient groupRecipient; @@ -384,7 +403,7 @@ public class SmsDatabase extends MessagingDatabase { } boolean unread = (Util.isDefaultSmsProvider(context) || - message.isSecureMessage() || message.isGroup()); + message.isSecureMessage() || message.isGroup() || message.isCallInfo()); long threadId; @@ -441,6 +460,10 @@ public class SmsDatabase extends MessagingDatabase { return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0); } + public Optional insertCallMessage(IncomingTextMessage message) { + return insertMessageInbox(message, 0, 0); + } + public Optional insertMessageInbox(IncomingTextMessage message, long serverTimestamp) { return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 657e699b52..0a3d3c45d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob @@ -716,4 +717,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe) } + + override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { + val database = DatabaseComponent.get(context).smsDatabase() + val address = fromSerialized(senderPublicKey) + val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp) + database.insertCallMessage(callMessage) + } + + override fun conversationHasOutgoing(userPublicKey: String): Boolean { + val database = DatabaseComponent.get(context).threadDatabase() + val threadId = database.getThreadIdIfExistsFor(userPublicKey) + + if (threadId == -1L) return false + + return database.getLastSeenAndHasSent(threadId).second() ?: false + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 9a47de7850..9e105025f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -547,22 +547,10 @@ public class ThreadDatabase extends Database { SessionMetaProtocol.clearReceivedMessages(); } - public boolean hasThread(long threadId) { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, new String[]{ ID }, ID_WHERE, new String[]{ String.valueOf(threadId) }, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { return true; } - return false; - } finally { - if (cursor != null) cursor.close(); - } - } - - public long getThreadIdIfExistsFor(Recipient recipient) { + public long getThreadIdIfExistsFor(String address) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); String where = ADDRESS + " = ?"; - String[] recipientsArg = new String[] {recipient.getAddress().serialize()}; + String[] recipientsArg = new String[] {address}; Cursor cursor = null; try { @@ -578,6 +566,10 @@ public class ThreadDatabase extends Database { } } + public long getThreadIdIfExistsFor(Recipient recipient) { + return getThreadIdIfExistsFor(recipient.getAddress().serialize()); + } + public long getOrCreateThreadIdFor(Recipient recipient) { return getOrCreateThreadIdFor(recipient, DistributionTypes.DEFAULT); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 141c77791e..ef0f4b54f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -117,11 +117,14 @@ public abstract class DisplayRecord { public boolean isMissedCall() { return SmsDatabase.Types.isMissedCall(type); } + public boolean isFirstMissedCall() { + return SmsDatabase.Types.isFirstMissedCall(type); + } public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } public boolean isMessageRequestResponse() { return MmsSmsColumns.Types.isMessageRequestResponse(type); } public boolean isControlMessage() { return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification() - || isMessageRequestResponse(); + || isMessageRequestResponse() || isCallLog(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 31db5b4514..0f5247cf8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -24,6 +24,7 @@ import android.text.style.StyleSpan; import androidx.annotation.NonNull; +import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; @@ -112,6 +113,18 @@ public abstract class MessageRecord extends DisplayRecord { } else if (isDataExtractionNotification()) { if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); + } else if (isCallLog()) { + CallMessageType callType; + if (isIncomingCall()) { + callType = CallMessageType.CALL_INCOMING; + } else if (isOutgoingCall()) { + callType = CallMessageType.CALL_OUTGOING; + } else if (isMissedCall()) { + callType = CallMessageType.CALL_MISSED; + } else { + callType = CallMessageType.CALL_FIRST_MISSED; + } + return new SpannableString(UpdateMessageBuilder.INSTANCE.buildCallMessage(context, callType, getIndividualRecipient().getAddress().serialize())); } return new SpannableString(getBody()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt new file mode 100644 index 0000000000..da15c2f6b4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.dependencies + +import android.content.Context +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ServiceComponent +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ServiceScoped +import dagger.hilt.components.SingletonComponent +import org.session.libsession.database.CallDataProvider +import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.webrtc.CallManager +import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object CallModule { + + @Provides + @Singleton + fun provideAudioManagerCompat(@ApplicationContext context: Context) = AudioManagerCompat.create(context) + + @Provides + @Singleton + fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat, storage: Storage) = + CallManager(context, audioManagerCompat, storage) + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 0522dbcac2..d70653f8a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show + import java.io.IOException import java.util.Locale import javax.inject.Inject @@ -195,6 +196,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } this.broadcastReceiver = broadcastReceiver LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged")) + lifecycleScope.launchWhenStarted { launch(Dispatchers.IO) { // Double check that the long poller is up diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 0b9a37971a..697f6718c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -43,7 +43,7 @@ public class LinkPreviewRepository { private final OkHttpClient client; - public LinkPreviewRepository(@NonNull Context context) { + public LinkPreviewRepository() { this.client = new OkHttpClient.Builder() .addNetworkInterceptor(new ContentProxySafetyInterceptor()) .cache(null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index a359596c91..549d6eac0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -25,7 +25,7 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui @SuppressWarnings("unused") private static final String TAG = AbstractNotificationBuilder.class.getSimpleName(); - private static final int MAX_DISPLAY_LENGTH = 500; + private static final int MAX_DISPLAY_LENGTH = 50; protected Context context; protected NotificationPrivacyPreference privacy; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index efe75fef8e..31186e24c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -259,10 +259,8 @@ public class DefaultMessageNotifier implements MessageNotifier { try { telcoCursor = DatabaseComponent.get(context).mmsSmsDatabase().getUnread(); - pushCursor = DatabaseComponent.get(context).pushDatabase().getPending(); - if (((telcoCursor == null || telcoCursor.isAfterLast()) && - (pushCursor == null || pushCursor.isAfterLast())) || !TextSecurePreferences.hasSeenWelcomeScreen(context)) + if ((telcoCursor == null || telcoCursor.isAfterLast()) || !TextSecurePreferences.hasSeenWelcomeScreen(context)) { cancelActiveNotifications(context); updateBadge(context, 0); @@ -278,15 +276,19 @@ public class DefaultMessageNotifier implements MessageNotifier { lastAudibleNotification = System.currentTimeMillis(); } - if (notificationState.hasMultipleThreads()) { - for (long threadId : notificationState.getThreads()) { - sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); + try { + if (notificationState.hasMultipleThreads()) { + for (long threadId : notificationState.getThreads()) { + sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); + } + sendMultipleThreadNotification(context, notificationState, signal); + } else if (notificationState.getMessageCount() > 0){ + sendSingleThreadNotification(context, notificationState, signal, false); + } else { + cancelActiveNotifications(context); } - sendMultipleThreadNotification(context, notificationState, signal); - } else if (notificationState.getMessageCount() > 0){ - sendSingleThreadNotification(context, notificationState, signal, false); - } else { - cancelActiveNotifications(context); + } catch (Exception e) { + Log.e(TAG, "Error creating notification",e); } cancelOrphanedNotifications(context, notificationState); updateBadge(context, notificationState.getMessageCount()); @@ -296,10 +298,18 @@ public class DefaultMessageNotifier implements MessageNotifier { } } finally { if (telcoCursor != null) telcoCursor.close(); - if (pushCursor != null) pushCursor.close(); } } + private String getTrimmedText(CharSequence text) { + String trimmedText = ""; + if (text != null) { + int trimEnd = Math.min(text.length(), 50); + trimmedText = text.subSequence(0,trimEnd) + (text.length() > 50 ? "..." : ""); + } + return trimmedText; + } + private void sendSingleThreadNotification(@NonNull Context context, @NonNull NotificationState notificationState, boolean signal, boolean bundled) @@ -331,11 +341,14 @@ public class DefaultMessageNotifier implements MessageNotifier { builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag); + CharSequence text = notifications.get(0).getText(); + String trimmedText = getTrimmedText(text); + builder.setThread(notifications.get(0).getRecipient()); builder.setMessageCount(notificationState.getMessageCount()); MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(notifications.get(0).getThreadId(),context); builder.setPrimaryMessageBody(recipient, notifications.get(0).getIndividualRecipient(), - MentionUtilities.highlightMentions(notifications.get(0).getText(), + MentionUtilities.highlightMentions(trimmedText, notifications.get(0).getThreadId(), context), notifications.get(0).getSlideDeck()); @@ -435,8 +448,8 @@ public class DefaultMessageNotifier implements MessageNotifier { builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag); Notification notification = builder.build(); - NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, builder.build()); - Log.i(TAG, "Posted notification. " + notification.toString()); + NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, notification); + Log.i(TAG, "Posted notification. " + notification); } private void sendInThreadNotification(Context context, Recipient recipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 03da0f378d..81332e87d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -93,7 +93,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu } if (privacy.isDisplayContact() && sender.getContactUri() != null) { - addPerson(sender.getContactUri().toString()); +// addPerson(sender.getContactUri().toString()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 68b69344b2..61c126339d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.notifications; import android.annotation.TargetApi; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -44,14 +45,15 @@ public class NotificationChannels { private static final String TAG = NotificationChannels.class.getSimpleName(); private static final int VERSION_MESSAGES_CATEGORY = 2; + private static final int VERSION_SESSION_CALLS = 3; - private static final int VERSION = 2; + private static final int VERSION = 3; private static final String CATEGORY_MESSAGES = "messages"; private static final String CONTACT_PREFIX = "contact_"; private static final String MESSAGES_PREFIX = "messages_"; - public static final String CALLS = "calls_v2"; + public static final String CALLS = "calls_v3"; public static final String FAILURES = "failures"; public static final String APP_UPDATES = "app_updates"; public static final String BACKUPS = "backups_v2"; @@ -427,7 +429,7 @@ public class NotificationChannels { notificationManager.createNotificationChannelGroup(messagesGroup); NotificationChannel messages = new NotificationChannel(getMessagesChannel(context), context.getString(R.string.NotificationChannel_messages), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_LOW); + NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_HIGH); NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.NotificationChannel_failures), NotificationManager.IMPORTANCE_HIGH); NotificationChannel backups = new NotificationChannel(BACKUPS, context.getString(R.string.NotificationChannel_backups), NotificationManager.IMPORTANCE_LOW); NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.NotificationChannel_locked_status), NotificationManager.IMPORTANCE_LOW); @@ -439,6 +441,7 @@ public class NotificationChannels { setLedPreference(messages, TextSecurePreferences.getNotificationLedColor(context)); calls.setShowBadge(false); + calls.setSound(null, null); backups.setShowBadge(false); lockedStatus.setShowBadge(false); other.setShowBadge(false); @@ -463,6 +466,8 @@ public class NotificationChannels { notificationManager.deleteNotificationChannel("locked_status"); notificationManager.deleteNotificationChannel("backups"); notificationManager.deleteNotificationChannel("other"); + } if (oldVersion < VERSION_SESSION_CALLS) { + notificationManager.deleteNotificationChannel("calls_v2"); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index db93b739ae..acac47e568 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -295,11 +295,11 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil .asBitmap() .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) - .submit(500, 500) + .submit(64, 64) .get(); } catch (InterruptedException | ExecutionException e) { Log.w(TAG, e); - return Bitmap.createBitmap(500, 500, Bitmap.Config.RGB_565); + return Bitmap.createBitmap(64, 64, Bitmap.Config.RGB_565); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index 2e9e6b846e..5c42f38bcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -1,22 +1,33 @@ package org.thoughtcrime.securesms.preferences; +import android.Manifest; import android.app.Activity; +import android.app.AlertDialog; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import androidx.preference.Preference; import org.session.libsession.utilities.TextSecurePreferences; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.CallNotificationBuilder; +import org.thoughtcrime.securesms.util.IntentUtils; import java.util.concurrent.TimeUnit; +import kotlin.jvm.functions.Function1; import mobi.upod.timedurationpicker.TimeDurationPickerDialog; +import network.loki.messenger.BuildConfig; import network.loki.messenger.R; public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment { @@ -36,10 +47,51 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener()); this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener()); this.findPreference(TextSecurePreferences.LINK_PREVIEWS).setOnPreferenceChangeListener(new LinkPreviewToggleListener()); + this.findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED).setOnPreferenceChangeListener(new CallToggleListener(this, this::setCall)); initializeVisibility(); } + private Void setCall(boolean isEnabled) { + ((SwitchPreferenceCompat)findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)).setChecked(isEnabled); + if (isEnabled && !CallNotificationBuilder.areNotificationsEnabled(requireActivity())) { + // show a dialog saying that calls won't work properly if you don't have notifications on at a system level + new AlertDialog.Builder(requireActivity()) + .setTitle(R.string.CallNotificationBuilder_system_notification_title) + .setMessage(R.string.CallNotificationBuilder_system_notification_message) + .setPositiveButton(R.string.activity_notification_settings_title, (d, w) -> { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + Intent settingsIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID); + if (IntentUtils.isResolvable(requireContext(), settingsIntent)) { + startActivity(settingsIntent); + } + } else { + Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .setData(Uri.parse("package:"+BuildConfig.APPLICATION_ID)); + if (IntentUtils.isResolvable(requireContext(), settingsIntent)) { + startActivity(settingsIntent); + } + } + d.dismiss(); + }) + .setNeutralButton(R.string.dismiss, (d, w) -> { + // do nothing, user might have broken notifications + d.dismiss(); + }) + .show(); + } + return null; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); + } + @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences_app_protection); @@ -136,4 +188,52 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment return true; } } + + private class CallToggleListener implements Preference.OnPreferenceChangeListener { + + private final Fragment context; + private final Function1 setCallback; + + private CallToggleListener(Fragment context, Function1 setCallback) { + this.context = context; + this.setCallback = setCallback; + } + + private void requestMicrophonePermission() { + Permissions.with(context) + .request(Manifest.permission.RECORD_AUDIO) + .onAllGranted(() -> { + TextSecurePreferences.setBooleanPreference(context.requireContext(), TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED, true); + setCallback.invoke(true); + }) + .onAnyDenied(() -> setCallback.invoke(false)) + .execute(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean val = (boolean) newValue; + if (val) { + // check if we've shown the info dialog and check for microphone permissions + if (TextSecurePreferences.setShownCallWarning(context.requireContext())) { + new AlertDialog.Builder(context.requireContext()) + .setTitle(R.string.dialog_voice_video_title) + .setMessage(R.string.dialog_voice_video_message) + .setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> { + requestMicrophonePermission(); + }) + .setNegativeButton(R.string.cancel, (d, w) -> { + + }) + .show(); + } else { + requestMicrophonePermission(); + } + return false; + } else { + return true; + } + } + } + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt new file mode 100644 index 0000000000..079d7a21ad --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -0,0 +1,861 @@ +package org.thoughtcrime.securesms.service + +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.IntentFilter +import android.media.AudioManager +import android.os.IBinder +import android.os.ResultReceiver +import android.telephony.PhoneStateListener +import android.telephony.TelephonyManager +import androidx.core.os.bundleOf +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.FutureTaskListener +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.calls.WebRtcCallActivity +import org.thoughtcrime.securesms.util.CallNotificationBuilder +import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED +import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING +import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER +import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING +import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING +import org.thoughtcrime.securesms.webrtc.AudioManagerCommand +import org.thoughtcrime.securesms.webrtc.CallManager +import org.thoughtcrime.securesms.webrtc.CallViewModel +import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener +import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver +import org.thoughtcrime.securesms.webrtc.NetworkChangeReceiver +import org.thoughtcrime.securesms.webrtc.PeerConnectionException +import org.thoughtcrime.securesms.webrtc.PowerButtonReceiver +import org.thoughtcrime.securesms.webrtc.ProximityLockRelease +import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager +import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver +import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger +import org.thoughtcrime.securesms.webrtc.data.Event +import org.thoughtcrime.securesms.webrtc.locks.LockManager +import org.webrtc.DataChannel +import org.webrtc.IceCandidate +import org.webrtc.MediaStream +import org.webrtc.PeerConnection +import org.webrtc.PeerConnection.IceConnectionState.CONNECTED +import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED +import org.webrtc.PeerConnection.IceConnectionState.FAILED +import org.webrtc.RtpReceiver +import org.webrtc.SessionDescription +import java.util.UUID +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import org.thoughtcrime.securesms.webrtc.data.State as CallState + +@AndroidEntryPoint +class WebRtcCallService: Service(), CallManager.WebRtcListener { + + companion object { + + private val TAG = Log.tag(WebRtcCallService::class.java) + + const val ACTION_INCOMING_RING = "RING_INCOMING" + const val ACTION_OUTGOING_CALL = "CALL_OUTGOING" + const val ACTION_ANSWER_CALL = "ANSWER_CALL" + const val ACTION_DENY_CALL = "DENY_CALL" + const val ACTION_LOCAL_HANGUP = "LOCAL_HANGUP" + const val ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO" + const val ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO" + const val ACTION_FLIP_CAMERA = "FLIP_CAMERA" + const val ACTION_UPDATE_AUDIO = "UPDATE_AUDIO" + const val ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE" + const val ACTION_SCREEN_OFF = "SCREEN_OFF" + const val ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT" + const val ACTION_CHECK_RECONNECT = "CHECK_RECONNECT" + const val ACTION_CHECK_RECONNECT_TIMEOUT = "CHECK_RECONNECT_TIMEOUT" + const val ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL" + const val ACTION_WANTS_TO_ANSWER = "WANTS_TO_ANSWER" + + const val ACTION_PRE_OFFER = "PRE_OFFER" + const val ACTION_RESPONSE_MESSAGE = "RESPONSE_MESSAGE" + const val ACTION_ICE_MESSAGE = "ICE_MESSAGE" + const val ACTION_REMOTE_HANGUP = "REMOTE_HANGUP" + const val ACTION_ICE_CONNECTED = "ICE_CONNECTED" + + const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID" + const val EXTRA_ENABLED = "ENABLED" + const val EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND" + const val EXTRA_MUTE = "mute_value" + const val EXTRA_AVAILABLE = "enabled_value" + const val EXTRA_REMOTE_DESCRIPTION = "remote_description" + const val EXTRA_TIMESTAMP = "timestamp" + const val EXTRA_CALL_ID = "call_id" + const val EXTRA_ICE_SDP = "ice_sdp" + const val EXTRA_ICE_SDP_MID = "ice_sdp_mid" + const val EXTRA_ICE_SDP_LINE_INDEX = "ice_sdp_line_index" + const val EXTRA_RESULT_RECEIVER = "result_receiver" + const val EXTRA_WANTS_TO_ANSWER = "wants_to_answer" + + const val INVALID_NOTIFICATION_ID = -1 + private const val TIMEOUT_SECONDS = 30L + private const val RECONNECT_SECONDS = 5L + private const val MAX_RECONNECTS = 5 + + fun cameraEnabled(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_SET_MUTE_VIDEO) + .putExtra(EXTRA_MUTE, !enabled) + + fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_FLIP_CAMERA) + + fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_ANSWER_CALL) + + fun microphoneIntent(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_SET_MUTE_AUDIO) + .putExtra(EXTRA_MUTE, !enabled) + + fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_OUTGOING_CALL) + .putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address) + + fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_INCOMING_RING) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) + .putExtra(EXTRA_TIMESTAMP, callTime) + + fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_RESPONSE_MESSAGE) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) + + fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_PRE_OFFER) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_TIMESTAMP, callTime) + + fun iceCandidates(context: Context, address: Address, iceCandidates: List, callId: UUID) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_ICE_MESSAGE) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray()) + .putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()) + .putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray()) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + + fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL) + + fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_REMOTE_HANGUP) + .putExtra(EXTRA_CALL_ID, callId) + + fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP) + + fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_UPDATE_AUDIO) + .putExtra(EXTRA_AUDIO_COMMAND, command) + context.startService(intent) + } + + fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) { + val intent = Intent(ACTION_WANTS_TO_ANSWER) + .putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer) + + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } + + @JvmStatic + fun isCallActive(context: Context, resultReceiver: ResultReceiver) { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_IS_IN_CALL_QUERY) + .putExtra(EXTRA_RESULT_RECEIVER, resultReceiver) + context.startService(intent) + } + } + + @Inject lateinit var callManager: CallManager + + private var wantsToAnswer = false + private var currentTimeouts = 0 + private var isNetworkAvailable = true + private var scheduledTimeout: ScheduledFuture<*>? = null + private var scheduledReconnect: ScheduledFuture<*>? = null + + private val lockManager by lazy { LockManager(this) } + private val serviceExecutor = Executors.newSingleThreadExecutor() + private val timeoutExecutor = Executors.newScheduledThreadPool(1) + private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener { + startService(hangupIntent(this)) + } + + private var networkChangedReceiver: NetworkChangeReceiver? = null + private var callReceiver: IncomingPstnCallReceiver? = null + private var wantsToAnswerReceiver: BroadcastReceiver? = null + private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null + private var uncaughtExceptionHandlerManager: UncaughtExceptionHandlerManager? = null + private var powerButtonReceiver: PowerButtonReceiver? = null + + @Synchronized + private fun terminate() { + LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(WebRtcCallActivity.ACTION_END)) + lockManager.updatePhoneState(LockManager.PhoneState.IDLE) + callManager.stop() + wantsToAnswer = false + currentTimeouts = 0 + isNetworkAvailable = true + scheduledTimeout?.cancel(false) + scheduledReconnect?.cancel(false) + scheduledTimeout = null + scheduledReconnect = null + stopForeground(true) + } + + private fun isSameCall(intent: Intent): Boolean { + val expectedCallId = getCallId(intent) + return callManager.callId == expectedCallId + } + + + private fun isPreOffer() = callManager.isPreOffer() + + private fun isBusy(intent: Intent) = callManager.isBusy(this, getCallId(intent)) + + private fun isIdle() = callManager.isIdle() + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onHangup() { + serviceExecutor.execute { + callManager.handleRemoteHangup() + + if (callManager.currentConnectionState in CallState.CAN_DECLINE_STATES) { + callManager.recipient?.let { recipient -> + insertMissedCall(recipient, true) + } + } + terminate() + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent == null || intent.action == null) return START_NOT_STICKY + serviceExecutor.execute { + val action = intent.action + Log.i("Loki", "Handling ${intent.action}") + when { + action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(intent) + action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent) + action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent) + action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent) + action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent) + action == ACTION_ANSWER_CALL -> handleAnswerCall(intent) + action == ACTION_DENY_CALL -> handleDenyCall(intent) + action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent) + action == ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent) + action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent) + action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent) + action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent) + action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent) + action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent) + action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage(intent) + action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent) + action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent) + action == ACTION_ICE_CONNECTED -> handleIceConnected(intent) + action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent) + action == ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent) + action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent) + action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent) + } + } + return START_NOT_STICKY + } + + override fun onCreate() { + super.onCreate() + callManager.registerListener(this) + wantsToAnswer = false + isNetworkAvailable = true + registerIncomingPstnCallReceiver() + registerWiredHeadsetStateReceiver() + registerWantsToAnswerReceiver() + getSystemService(TelephonyManager::class.java) + .listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE) + registerUncaughtExceptionHandler() + networkChangedReceiver = NetworkChangeReceiver(::networkChange) + networkChangedReceiver!!.register(this) + } + + private fun registerUncaughtExceptionHandler() { + uncaughtExceptionHandlerManager = UncaughtExceptionHandlerManager().apply { + registerHandler(ProximityLockRelease(lockManager)) + } + } + + private fun registerIncomingPstnCallReceiver() { + callReceiver = IncomingPstnCallReceiver() + registerReceiver(callReceiver, IntentFilter("android.intent.action.PHONE_STATE")) + } + + private fun registerWantsToAnswerReceiver() { + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + wantsToAnswer = intent?.getBooleanExtra(EXTRA_WANTS_TO_ANSWER, false) ?: false + } + } + wantsToAnswerReceiver = receiver + LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER)) + } + + private fun registerWiredHeadsetStateReceiver() { + wiredHeadsetStateReceiver = WiredHeadsetStateReceiver() + registerReceiver(wiredHeadsetStateReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) + } + + private fun handleBusyCall(intent: Intent) { + val recipient = getRemoteRecipient(intent) + val callState = callManager.currentConnectionState + + insertMissedCall(recipient, false) + + if (callState == CallState.Idle) { + stopForeground(true) + } + } + + private fun handleUpdateAudio(intent: Intent) { + val audioCommand = intent.getParcelableExtra(EXTRA_AUDIO_COMMAND)!! + if (callManager.currentConnectionState !in arrayOf(CallState.Connected, *CallState.PENDING_CONNECTION_STATES)) { + Log.w(TAG, "handling audio command not in call") + return + } + callManager.handleAudioCommand(audioCommand) + } + + private fun handleNewOffer(intent: Intent) { + val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return + val callId = getCallId(intent) + val recipient = getRemoteRecipient(intent) + callManager.onNewOffer(offer, callId, recipient).fail { + Log.e("Loki", "Error handling new offer", it) + callManager.postConnectionError() + terminate() + } + } + + private fun handlePreOffer(intent: Intent) { + if (!callManager.isIdle()) { + Log.w(TAG, "Handling pre-offer from non-idle state") + return + } + val callId = getCallId(intent) + val recipient = getRemoteRecipient(intent) + + if (isIncomingMessageExpired(intent)) { + insertMissedCall(recipient, true) + terminate() + return + } + + callManager.onPreOffer(callId, recipient) { + setCallInProgressNotification(TYPE_INCOMING_PRE_OFFER, recipient) + callManager.postViewModelState(CallViewModel.State.CALL_PRE_INIT) + callManager.initializeAudioForCall() + callManager.startIncomingRinger() + callManager.setAudioEnabled(true) + } + } + + private fun handleIncomingRing(intent: Intent) { + val callId = getCallId(intent) + val recipient = getRemoteRecipient(intent) + val preOffer = callManager.preOfferCallData + + if (callManager.isPreOffer() && (preOffer == null || preOffer.callId != callId || preOffer.recipient != recipient)) { + Log.d(TAG, "Incoming ring from non-matching pre-offer") + return + } + + val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return + val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1) + + callManager.onIncomingRing(offer, callId, recipient, timestamp) { + if (wantsToAnswer) { + setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient) + } else { + setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient) + } + callManager.clearPendingIceUpdates() + callManager.postViewModelState(CallViewModel.State.CALL_RINGING) + registerPowerButtonReceiver() + } + } + + private fun handleOutgoingCall(intent: Intent) { + callManager.postConnectionEvent(Event.SendPreOffer) { + val recipient = getRemoteRecipient(intent) + callManager.recipient = recipient + val callId = UUID.randomUUID() + callManager.callId = callId + + callManager.initializeVideo(this) + + callManager.postViewModelState(CallViewModel.State.CALL_OUTGOING) + lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL) + callManager.initializeAudioForCall() + callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING) + setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient) + callManager.insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_OUTGOING) + scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + callManager.setAudioEnabled(true) + + val expectedState = callManager.currentConnectionState + val expectedCallId = callManager.callId + + try { + val offerFuture = callManager.onOutgoingCall(this) + offerFuture.fail { e -> + if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) { + Log.e(TAG,e) + callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE) + callManager.postConnectionError() + terminate() + } + } + } catch (e: Exception) { + Log.e(TAG,e) + callManager.postConnectionError() + terminate() + } + } + } + + private fun handleAnswerCall(intent: Intent) { + val recipient = callManager.recipient ?: return + val pending = callManager.pendingOffer ?: return + val callId = callManager.callId ?: return + val timestamp = callManager.pendingOfferTime + + if (callManager.currentConnectionState != CallState.RemoteRing) { + Log.e(TAG, "Can only answer from ringing!") + return + } + + intent.putExtra(EXTRA_CALL_ID, callId) + intent.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address) + intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending) + intent.putExtra(EXTRA_TIMESTAMP, timestamp) + + if (isIncomingMessageExpired(intent)) { + val didHangup = callManager.postConnectionEvent(Event.TimeOut) { + insertMissedCall(recipient, true) + terminate() + } + if (didHangup) { + return + } + } + + callManager.postConnectionEvent(Event.SendAnswer) { + setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient) + + callManager.silenceIncomingRinger() + callManager.postViewModelState(CallViewModel.State.CALL_INCOMING) + + scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + + callManager.initializeAudioForCall() + callManager.initializeVideo(this) + + val expectedState = callManager.currentConnectionState + val expectedCallId = callManager.callId + + try { + val answerFuture = callManager.onIncomingCall(this) + answerFuture.fail { e -> + if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) { + Log.e(TAG, e) + insertMissedCall(recipient, true) + callManager.postConnectionError() + terminate() + } + } + lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING) + callManager.setAudioEnabled(true) + } catch (e: Exception) { + Log.e(TAG,e) + callManager.postConnectionError() + terminate() + } + } + } + + private fun handleDenyCall(intent: Intent) { + callManager.handleDenyCall() + terminate() + } + + private fun handleLocalHangup(intent: Intent) { + val intentRecipient = getOptionalRemoteRecipient(intent) + callManager.handleLocalHangup(intentRecipient) + terminate() + } + + private fun handleRemoteHangup(intent: Intent) { + if (callManager.callId != getCallId(intent)) { + Log.e(TAG, "Hangup for non-active call...") + return + } + + onHangup() + } + + private fun handleSetMuteAudio(intent: Intent) { + val muted = intent.getBooleanExtra(EXTRA_MUTE, false) + callManager.handleSetMuteAudio(muted) + } + + private fun handleSetMuteVideo(intent: Intent) { + val muted = intent.getBooleanExtra(EXTRA_MUTE, false) + callManager.handleSetMuteVideo(muted, lockManager) + } + + private fun handleSetCameraFlip(intent: Intent) { + callManager.handleSetCameraFlip() + } + + private fun handleWiredHeadsetChanged(intent: Intent) { + callManager.handleWiredHeadsetChanged(intent.getBooleanExtra(EXTRA_AVAILABLE, false)) + } + + private fun handleScreenOffChange(intent: Intent) { + callManager.handleScreenOffChange() + } + + private fun handleResponseMessage(intent: Intent) { + try { + val recipient = getRemoteRecipient(intent) + if (callManager.isCurrentUser(recipient) && callManager.currentConnectionState in CallState.CAN_DECLINE_STATES) { + handleLocalHangup(intent) + return + } + val callId = getCallId(intent) + val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) + callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, description)) + } catch (e: PeerConnectionException) { + terminate() + } + } + + private fun handleRemoteIceCandidate(intent: Intent) { + val callId = getCallId(intent) + val sdpMids = intent.getStringArrayExtra(EXTRA_ICE_SDP_MID) ?: return + val sdpLineIndexes = intent.getIntArrayExtra(EXTRA_ICE_SDP_LINE_INDEX) ?: return + val sdps = intent.getStringArrayExtra(EXTRA_ICE_SDP) ?: return + if (sdpMids.size != sdpLineIndexes.size || sdpLineIndexes.size != sdps.size) { + Log.w(TAG,"sdp info not of equal length") + return + } + val iceCandidates = sdpMids.indices.map { index -> + IceCandidate( + sdpMids[index], + sdpLineIndexes[index], + sdps[index] + ) + } + callManager.handleRemoteIceCandidate(iceCandidates, callId) + } + + private fun handleIceConnected(intent: Intent) { + val recipient = callManager.recipient ?: return + val connected = callManager.postConnectionEvent(Event.Connect) { + callManager.postViewModelState(CallViewModel.State.CALL_CONNECTED) + setCallInProgressNotification(TYPE_ESTABLISHED, recipient) + callManager.startCommunication(lockManager) + } + if (!connected) { + Log.e("Loki", "Error handling ice connected state transition") + callManager.postConnectionError() + terminate() + } + } + + private fun handleIsInCallQuery(intent: Intent) { + val listener = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER) ?: return + val currentState = callManager.currentConnectionState + val isInCall = if (currentState in arrayOf(*CallState.PENDING_CONNECTION_STATES, CallState.Connected)) 1 else 0 + listener.send(isInCall, bundleOf()) + } + + private fun registerPowerButtonReceiver() { + if (powerButtonReceiver == null) { + powerButtonReceiver = PowerButtonReceiver() + + registerReceiver(powerButtonReceiver, IntentFilter(Intent.ACTION_SCREEN_OFF)) + } + } + + private fun handleCheckReconnect(intent: Intent) { + val callId = callManager.callId ?: return + val numTimeouts = ++currentTimeouts + + if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) { + Log.i("Loki", "Trying to re-connect") + callManager.networkReestablished() + scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + } else if (numTimeouts < MAX_RECONNECTS) { + Log.i("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS") + scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS) + } else { + Log.i("Loki", "Network isn't available, timing out") + handleLocalHangup(intent) + } + } + + + + private fun handleCheckTimeout(intent: Intent) { + val callId = callManager.callId ?: return + val callState = callManager.currentConnectionState + + if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) { + Log.w(TAG, "Timing out call: $callId") + handleLocalHangup(intent) + } + } + + private fun setCallInProgressNotification(type: Int, recipient: Recipient?) { + startForeground( + CallNotificationBuilder.WEBRTC_NOTIFICATION, + CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) + ) + if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) { + // start an intent for the fullscreen + val foregroundIntent = Intent(this, WebRtcCallActivity::class.java) + .setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_BROUGHT_TO_FRONT) + .setAction(WebRtcCallActivity.ACTION_FULL_SCREEN_INTENT) + startActivity(foregroundIntent) + } + } + + private fun getOptionalRemoteRecipient(intent: Intent): Recipient? = + if (intent.hasExtra(EXTRA_RECIPIENT_ADDRESS)) { + getRemoteRecipient(intent) + } else { + null + } + + private fun getRemoteRecipient(intent: Intent): Recipient { + val remoteAddress = intent.getParcelableExtra
(EXTRA_RECIPIENT_ADDRESS) + ?: throw AssertionError("No recipient in intent!") + + return Recipient.from(this, remoteAddress, true) + } + + private fun getCallId(intent: Intent) : UUID { + return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID + ?: throw AssertionError("No callId in intent!") + } + + private fun insertMissedCall(recipient: Recipient, signal: Boolean) { + callManager.insertCallMessage( + threadPublicKey = recipient.address.serialize(), + callMessageType = CallMessageType.CALL_MISSED, + signal = signal + ) + } + + private fun isIncomingMessageExpired(intent: Intent) = + System.currentTimeMillis() - intent.getLongExtra(EXTRA_TIMESTAMP, -1) > TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS) + + override fun onDestroy() { + Log.d(TAG,"onDestroy()") + callManager.unregisterListener(this) + callReceiver?.let { receiver -> + unregisterReceiver(receiver) + } + networkChangedReceiver?.unregister(this) + wantsToAnswerReceiver?.let { receiver -> + LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) + } + networkChangedReceiver = null + callReceiver = null + uncaughtExceptionHandlerManager?.unregister() + wantsToAnswer = false + currentTimeouts = 0 + isNetworkAvailable = false + super.onDestroy() + } + + private fun networkChange(networkAvailable: Boolean) { + Log.d("Loki", "flipping network available to $networkAvailable") + isNetworkAvailable = networkAvailable + if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState == CallState.Connected) { + Log.d("Loki", "Should reconnected") + } + } + + private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context): Runnable { + override fun run() { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_CHECK_RECONNECT) + .putExtra(EXTRA_CALL_ID, callId) + context.startService(intent) + } + } + + private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context): Runnable { + override fun run() { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_CHECK_RECONNECT_TIMEOUT) + .putExtra(EXTRA_CALL_ID, callId) + context.startService(intent) + } + } + + private class TimeoutRunnable(private val callId: UUID, private val context: Context): Runnable { + override fun run() { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_CHECK_TIMEOUT) + .putExtra(EXTRA_CALL_ID, callId) + context.startService(intent) + } + } + + private abstract class FailureListener( + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + override fun onSuccessContinue(result: V) {} + } + + private abstract class SuccessOnlyListener( + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + override fun onFailureContinue(throwable: Throwable?) { + Log.e(TAG, throwable) + throw AssertionError(throwable) + } + } + + private abstract class StateAwareListener( + private val expectedState: CallState, + private val expectedCallId: UUID?, + private val getState: ()->Pair): FutureTaskListener { + + companion object { + private val TAG = Log.tag(StateAwareListener::class.java) + } + + override fun onSuccess(result: V) { + if (!isConsistentState()) { + Log.w(TAG,"State has changed since request, aborting success callback...") + } else { + onSuccessContinue(result) + } + } + + override fun onFailure(exception: ExecutionException?) { + if (!isConsistentState()) { + Log.w(TAG, exception) + Log.w(TAG,"State has changed since request, aborting failure callback...") + } else { + exception?.let { + onFailureContinue(it.cause) + } + } + } + + private fun isConsistentState(): Boolean { + val (currentState, currentCallId) = getState() + return expectedState == currentState && expectedCallId == currentCallId + } + + abstract fun onSuccessContinue(result: V) + abstract fun onFailureContinue(throwable: Throwable?) + + } + + private fun isConsistentState( + expectedState: CallState, + expectedCallId: UUID?, + currentState: CallState, + currentCallId: UUID? + ): Boolean { + return expectedState == currentState && expectedCallId == currentCallId + } + + override fun onSignalingChange(p0: PeerConnection.SignalingState?) {} + + override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) { + newState?.let { state -> processIceConnectionChange(state) } + } + + private fun processIceConnectionChange(newState: PeerConnection.IceConnectionState) { + serviceExecutor.execute { + if (newState == CONNECTED) { + scheduledTimeout?.cancel(false) + scheduledReconnect?.cancel(false) + scheduledTimeout = null + scheduledReconnect = null + + val intent = Intent(this, WebRtcCallService::class.java) + .setAction(ACTION_ICE_CONNECTED) + startService(intent) + } else if (newState in arrayOf(FAILED, DISCONNECTED) && (scheduledReconnect == null && scheduledTimeout == null)) { + callManager.callId?.let { callId -> + callManager.postConnectionEvent(Event.IceDisconnect) { + callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING) + if (callManager.isInitiator()) { + Log.i("Loki", "Starting reconnect timer") + scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS) + } else { + Log.i("Loki", "Starting timeout, awaiting new reconnect") + callManager.postConnectionEvent(Event.PrepareForNewOffer) { + scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + } + } + } + } ?: run { + val intent = hangupIntent(this) + startService(intent) + } + } + Log.i("Loki", "onIceConnectionChange: $newState") + } + } + + override fun onIceConnectionReceivingChange(p0: Boolean) {} + + override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {} + + override fun onIceCandidate(p0: IceCandidate?) {} + + override fun onIceCandidatesRemoved(p0: Array?) {} + + override fun onAddStream(p0: MediaStream?) {} + + override fun onRemoveStream(p0: MediaStream?) {} + + override fun onDataChannel(p0: DataChannel?) {} + + override fun onRenegotiationNeeded() { + Log.w(TAG,"onRenegotiationNeeded was called!") + } + + override fun onAddTrack(p0: RtpReceiver?, p1: Array?) {} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt new file mode 100644 index 0000000000..56c0a55dda --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt @@ -0,0 +1,163 @@ +package org.thoughtcrime.securesms.util + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.os.Build +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.calls.WebRtcCallActivity +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.preferences.SettingsActivity +import org.thoughtcrime.securesms.service.WebRtcCallService + +class CallNotificationBuilder { + + companion object { + const val WEBRTC_NOTIFICATION = 313388 + + const val TYPE_INCOMING_RINGING = 1 + const val TYPE_OUTGOING_RINGING = 2 + const val TYPE_ESTABLISHED = 3 + const val TYPE_INCOMING_CONNECTING = 4 + const val TYPE_INCOMING_PRE_OFFER = 5 + + @JvmStatic + fun areNotificationsEnabled(context: Context): Boolean { + val notificationManager = NotificationManagerCompat.from(context) + return when { + !notificationManager.areNotificationsEnabled() -> false + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> { + notificationManager.notificationChannels.firstOrNull { channel -> + channel.importance == NotificationManager.IMPORTANCE_NONE + } == null + } + else -> true + } + } + + @JvmStatic + fun getFirstCallNotification(context: Context): Notification { + val contentIntent = Intent(context, SettingsActivity::class.java) + + val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + val text = context.getString(R.string.CallNotificationBuilder_first_call_message) + + val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS) + .setSound(null) + .setSmallIcon(R.drawable.ic_baseline_call_24) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentTitle(context.getString(R.string.CallNotificationBuilder_first_call_title)) + .setContentText(text) + .setStyle(NotificationCompat.BigTextStyle().bigText(text)) + .setAutoCancel(true) + + return builder.build() + } + + @JvmStatic + fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification { + val contentIntent = Intent(context, WebRtcCallActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + + val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS) + .setSound(null) + .setSmallIcon(R.drawable.ic_baseline_call_24) + .setContentIntent(pendingIntent) + .setOngoing(true) + + + recipient?.name?.let { name -> + builder.setContentTitle(name) + } + + when (type) { + TYPE_INCOMING_CONNECTING -> { + builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting)) + .setNotificationSilent() + } + TYPE_INCOMING_PRE_OFFER, + TYPE_INCOMING_RINGING -> { + builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call)) + .setCategory(NotificationCompat.CATEGORY_CALL) + builder.addAction(getServiceNotificationAction( + context, + WebRtcCallService.ACTION_DENY_CALL, + R.drawable.ic_close_grey600_32dp, + R.string.NotificationBarManager__deny_call + )) + // if notifications aren't enabled, we will trigger the intent from WebRtcCallService + builder.setFullScreenIntent(getFullScreenPendingIntent( + context + ), true) + builder.addAction(getActivityNotificationAction( + context, + if (type == TYPE_INCOMING_PRE_OFFER) WebRtcCallActivity.ACTION_PRE_OFFER else WebRtcCallActivity.ACTION_ANSWER, + R.drawable.ic_phone_grey600_32dp, + R.string.NotificationBarManager__answer_call + )) + builder.priority = NotificationCompat.PRIORITY_MAX + } + 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 + )) + } + else -> { + builder.setContentText(context.getString(R.string.NotificationBarManager_call_in_progress)) + builder.addAction(getServiceNotificationAction( + context, + WebRtcCallService.ACTION_LOCAL_HANGUP, + R.drawable.ic_call_end_grey600_32dp, + R.string.NotificationBarManager__end_call + )).setUsesChronometer(true) + } + } + + return builder.build() + } + + private fun getServiceNotificationAction(context: Context, action: String, iconResId: Int, titleResId: Int): NotificationCompat.Action { + val intent = Intent(context, WebRtcCallService::class.java) + .setAction(action) + + val pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + return NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent) + } + + private fun getFullScreenPendingIntent(context: Context): PendingIntent { + val intent = Intent(context, WebRtcCallActivity::class.java) + .setFlags(FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) + .setAction(WebRtcCallActivity.ACTION_FULL_SCREEN_INTENT) + + return PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) + } + + private fun getActivityNotificationAction(context: Context, action: String, + @DrawableRes iconResId: Int, @StringRes titleResId: Int): NotificationCompat.Action { + val intent = Intent(context, WebRtcCallActivity::class.java) + .setAction(action) + + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + return NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioEvent.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioEvent.kt new file mode 100644 index 0000000000..b01edfb492 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioEvent.kt @@ -0,0 +1,5 @@ +package org.thoughtcrime.securesms.webrtc + +enum class AudioEvent { + +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioManagerCommand.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioManagerCommand.kt new file mode 100644 index 0000000000..c6dd6525e9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/AudioManagerCommand.kt @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.webrtc + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager + +@Parcelize +open class AudioManagerCommand: Parcelable { + @Parcelize + object Initialize: AudioManagerCommand() + + @Parcelize + object UpdateAudioDeviceState: AudioManagerCommand() + + @Parcelize + data class StartOutgoingRinger(val type: OutgoingRinger.Type): AudioManagerCommand() + + @Parcelize + object SilenceIncomingRinger: AudioManagerCommand() + + @Parcelize + object Start: AudioManagerCommand() + + @Parcelize + data class Stop(val playDisconnect: Boolean): AudioManagerCommand() + + @Parcelize + data class StartIncomingRinger(val vibrate: Boolean): AudioManagerCommand() + + @Parcelize + data class SetUserDevice(val device: SignalAudioManager.AudioDevice): AudioManagerCommand() + + @Parcelize + data class SetDefaultDevice(val device: SignalAudioManager.AudioDevice, + val clearUserEarpieceSelection: Boolean): AudioManagerCommand() +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt new file mode 100644 index 0000000000..006da2b63e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -0,0 +1,718 @@ +package org.thoughtcrime.securesms.webrtc + +import android.content.Context +import android.telephony.TelephonyManager +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.bind +import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.messaging.messages.control.CallMessage +import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Debouncer +import org.session.libsession.utilities.Util +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioDeviceUpdate +import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioEnabled +import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.RecipientUpdate +import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoEnabled +import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat +import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice +import org.thoughtcrime.securesms.webrtc.data.Event +import org.thoughtcrime.securesms.webrtc.data.StateProcessor +import org.thoughtcrime.securesms.webrtc.locks.LockManager +import org.thoughtcrime.securesms.webrtc.video.CameraEventListener +import org.thoughtcrime.securesms.webrtc.video.CameraState +import org.thoughtcrime.securesms.webrtc.video.RemoteRotationVideoProxySink +import org.webrtc.DataChannel +import org.webrtc.DefaultVideoDecoderFactory +import org.webrtc.DefaultVideoEncoderFactory +import org.webrtc.EglBase +import org.webrtc.IceCandidate +import org.webrtc.MediaConstraints +import org.webrtc.MediaStream +import org.webrtc.PeerConnection +import org.webrtc.PeerConnection.IceConnectionState +import org.webrtc.PeerConnectionFactory +import org.webrtc.RtpReceiver +import org.webrtc.SessionDescription +import org.webrtc.SurfaceViewRenderer +import java.nio.ByteBuffer +import java.util.ArrayDeque +import java.util.UUID +import org.thoughtcrime.securesms.webrtc.data.State as CallState + +class CallManager(context: Context, audioManager: AudioManagerCompat, private val storage: StorageProtocol): PeerConnection.Observer, + SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { + + sealed class StateEvent { + data class AudioEnabled(val isEnabled: Boolean): StateEvent() + data class VideoEnabled(val isEnabled: Boolean): StateEvent() + data class CallStateUpdate(val state: CallState): StateEvent() + data class AudioDeviceUpdate(val selectedDevice: AudioDevice, val audioDevices: Set): StateEvent() + data class RecipientUpdate(val recipient: Recipient?): StateEvent() { + companion object { + val UNKNOWN = RecipientUpdate(recipient = null) + } + } + } + + companion object { + val VIDEO_DISABLED_JSON by lazy { buildJsonObject { put("video", false) } } + val VIDEO_ENABLED_JSON by lazy { buildJsonObject { put("video", true) } } + val HANGUP_JSON by lazy { buildJsonObject { put("hangup", true) } } + + private val TAG = Log.tag(CallManager::class.java) + private const val DATA_CHANNEL_NAME = "signaling" + } + + private val signalAudioManager: SignalAudioManager = SignalAudioManager(context, this, audioManager) + + private val peerConnectionObservers = mutableSetOf() + + fun registerListener(listener: WebRtcListener) { + peerConnectionObservers.add(listener) + } + + fun unregisterListener(listener: WebRtcListener) { + peerConnectionObservers.remove(listener) + } + + private val _audioEvents = MutableStateFlow(AudioEnabled(false)) + val audioEvents = _audioEvents.asSharedFlow() + private val _videoEvents = MutableStateFlow(VideoEnabled(false)) + val videoEvents = _videoEvents.asSharedFlow() + private val _remoteVideoEvents = MutableStateFlow(VideoEnabled(false)) + val remoteVideoEvents = _remoteVideoEvents.asSharedFlow() + + private val stateProcessor = StateProcessor(CallState.Idle) + + private val _callStateEvents = MutableStateFlow(CallViewModel.State.CALL_PENDING) + val callStateEvents = _callStateEvents.asSharedFlow() + private val _recipientEvents = MutableStateFlow(RecipientUpdate.UNKNOWN) + val recipientEvents = _recipientEvents.asSharedFlow() + private var localCameraState: CameraState = CameraState.UNKNOWN + + private val _audioDeviceEvents = MutableStateFlow(AudioDeviceUpdate(AudioDevice.NONE, setOf())) + val audioDeviceEvents = _audioDeviceEvents.asSharedFlow() + + val currentConnectionState + get() = stateProcessor.currentState + + val currentCallState + get() = _callStateEvents.value + + var iceState = IceConnectionState.CLOSED + + private var eglBase: EglBase? = null + + var pendingOffer: String? = null + var pendingOfferTime: Long = -1 + var preOfferCallData: PreOffer? = null + var callId: UUID? = null + var recipient: Recipient? = null + set(value) { + field = value + _recipientEvents.value = RecipientUpdate(value) + } + var callStartTime: Long = -1 + var isReestablishing: Boolean = false + + private var peerConnection: PeerConnectionWrapper? = null + private var dataChannel: DataChannel? = null + + private val pendingOutgoingIceUpdates = ArrayDeque() + private val pendingIncomingIceUpdates = ArrayDeque() + + private val outgoingIceDebouncer = Debouncer(200L) + + var localRenderer: SurfaceViewRenderer? = null + var remoteRotationSink: RemoteRotationVideoProxySink? = null + var remoteRenderer: SurfaceViewRenderer? = null + private var peerConnectionFactory: PeerConnectionFactory? = null + + fun clearPendingIceUpdates() { + pendingOutgoingIceUpdates.clear() + pendingIncomingIceUpdates.clear() + } + + fun initializeAudioForCall() { + signalAudioManager.handleCommand(AudioManagerCommand.Initialize) + } + + fun startOutgoingRinger(ringerType: OutgoingRinger.Type) { + if (ringerType == OutgoingRinger.Type.RINGING) { + signalAudioManager.handleCommand(AudioManagerCommand.UpdateAudioDeviceState) + } + signalAudioManager.handleCommand(AudioManagerCommand.StartOutgoingRinger(ringerType)) + } + + fun silenceIncomingRinger() { + signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger) + } + + fun postConnectionEvent(transition: Event, onSuccess: ()->Unit): Boolean { + return stateProcessor.processEvent(transition, onSuccess) + } + + fun postConnectionError(): Boolean { + return stateProcessor.processEvent(Event.Error) + } + + fun postViewModelState(newState: CallViewModel.State) { + Log.d("Loki", "Posting view model state $newState") + _callStateEvents.value = newState + } + + fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.Idle + || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE) + + fun isPreOffer() = currentConnectionState == CallState.RemotePreOffer + + fun isIdle() = currentConnectionState == CallState.Idle + + fun isCurrentUser(recipient: Recipient) = recipient.address.serialize() == storage.getUserPublicKey() + + fun initializeVideo(context: Context) { + Util.runOnMainSync { + val base = EglBase.create() + eglBase = base + localRenderer = SurfaceViewRenderer(context).apply { +// setScalingType(SCALE_ASPECT_FIT) + } + + remoteRenderer = SurfaceViewRenderer(context).apply { +// setScalingType(SCALE_ASPECT_FIT) + } + remoteRotationSink = RemoteRotationVideoProxySink() + + + localRenderer?.init(base.eglBaseContext, null) + localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT) + remoteRenderer?.init(base.eglBaseContext, null) + remoteRotationSink!!.setSink(remoteRenderer!!) + + val encoderFactory = DefaultVideoEncoderFactory(base.eglBaseContext, true, true) + val decoderFactory = DefaultVideoDecoderFactory(base.eglBaseContext) + + peerConnectionFactory = PeerConnectionFactory.builder() + .setOptions(object: PeerConnectionFactory.Options() { + init { + networkIgnoreMask = 1 shl 4 + } + }) + .setVideoEncoderFactory(encoderFactory) + .setVideoDecoderFactory(decoderFactory) + .createPeerConnectionFactory() + } + } + + fun callEnded() { + peerConnection?.dispose() + peerConnection = null + } + + fun setAudioEnabled(isEnabled: Boolean) { + currentConnectionState.withState(*CallState.CAN_HANGUP_STATES) { + peerConnection?.setAudioEnabled(isEnabled) + _audioEvents.value = AudioEnabled(true) + } + } + + override fun onSignalingChange(newState: PeerConnection.SignalingState) { + peerConnectionObservers.forEach { listener -> listener.onSignalingChange(newState) } + } + + override fun onIceConnectionChange(newState: IceConnectionState) { + Log.d("Loki", "New ice connection state = $newState") + iceState = newState + peerConnectionObservers.forEach { listener -> listener.onIceConnectionChange(newState) } + if (newState == IceConnectionState.CONNECTED) { + callStartTime = System.currentTimeMillis() + } + } + + override fun onIceConnectionReceivingChange(receiving: Boolean) { + peerConnectionObservers.forEach { listener -> listener.onIceConnectionReceivingChange(receiving) } + } + + override fun onIceGatheringChange(newState: PeerConnection.IceGatheringState) { + peerConnectionObservers.forEach { listener -> listener.onIceGatheringChange(newState) } + } + + override fun onIceCandidate(iceCandidate: IceCandidate) { + peerConnectionObservers.forEach { listener -> listener.onIceCandidate(iceCandidate) } + val expectedCallId = this.callId ?: return + val expectedRecipient = this.recipient ?: return + pendingOutgoingIceUpdates.add(iceCandidate) + + if (peerConnection?.readyForIce != true) return + + queueOutgoingIce(expectedCallId, expectedRecipient) + } + + private fun queueOutgoingIce(expectedCallId: UUID, expectedRecipient: Recipient) { + outgoingIceDebouncer.publish { + val currentCallId = this.callId ?: return@publish + val currentRecipient = this.recipient ?: return@publish + if (currentCallId == expectedCallId && expectedRecipient == currentRecipient) { + val currentPendings = mutableSetOf() + while (pendingOutgoingIceUpdates.isNotEmpty()) { + currentPendings.add(pendingOutgoingIceUpdates.pop()) + } + val sdps = currentPendings.map { it.sdp } + val sdpMLineIndexes = currentPendings.map { it.sdpMLineIndex } + val sdpMids = currentPendings.map { it.sdpMid } + + MessageSender.sendNonDurably(CallMessage( + ICE_CANDIDATES, + sdps = sdps, + sdpMLineIndexes = sdpMLineIndexes, + sdpMids = sdpMids, + currentCallId + ), currentRecipient.address) + } + } + } + + override fun onIceCandidatesRemoved(candidates: Array?) { + peerConnectionObservers.forEach { listener -> listener.onIceCandidatesRemoved(candidates) } + } + + override fun onAddStream(stream: MediaStream) { + peerConnectionObservers.forEach { listener -> listener.onAddStream(stream) } + for (track in stream.audioTracks) { + track.setEnabled(true) + } + + if (stream.videoTracks != null && stream.videoTracks.size == 1) { + val videoTrack = stream.videoTracks.first() + videoTrack.setEnabled(true) + videoTrack.addSink(remoteRotationSink) + } + } + + override fun onRemoveStream(p0: MediaStream?) { + peerConnectionObservers.forEach { listener -> listener.onRemoveStream(p0) } + } + + override fun onDataChannel(p0: DataChannel?) { + peerConnectionObservers.forEach { listener -> listener.onDataChannel(p0) } + } + + override fun onRenegotiationNeeded() { + peerConnectionObservers.forEach { listener -> listener.onRenegotiationNeeded() } + } + + override fun onAddTrack(p0: RtpReceiver?, p1: Array?) { + peerConnectionObservers.forEach { listener -> listener.onAddTrack(p0, p1) } + } + + override fun onBufferedAmountChange(l: Long) { + Log.i(TAG,"onBufferedAmountChange: $l") + } + + override fun onStateChange() { + Log.i(TAG,"onStateChange") + } + + override fun onMessage(buffer: DataChannel.Buffer?) { + Log.i(TAG,"onMessage...") + buffer ?: return + + try { + val byteArray = ByteArray(buffer.data.remaining()) { buffer.data[it] } + val json = Json.parseToJsonElement(byteArray.decodeToString()) as JsonObject + if (json.containsKey("video")) { + _remoteVideoEvents.value = VideoEnabled((json["video"] as JsonPrimitive).boolean) + } else if (json.containsKey("hangup")) { + peerConnectionObservers.forEach(WebRtcListener::onHangup) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to deserialize data channel message", e) + } + } + + override fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set) { + _audioDeviceEvents.value = AudioDeviceUpdate(activeDevice, devices) + } + + fun stop() { + val isOutgoing = currentConnectionState in CallState.OUTGOING_STATES + stateProcessor.processEvent(Event.Cleanup) { + signalAudioManager.handleCommand(AudioManagerCommand.Stop(isOutgoing)) + peerConnection?.dispose() + peerConnection = null + + localRenderer?.release() + remoteRotationSink?.release() + remoteRenderer?.release() + eglBase?.release() + + localRenderer = null + remoteRenderer = null + eglBase = null + + localCameraState = CameraState.UNKNOWN + recipient = null + callId = null + pendingOfferTime = -1 + pendingOffer = null + callStartTime = -1 + _audioEvents.value = AudioEnabled(false) + _videoEvents.value = VideoEnabled(false) + _remoteVideoEvents.value = VideoEnabled(false) + pendingOutgoingIceUpdates.clear() + pendingIncomingIceUpdates.clear() + } + } + + override fun onCameraSwitchCompleted(newCameraState: CameraState) { + localCameraState = newCameraState + } + + fun onPreOffer(callId: UUID, recipient: Recipient, onSuccess: () -> Unit) { + stateProcessor.processEvent(Event.ReceivePreOffer) { + if (preOfferCallData != null) { + Log.d(TAG, "Received new pre-offer when we are already expecting one") + } + this.recipient = recipient + this.callId = callId + preOfferCallData = PreOffer(callId, recipient) + onSuccess() + } + } + + fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise { + if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId")) + if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient")) + + val connection = peerConnection ?: return Promise.ofFail(NullPointerException("No peer connection wrapper")) + + val reconnected = stateProcessor.processEvent(Event.ReceiveOffer) && stateProcessor.processEvent(Event.SendAnswer) + return if (reconnected) { + Log.i("Loki", "Handling new offer, restarting ice session") + connection.setNewRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer)) + // re-established an ice + val answer = connection.createAnswer(MediaConstraints().apply { + mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) + }) + connection.setLocalDescription(answer) + pendingIncomingIceUpdates.toList().forEach { update -> + connection.addIceCandidate(update) + } + pendingIncomingIceUpdates.clear() + val answerMessage = CallMessage.answer(answer.description, callId) + Log.i("Loki", "Posting new answer") + MessageSender.sendNonDurably(answerMessage, recipient.address) + } else { + Promise.ofFail(Exception("Couldn't reconnect from current state")) + } + } + + fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long, onSuccess: () -> Unit) { + postConnectionEvent(Event.ReceiveOffer) { + this.callId = callId + this.recipient = recipient + this.pendingOffer = offer + this.pendingOfferTime = callTime + initializeAudioForCall() + startIncomingRinger() + onSuccess() + } + } + + fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise { + val callId = callId ?: return Promise.ofFail(NullPointerException("callId is null")) + val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null")) + val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null")) + val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null")) + val local = localRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null")) + val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null")) + val connection = PeerConnectionWrapper( + context, + factory, + this, + local, + this, + base, + isAlwaysTurn + ) + peerConnection = connection + localCameraState = connection.getCameraState() + val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME) + this.dataChannel = dataChannel + dataChannel.registerObserver(this) + connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer)) + val answer = connection.createAnswer(MediaConstraints()) + connection.setLocalDescription(answer) + val answerMessage = CallMessage.answer(answer.description, callId) + val userAddress = storage.getUserPublicKey() ?: return Promise.ofFail(NullPointerException("No user public key")) + MessageSender.sendNonDurably(answerMessage, Address.fromSerialized(userAddress)) + val sendAnswerMessage = MessageSender.sendNonDurably(CallMessage.answer( + answer.description, + callId + ), recipient.address) + + insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_INCOMING, false) + + while (pendingIncomingIceUpdates.isNotEmpty()) { + val candidate = pendingIncomingIceUpdates.pop() ?: break + connection.addIceCandidate(candidate) + } + return sendAnswerMessage.success { + pendingOffer = null + pendingOfferTime = -1 + } + } + + fun onOutgoingCall(context: Context, isAlwaysTurn: Boolean = false): Promise { + val callId = callId ?: return Promise.ofFail(NullPointerException("callId is null")) + val recipient = recipient + ?: return Promise.ofFail(NullPointerException("recipient is null")) + val factory = peerConnectionFactory + ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null")) + val local = localRenderer + ?: return Promise.ofFail(NullPointerException("localRenderer is null")) + val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null")) + + val sentOffer = stateProcessor.processEvent(Event.SendOffer) + + if (!sentOffer) { + return Promise.ofFail(Exception("Couldn't transition to sent offer state")) + } else { + val connection = PeerConnectionWrapper( + context, + factory, + this, + local, + this, + base, + isAlwaysTurn + ) + + peerConnection = connection + localCameraState = connection.getCameraState() + val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME) + dataChannel.registerObserver(this) + this.dataChannel = dataChannel + val offer = connection.createOffer(MediaConstraints()) + connection.setLocalDescription(offer) + + Log.d("Loki", "Sending pre-offer") + return MessageSender.sendNonDurably(CallMessage.preOffer( + callId + ), recipient.address).bind { + Log.d("Loki", "Sent pre-offer") + Log.d("Loki", "Sending offer") + MessageSender.sendNonDurably(CallMessage.offer( + offer.description, + callId + ), recipient.address).success { + Log.d("Loki", "Sent offer") + }.fail { + Log.e("Loki", "Failed to send offer", it) + } + } + } + } + + fun handleDenyCall() { + val callId = callId ?: return + val recipient = recipient ?: return + val userAddress = storage.getUserPublicKey() ?: return + stateProcessor.processEvent(Event.DeclineCall) { + MessageSender.sendNonDurably(CallMessage.endCall(callId), Address.fromSerialized(userAddress)) + MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) + insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_MISSED) + } + } + + fun handleLocalHangup(intentRecipient: Recipient?) { + val recipient = recipient ?: return + val callId = callId ?: return + + val currentUserPublicKey = storage.getUserPublicKey() + val sendHangup = intentRecipient == null || (intentRecipient == recipient && recipient.address.serialize() != currentUserPublicKey) + + postViewModelState(CallViewModel.State.CALL_DISCONNECTED) + stateProcessor.processEvent(Event.Hangup) + if (sendHangup) { + dataChannel?.let { channel -> + val buffer = DataChannel.Buffer(ByteBuffer.wrap(HANGUP_JSON.toString().encodeToByteArray()), false) + channel.send(buffer) + } + MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) + } + } + + fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, signal: Boolean = false, sentTimestamp: Long = System.currentTimeMillis()) { + storage.insertCallMessage(threadPublicKey, callMessageType, sentTimestamp) + } + + fun handleRemoteHangup() { + when (currentConnectionState) { + CallState.LocalRing, + CallState.RemoteRing -> postViewModelState(CallViewModel.State.RECIPIENT_UNAVAILABLE) + else -> postViewModelState(CallViewModel.State.CALL_DISCONNECTED) + } + if (!stateProcessor.processEvent(Event.Hangup)) { + Log.e("Loki", "") + stateProcessor.processEvent(Event.Error) + } + } + + fun handleSetMuteAudio(muted: Boolean) { + _audioEvents.value = AudioEnabled(!muted) + peerConnection?.setAudioEnabled(!muted) + } + + fun handleSetMuteVideo(muted: Boolean, lockManager: LockManager) { + _videoEvents.value = VideoEnabled(!muted) + val connection = peerConnection ?: return + connection.setVideoEnabled(!muted) + dataChannel?.let { channel -> + val toSend = if (muted) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON + val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false) + channel.send(buffer) + } + + if (currentConnectionState == CallState.Connected) { + if (connection.isVideoEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO) + else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL) + } + + if (localCameraState.enabled + && !signalAudioManager.isSpeakerphoneOn() + && !signalAudioManager.isBluetoothScoOn() + && !signalAudioManager.isWiredHeadsetOn() + ) { + signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.SPEAKER_PHONE)) + } + } + + fun handleSetCameraFlip() { + if (!localCameraState.enabled) return + peerConnection?.let { connection -> + connection.flipCamera() + localCameraState = connection.getCameraState() + localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT) + } + } + + fun setDeviceRotation(newRotation: Int) { + peerConnection?.setDeviceRotation(newRotation) + remoteRotationSink?.rotation = newRotation + } + + fun handleWiredHeadsetChanged(present: Boolean) { + if (currentConnectionState in arrayOf(CallState.Connected, + CallState.LocalRing, + CallState.RemoteRing)) { + if (present && signalAudioManager.isSpeakerphoneOn()) { + signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.WIRED_HEADSET)) + } else if (!present && !signalAudioManager.isSpeakerphoneOn() && !signalAudioManager.isBluetoothScoOn() && localCameraState.enabled) { + signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.SPEAKER_PHONE)) + } + } + } + + fun handleScreenOffChange() { + if (currentConnectionState in arrayOf(CallState.Connecting, CallState.LocalRing)) { + signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger) + } + } + + fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) { + if (recipient != this.recipient || callId != this.callId) { + Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing") + return + } + + stateProcessor.processEvent(Event.ReceiveAnswer) { + val connection = peerConnection ?: throw AssertionError("assert") + + connection.setRemoteDescription(answer) + while (pendingIncomingIceUpdates.isNotEmpty()) { + connection.addIceCandidate(pendingIncomingIceUpdates.pop()) + } + queueOutgoingIce(callId, recipient) + } + } + + fun handleRemoteIceCandidate(iceCandidates: List, callId: UUID) { + if (callId != this.callId) { + Log.w(TAG, "Got remote ice candidates for a call that isn't active") + return + } + + val connection = peerConnection + if (connection != null && connection.readyForIce && currentConnectionState != CallState.Reconnecting) { + Log.i("Loki", "Handling connection ice candidate") + iceCandidates.forEach { candidate -> + connection.addIceCandidate(candidate) + } + } else { + Log.i("Loki", "Handling add to pending ice candidate") + pendingIncomingIceUpdates.addAll(iceCandidates) + } + } + + fun startIncomingRinger() { + signalAudioManager.handleCommand(AudioManagerCommand.StartIncomingRinger(true)) + } + + fun startCommunication(lockManager: LockManager) { + signalAudioManager.handleCommand(AudioManagerCommand.Start) + val connection = peerConnection ?: return + if (connection.isVideoEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO) + else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL) + connection.setCommunicationMode() + setAudioEnabled(true) + dataChannel?.let { channel -> + val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON + val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false) + channel.send(buffer) + } + } + + fun handleAudioCommand(audioCommand: AudioManagerCommand) { + signalAudioManager.handleCommand(audioCommand) + } + + fun networkReestablished() { + val connection = peerConnection ?: return + val callId = callId ?: return + val recipient = recipient ?: return + + postConnectionEvent(Event.NetworkReconnect) { + Log.d("Loki", "start re-establish") + + val offer = connection.createOffer(MediaConstraints().apply { + mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) + }) + connection.setLocalDescription(offer) + + MessageSender.sendNonDurably(CallMessage.offer(offer.description, callId), recipient.address) + } + } + + fun isInitiator(): Boolean = peerConnection?.isInitiator() == true + + interface WebRtcListener: PeerConnection.Observer { + fun onHangup() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt new file mode 100644 index 0000000000..a66dd591f6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -0,0 +1,151 @@ +package org.thoughtcrime.securesms.webrtc + +import android.app.NotificationManager +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.coroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.messaging.messages.control.CallMessage +import org.session.libsession.messaging.utilities.WebRtcUtils +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ANSWER +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.END_CALL +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PROVISIONAL_ANSWER +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.service.WebRtcCallService +import org.thoughtcrime.securesms.util.CallNotificationBuilder +import org.webrtc.IceCandidate + + +class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) { + + init { + lifecycle.coroutineScope.launch(IO) { + while (isActive) { + val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive() + Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED") + val sender = nextMessage.sender ?: continue + val approvedContact = Recipient.from(context, Address.fromSerialized(sender), false).isApproved + Log.i("Loki", "Contact is approved?: $approvedContact") + if (!approvedContact && storage.getUserPublicKey() != sender) continue + + if (!textSecurePreferences.isCallNotificationsEnabled()) { + Log.d("Loki","Dropping call message if call notifications disabled") + if (nextMessage.type != PRE_OFFER) continue + val sentTimestamp = nextMessage.sentTimestamp ?: continue + if (textSecurePreferences.setShownCallNotification()) { + // first time call notification encountered + val notification = CallNotificationBuilder.getFirstCallNotification(context) + context.getSystemService(NotificationManager::class.java).notify(CallNotificationBuilder.WEBRTC_NOTIFICATION, notification) + insertMissedCall(sender, sentTimestamp, isFirstCall = true) + } else { + insertMissedCall(sender, sentTimestamp) + } + continue + } + when (nextMessage.type) { + OFFER -> incomingCall(nextMessage) + ANSWER -> incomingAnswer(nextMessage) + END_CALL -> incomingHangup(nextMessage) + ICE_CANDIDATES -> handleIceCandidates(nextMessage) + PRE_OFFER -> incomingPreOffer(nextMessage) + PROVISIONAL_ANSWER, null -> {} // TODO: if necessary + } + } + } + } + + private fun insertMissedCall(sender: String, sentTimestamp: Long, isFirstCall: Boolean = false) { + val currentUserPublicKey = storage.getUserPublicKey() + if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender + if (isFirstCall) { + storage.insertCallMessage(sender, CallMessageType.CALL_FIRST_MISSED, sentTimestamp) + } else { + storage.insertCallMessage(sender, CallMessageType.CALL_MISSED, sentTimestamp) + } + } + + private fun incomingHangup(callMessage: CallMessage) { + val callId = callMessage.callId ?: return + val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) + context.startService(hangupIntent) + } + + private fun incomingAnswer(callMessage: CallMessage) { + val recipientAddress = callMessage.sender ?: return + val callId = callMessage.callId ?: return + val sdp = callMessage.sdps.firstOrNull() ?: return + val answerIntent = WebRtcCallService.incomingAnswer( + context = context, + address = Address.fromSerialized(recipientAddress), + sdp = sdp, + callId = callId + ) + context.startService(answerIntent) + } + + private fun handleIceCandidates(callMessage: CallMessage) { + val callId = callMessage.callId ?: return + val sender = callMessage.sender ?: return + + val iceCandidates = callMessage.iceCandidates() + if (iceCandidates.isEmpty()) return + + val iceIntent = WebRtcCallService.iceCandidates( + context = context, + iceCandidates = iceCandidates, + callId = callId, + address = Address.fromSerialized(sender) + ) + context.startService(iceIntent) + } + + private fun incomingPreOffer(callMessage: CallMessage) { + // handle notification state + val recipientAddress = callMessage.sender ?: return + val callId = callMessage.callId ?: return + val incomingIntent = WebRtcCallService.preOffer( + context = context, + address = Address.fromSerialized(recipientAddress), + callId = callId, + callTime = callMessage.sentTimestamp!! + ) + ContextCompat.startForegroundService(context, incomingIntent) + } + + private fun incomingCall(callMessage: CallMessage) { + val recipientAddress = callMessage.sender ?: return + val callId = callMessage.callId ?: return + val sdp = callMessage.sdps.firstOrNull() ?: return + val incomingIntent = WebRtcCallService.incomingCall( + context = context, + address = Address.fromSerialized(recipientAddress), + sdp = sdp, + callId = callId, + callTime = callMessage.sentTimestamp!! + ) + ContextCompat.startForegroundService(context, incomingIntent) + + } + + private fun CallMessage.iceCandidates(): List { + if (sdpMids.size != sdpMLineIndexes.size || sdpMLineIndexes.size != sdps.size) { + return listOf() // uneven sdp numbers + } + val candidateSize = sdpMids.size + return (0 until candidateSize).map { i -> + IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i]) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt new file mode 100644 index 0000000000..4f27e5d1ad --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -0,0 +1,88 @@ +package org.thoughtcrime.securesms.webrtc + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager +import org.webrtc.SurfaceViewRenderer +import javax.inject.Inject + +@HiltViewModel +class CallViewModel @Inject constructor(private val callManager: CallManager): ViewModel() { + + enum class State { + CALL_PENDING, + + CALL_PRE_INIT, + CALL_INCOMING, + CALL_OUTGOING, + CALL_CONNECTED, + CALL_RINGING, + CALL_BUSY, + CALL_DISCONNECTED, + CALL_RECONNECTING, + + NETWORK_FAILURE, + RECIPIENT_UNAVAILABLE, + NO_SUCH_USER, + UNTRUSTED_IDENTITY, + } + + val localRenderer: SurfaceViewRenderer? + get() = callManager.localRenderer + + val remoteRenderer: SurfaceViewRenderer? + get() = callManager.remoteRenderer + + private var _videoEnabled: Boolean = false + + val videoEnabled: Boolean + get() = _videoEnabled + + private var _microphoneEnabled: Boolean = true + + val microphoneEnabled: Boolean + get() = _microphoneEnabled + + private var _isSpeaker: Boolean = false + val isSpeaker: Boolean + get() = _isSpeaker + + val audioDeviceState + get() = callManager.audioDeviceEvents + .onEach { + _isSpeaker = it.selectedDevice == SignalAudioManager.AudioDevice.SPEAKER_PHONE + } + + val localAudioEnabledState + get() = callManager.audioEvents.map { it.isEnabled } + .onEach { _microphoneEnabled = it } + + val localVideoEnabledState + get() = callManager.videoEvents + .map { it.isEnabled } + .onEach { _videoEnabled = it } + + val remoteVideoEnabledState + get() = callManager.remoteVideoEvents.map { it.isEnabled } + + var deviceRotation: Int = 0 + set(value) { + field = value + callManager.setDeviceRotation(value) + } + + val currentCallState + get() = callManager.currentCallState + + val callState + get() = callManager.callStateEvents + + val recipient + get() = callManager.recipientEvents + + val callStartTime: Long + get() = callManager.callStartTime + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/IncomingPstnCallReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/IncomingPstnCallReceiver.java new file mode 100644 index 0000000000..01b161e088 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/IncomingPstnCallReceiver.java @@ -0,0 +1,74 @@ +package org.thoughtcrime.securesms.webrtc; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.telephony.TelephonyManager; + +import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.service.WebRtcCallService; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Listens for incoming PSTN calls and rejects them if a RedPhone call is already in progress. + * + * Unstable use of reflection employed to gain access to ITelephony. + * + */ +public class IncomingPstnCallReceiver extends BroadcastReceiver { + + private static final String TAG = IncomingPstnCallReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Checking incoming call..."); + + if (intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) == null) { + Log.w(TAG, "Telephony event does not contain number..."); + return; + } + + if (!intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)) { + Log.w(TAG, "Telephony event is not state ringing..."); + return; + } + + InCallListener listener = new InCallListener(context, new Handler()); + + WebRtcCallService.isCallActive(context, listener); + } + + private static class InCallListener extends ResultReceiver { + + private final Context context; + + InCallListener(Context context, Handler handler) { + super(handler); + this.context = context.getApplicationContext(); + } + + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == 1) { + Log.i(TAG, "Attempting to deny incoming PSTN call."); + + TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + + try { + Method getTelephony = tm.getClass().getDeclaredMethod("getITelephony"); + getTelephony.setAccessible(true); + Object telephonyService = getTelephony.invoke(tm); + Method endCall = telephonyService.getClass().getDeclaredMethod("endCall"); + endCall.invoke(telephonyService); + Log.i(TAG, "Denied Incoming Call."); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + Log.w(TAG, "Unable to access ITelephony API", e); + } + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt new file mode 100644 index 0000000000..52ee5583d2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt @@ -0,0 +1,75 @@ +package org.thoughtcrime.securesms.webrtc + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.Network +import org.session.libsignal.utilities.Log + +class NetworkChangeReceiver(private val onNetworkChangedCallback: (Boolean)->Unit) { + + private val networkList: MutableSet = mutableSetOf() + + val broadcastDelegate = object: BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + receiveBroadcast(context, intent) + } + } + + val defaultObserver = object: ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + Log.i("Loki", "onAvailable: $network") + networkList += network + onNetworkChangedCallback(networkList.isNotEmpty()) + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + Log.i("Loki", "onLosing: $network, maxMsToLive: $maxMsToLive") + } + + override fun onLost(network: Network) { + Log.i("Loki", "onLost: $network") + networkList -= network + onNetworkChangedCallback(networkList.isNotEmpty()) + } + + override fun onUnavailable() { + Log.i("Loki", "onUnavailable") + } + } + + fun receiveBroadcast(context: Context, intent: Intent) { + val connected = context.isConnected() + Log.i("Loki", "received broadcast, network connected: $connected") + onNetworkChangedCallback(connected) + } + + fun Context.isConnected() : Boolean { + val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return cm.activeNetwork != null + } + + fun register(context: Context) { + val intentFilter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE") + context.registerReceiver(broadcastDelegate, intentFilter) +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +// val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager +// cm.registerDefaultNetworkCallback(defaultObserver) +// } else { +// +// } + } + + fun unregister(context: Context) { + context.unregisterReceiver(broadcastDelegate) +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +// val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager +// cm.unregisterNetworkCallback(defaultObserver) +// } else { +// +// } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionException.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionException.kt new file mode 100644 index 0000000000..2e8d79d62d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionException.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.webrtc + +class PeerConnectionException: Exception { + constructor(error: String?): super(error) + constructor(throwable: Throwable): super(throwable) +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt new file mode 100644 index 0000000000..26d8fc223d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt @@ -0,0 +1,335 @@ +package org.thoughtcrime.securesms.webrtc + +import android.content.Context +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.SettableFuture +import org.thoughtcrime.securesms.webrtc.video.Camera +import org.thoughtcrime.securesms.webrtc.video.CameraEventListener +import org.thoughtcrime.securesms.webrtc.video.CameraState +import org.thoughtcrime.securesms.webrtc.video.RotationVideoSink +import org.webrtc.AudioSource +import org.webrtc.AudioTrack +import org.webrtc.DataChannel +import org.webrtc.EglBase +import org.webrtc.IceCandidate +import org.webrtc.MediaConstraints +import org.webrtc.MediaStream +import org.webrtc.PeerConnection +import org.webrtc.PeerConnectionFactory +import org.webrtc.SdpObserver +import org.webrtc.SessionDescription +import org.webrtc.SurfaceTextureHelper +import org.webrtc.VideoSink +import org.webrtc.VideoSource +import org.webrtc.VideoTrack +import java.security.SecureRandom +import java.util.concurrent.ExecutionException +import kotlin.random.asKotlinRandom + +class PeerConnectionWrapper(private val context: Context, + private val factory: PeerConnectionFactory, + private val observer: PeerConnection.Observer, + private val localRenderer: VideoSink, + private val cameraEventListener: CameraEventListener, + private val eglBase: EglBase, + private val relay: Boolean = false): CameraEventListener { + + private var peerConnection: PeerConnection? = null + private val audioTrack: AudioTrack + private val audioSource: AudioSource + private val camera: Camera + private val mediaStream: MediaStream + private val videoSource: VideoSource? + private val videoTrack: VideoTrack? + private val rotationVideoSink = RotationVideoSink() + + val readyForIce + get() = peerConnection?.localDescription != null && peerConnection?.remoteDescription != null + + private var isInitiator = false + + private fun initPeerConnection() { + val random = SecureRandom().asKotlinRandom() + val iceServers = listOf("freyr","fenrir","frigg","angus","hereford","holstein", "brahman").shuffled(random).take(2).map { sub -> + PeerConnection.IceServer.builder("turn:$sub.getsession.org") + .setUsername("session202111") + .setPassword("053c268164bc7bd7") + .createIceServer() + } + + val constraints = MediaConstraints().apply { + optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")) + } + + val configuration = PeerConnection.RTCConfiguration(iceServers).apply { + bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE + rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE + if (relay) { + iceTransportsType = PeerConnection.IceTransportsType.RELAY + } + } + + val newPeerConnection = factory.createPeerConnection(configuration, constraints, observer)!! + peerConnection = newPeerConnection + newPeerConnection.setAudioPlayout(true) + newPeerConnection.setAudioRecording(true) + + newPeerConnection.addStream(mediaStream) + } + + init { + val audioConstraints = MediaConstraints().apply { + optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")) + } + + mediaStream = factory.createLocalMediaStream("ARDAMS") + audioSource = factory.createAudioSource(audioConstraints) + audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource) + audioTrack.setEnabled(true) + mediaStream.addTrack(audioTrack) + + val newCamera = Camera(context, this) + camera = newCamera + + if (newCamera.capturer != null) { + val newVideoSource = factory.createVideoSource(false) + videoSource = newVideoSource + val newVideoTrack = factory.createVideoTrack("ARDAMSv0", newVideoSource) + videoTrack = newVideoTrack + + rotationVideoSink.setObserver(newVideoSource.capturerObserver) + newCamera.capturer.initialize( + SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.eglBaseContext), + context, + rotationVideoSink + ) + rotationVideoSink.mirrored = newCamera.activeDirection == CameraState.Direction.FRONT + rotationVideoSink.setSink(localRenderer) + newVideoTrack.setEnabled(false) + mediaStream.addTrack(newVideoTrack) + } else { + videoSource = null + videoTrack = null + } + initPeerConnection() + } + + fun getCameraState(): CameraState { + return CameraState(camera.activeDirection, camera.cameraCount) + } + + fun createDataChannel(channelName: String): DataChannel { + val dataChannelConfiguration = DataChannel.Init().apply { + ordered = true + negotiated = true + id = 548 + } + return peerConnection!!.createDataChannel(channelName, dataChannelConfiguration) + } + + fun addIceCandidate(candidate: IceCandidate) { + // TODO: filter logic based on known servers + peerConnection!!.addIceCandidate(candidate) + } + + fun dispose() { + camera.dispose() + + videoSource?.dispose() + + audioSource.dispose() + peerConnection?.close() + peerConnection?.dispose() + } + + fun setNewRemoteDescription(description: SessionDescription) { + val future = SettableFuture() + + peerConnection!!.setRemoteDescription(object: SdpObserver { + override fun onCreateSuccess(p0: SessionDescription?) { + throw AssertionError() + } + + override fun onCreateFailure(p0: String?) { + throw AssertionError() + } + + override fun onSetSuccess() { + future.set(true) + } + + override fun onSetFailure(error: String?) { + future.setException(PeerConnectionException(error)) + } + }, description) + + try { + future.get() + } catch (e: InterruptedException) { + throw AssertionError(e) + } catch (e: ExecutionException) { + throw PeerConnectionException(e) + } + } + + fun setRemoteDescription(description: SessionDescription) { + val future = SettableFuture() + + peerConnection!!.setRemoteDescription(object: SdpObserver { + override fun onCreateSuccess(p0: SessionDescription?) { + throw AssertionError() + } + + override fun onCreateFailure(p0: String?) { + throw AssertionError() + } + + override fun onSetSuccess() { + future.set(true) + } + + override fun onSetFailure(error: String?) { + future.setException(PeerConnectionException(error)) + } + }, description) + + try { + future.get() + } catch (e: InterruptedException) { + throw AssertionError(e) + } catch (e: ExecutionException) { + throw PeerConnectionException(e) + } + } + + fun createAnswer(mediaConstraints: MediaConstraints) : SessionDescription { + val future = SettableFuture() + + peerConnection!!.createAnswer(object:SdpObserver { + override fun onCreateSuccess(sdp: SessionDescription?) { + future.set(sdp) + } + + override fun onSetSuccess() { + throw AssertionError() + } + + override fun onCreateFailure(p0: String?) { + future.setException(PeerConnectionException(p0)) + } + + override fun onSetFailure(p0: String?) { + throw AssertionError() + } + }, mediaConstraints) + + try { + return correctSessionDescription(future.get()) + } catch (e: InterruptedException) { + throw AssertionError() + } catch (e: ExecutionException) { + throw PeerConnectionException(e) + } + } + + private fun correctSessionDescription(sessionDescription: SessionDescription): SessionDescription { + val updatedSdp = sessionDescription.description.replace("(a=fmtp:111 ((?!cbr=).)*)\r?\n".toRegex(), "$1;cbr=1\r\n") + .replace(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n".toRegex(), "") + + return SessionDescription(sessionDescription.type, updatedSdp) + } + + fun createOffer(mediaConstraints: MediaConstraints): SessionDescription { + val future = SettableFuture() + + peerConnection!!.createOffer(object:SdpObserver { + override fun onCreateSuccess(sdp: SessionDescription?) { + future.set(sdp) + } + + override fun onSetSuccess() { + throw AssertionError() + } + + override fun onCreateFailure(p0: String?) { + future.setException(PeerConnectionException(p0)) + } + + override fun onSetFailure(p0: String?) { + throw AssertionError() + } + }, mediaConstraints) + + try { + isInitiator = true + return correctSessionDescription(future.get()) + } catch (e: InterruptedException) { + throw AssertionError() + } catch (e: ExecutionException) { + throw PeerConnectionException(e) + } + } + + fun setLocalDescription(sdp: SessionDescription) { + val future = SettableFuture() + + peerConnection!!.setLocalDescription(object: SdpObserver { + override fun onCreateSuccess(p0: SessionDescription?) { + + } + + override fun onSetSuccess() { + future.set(true) + } + + override fun onCreateFailure(p0: String?) {} + + override fun onSetFailure(error: String?) { + future.setException(PeerConnectionException(error)) + } + }, sdp) + + try { + future.get() + } catch(e: InterruptedException) { + throw AssertionError(e) + } catch(e: ExecutionException) { + throw PeerConnectionException(e) + } + } + + fun setCommunicationMode() { + peerConnection?.setAudioPlayout(true) + peerConnection?.setAudioRecording(true) + } + + fun setAudioEnabled(isEnabled: Boolean) { + audioTrack.setEnabled(isEnabled) + } + + fun setDeviceRotation(rotation: Int) { + Log.d("Loki", "rotation: $rotation") + rotationVideoSink.rotation = rotation + } + + fun setVideoEnabled(isEnabled: Boolean) { + videoTrack?.let { track -> + track.setEnabled(isEnabled) + camera.enabled = isEnabled + } + } + + fun isVideoEnabled() = camera.enabled + + fun flipCamera() { + camera.flip() + } + + override fun onCameraSwitchCompleted(newCameraState: CameraState) { + // mirror rotation offset + rotationVideoSink.mirrored = newCameraState.activeDirection == CameraState.Direction.FRONT + cameraEventListener.onCameraSwitchCompleted(newCameraState) + } + + fun isInitiator(): Boolean = isInitiator +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt new file mode 100644 index 0000000000..dfc2dc6fb3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PreOffer.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.webrtc + +import org.session.libsession.utilities.recipients.Recipient +import java.util.* + +data class PreOffer(val callId: UUID, val recipient: Recipient) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/UncaughtExceptionHandlerManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/UncaughtExceptionHandlerManager.java new file mode 100644 index 0000000000..56271b9803 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/UncaughtExceptionHandlerManager.java @@ -0,0 +1,42 @@ +package org.thoughtcrime.securesms.webrtc; + +import org.session.libsignal.utilities.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Allows multiple default uncaught exception handlers to be registered + * + * Calls all registered handlers in reverse order of registration. + * Errors in one handler do not prevent subsequent handlers from being called. + */ +public class UncaughtExceptionHandlerManager implements Thread.UncaughtExceptionHandler { + private final Thread.UncaughtExceptionHandler originalHandler; + private final List handlers = new ArrayList(); + + public UncaughtExceptionHandlerManager() { + originalHandler = Thread.getDefaultUncaughtExceptionHandler(); + registerHandler(originalHandler); + Thread.setDefaultUncaughtExceptionHandler(this); + } + + public void registerHandler(Thread.UncaughtExceptionHandler handler) { + handlers.add(handler); + } + + public void unregister() { + Thread.setDefaultUncaughtExceptionHandler(originalHandler); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + for (int i = handlers.size() - 1; i >= 0; i--) { + try { + handlers.get(i).uncaughtException(thread, throwable); + } catch(Throwable t) { + Log.e("UncaughtExceptionHandlerManager", "Error in uncaught exception handling", t); + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt new file mode 100644 index 0000000000..955356c7d1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt @@ -0,0 +1,57 @@ +package org.thoughtcrime.securesms.webrtc + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.telephony.PhoneStateListener +import android.telephony.TelephonyManager +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.service.WebRtcCallService +import org.thoughtcrime.securesms.webrtc.locks.LockManager + + +class HangUpRtcOnPstnCallAnsweredListener(private val hangupListener: ()->Unit): PhoneStateListener() { + + companion object { + private val TAG = Log.tag(HangUpRtcOnPstnCallAnsweredListener::class.java) + } + + override fun onCallStateChanged(state: Int, phoneNumber: String?) { + super.onCallStateChanged(state, phoneNumber) + if (state == TelephonyManager.CALL_STATE_OFFHOOK) { + hangupListener() + Log.i(TAG, "Device phone call ended Session call.") + } + } +} + +class PowerButtonReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_SCREEN_OFF == intent.action) { + val serviceIntent = Intent(context,WebRtcCallService::class.java) + .setAction(WebRtcCallService.ACTION_SCREEN_OFF) + context.startService(serviceIntent) + } + } +} + +class ProximityLockRelease(private val lockManager: LockManager): Thread.UncaughtExceptionHandler { + companion object { + private val TAG = Log.tag(ProximityLockRelease::class.java) + } + override fun uncaughtException(t: Thread, e: Throwable) { + Log.e(TAG,"Uncaught exception - releasing proximity lock", e) + lockManager.updatePhoneState(LockManager.PhoneState.IDLE) + } +} + +class WiredHeadsetStateReceiver: BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val state = intent.getIntExtra("state", -1) + val serviceIntent = Intent(context, WebRtcCallService::class.java) + .setAction(WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE) + .putExtra(WebRtcCallService.EXTRA_AVAILABLE, state != 0) + + context.startService(serviceIntent) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java new file mode 100644 index 0000000000..da50cd0846 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCompat.java @@ -0,0 +1,223 @@ +package org.thoughtcrime.securesms.webrtc.audio; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.AudioAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import org.session.libsession.utilities.ServiceUtil; +import org.session.libsignal.utilities.Log; + + +public abstract class AudioManagerCompat { + + private static final String TAG = Log.tag(AudioManagerCompat.class); + + private static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; + + protected final AudioManager audioManager; + + @SuppressWarnings("CodeBlock2Expr") + protected final AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener = focusChange -> { + Log.i(TAG, "onAudioFocusChangeListener: " + focusChange); + }; + + private AudioManagerCompat(@NonNull Context context) { + audioManager = ServiceUtil.getAudioManager(context); + } + + public boolean isBluetoothScoAvailableOffCall() { + return audioManager.isBluetoothScoAvailableOffCall(); + } + + public void startBluetoothSco() { + audioManager.startBluetoothSco(); + } + + public void stopBluetoothSco() { + audioManager.stopBluetoothSco(); + } + + public boolean isBluetoothScoOn() { + return audioManager.isBluetoothScoOn(); + } + + public void setBluetoothScoOn(boolean on) { + audioManager.setBluetoothScoOn(on); + } + + public int getMode() { + return audioManager.getMode(); + } + + public void setMode(int modeInCommunication) { + audioManager.setMode(modeInCommunication); + } + + public boolean isSpeakerphoneOn() { + return audioManager.isSpeakerphoneOn(); + } + + public void setSpeakerphoneOn(boolean on) { + audioManager.setSpeakerphoneOn(on); + } + + public boolean isMicrophoneMute() { + return audioManager.isMicrophoneMute(); + } + + public void setMicrophoneMute(boolean on) { + audioManager.setMicrophoneMute(on); + } + + public boolean hasEarpiece(@NonNull Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + } + + @SuppressLint("WrongConstant") + public boolean isWiredHeadsetOn() { + AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); + for (AudioDeviceInfo device : devices) { + final int type = device.getType(); + if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) { + return true; + } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) { + return true; + } + } + return false; + } + + public float ringVolumeWithMinimum() { + int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING); + float volume = logVolume(currentVolume, maxVolume); + float minVolume = logVolume(15, 100); + return Math.max(volume, minVolume); + } + + private static float logVolume(int volume, int maxVolume) { + if (maxVolume == 0 || volume > maxVolume) { + return 0.5f; + } + return (float) (1 - (Math.log(maxVolume + 1 - volume) / Math.log(maxVolume + 1))); + } + + abstract public SoundPool createSoundPool(); + abstract public void requestCallAudioFocus(); + abstract public void abandonCallAudioFocus(); + + public static AudioManagerCompat create(@NonNull Context context) { + if (Build.VERSION.SDK_INT >= 26) { + return new Api26AudioManagerCompat(context); + } else { + return new Api21AudioManagerCompat(context); + } + } + + @RequiresApi(26) + private static class Api26AudioManagerCompat extends AudioManagerCompat { + + private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); + + private AudioFocusRequest audioFocusRequest; + + private Api26AudioManagerCompat(@NonNull Context context) { + super(context); + } + + @Override + public SoundPool createSoundPool() { + return new SoundPool.Builder() + .setAudioAttributes(AUDIO_ATTRIBUTES) + .setMaxStreams(1) + .build(); + } + + @Override + public void requestCallAudioFocus() { + if (audioFocusRequest != null) { + Log.w(TAG, "Already requested audio focus. Ignoring..."); + return; + } + + audioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN) + .setAudioAttributes(AUDIO_ATTRIBUTES) + .setOnAudioFocusChangeListener(onAudioFocusChangeListener) + .build(); + + int result = audioManager.requestAudioFocus(audioFocusRequest); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.w(TAG, "Audio focus not granted. Result code: " + result); + } + } + + @Override + public void abandonCallAudioFocus() { + if (audioFocusRequest == null) { + Log.w(TAG, "Don't currently have audio focus. Ignoring..."); + return; + } + + int result = audioManager.abandonAudioFocusRequest(audioFocusRequest); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.w(TAG, "Audio focus abandon failed. Result code: " + result); + } + + audioFocusRequest = null; + } + } + + @RequiresApi(21) + private static class Api21AudioManagerCompat extends AudioManagerCompat { + + private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL) + .build(); + + private Api21AudioManagerCompat(@NonNull Context context) { + super(context); + } + + @Override + public SoundPool createSoundPool() { + return new SoundPool.Builder() + .setAudioAttributes(AUDIO_ATTRIBUTES) + .setMaxStreams(1) + .build(); + } + + @Override + public void requestCallAudioFocus() { + int result = audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_VOICE_CALL, AUDIOFOCUS_GAIN); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.w(TAG, "Audio focus not granted. Result code: " + result); + } + } + + @Override + public void abandonCallAudioFocus() { + int result = audioManager.abandonAudioFocus(onAudioFocusChangeListener); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.w(TAG, "Audio focus abandon failed. Result code: " + result); + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.kt new file mode 100644 index 0000000000..1b3995a2aa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.kt @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.webrtc.audio + +import android.content.Context +import android.media.AudioManager +import android.media.MediaPlayer +import android.media.RingtoneManager +import android.os.Vibrator +import org.session.libsession.utilities.ServiceUtil +import org.session.libsignal.utilities.Log + +class IncomingRinger(private val context: Context) { + companion object { + const val TAG = "IncomingRinger" + val PATTERN = longArrayOf(0L, 1000L, 1000L) + } + + private val vibrator: Vibrator? = ServiceUtil.getVibrator(context) + var mediaPlayer: MediaPlayer? = null + + val isRinging: Boolean + get() = mediaPlayer?.isPlaying ?: false + + fun start(vibrate: Boolean) { + val audioManager = ServiceUtil.getAudioManager(context) + mediaPlayer?.release() + mediaPlayer = createMediaPlayer() + val ringerMode = audioManager.ringerMode + + if (shouldVibrate(mediaPlayer, ringerMode, vibrate)) { + Log.i(TAG,"Starting vibration") + vibrator?.vibrate(PATTERN, 1) + } else { + Log.i(TAG,"Skipping vibration") + } + + mediaPlayer?.let { player -> + if (ringerMode == AudioManager.RINGER_MODE_NORMAL) { + try { + if (!player.isPlaying) { + player.prepare() + player.start() + Log.i(TAG,"Playing ringtone") + } + } catch (e: Exception) { + Log.e(TAG,"Failed to start mediaPlayer", e) + } + } + } ?: run { + Log.w(TAG,"Not ringing, mediaPlayer: ${mediaPlayer?.let{"available"}}, mode: $ringerMode") + } + + } + + fun stop() { + mediaPlayer?.release() + mediaPlayer = null + vibrator?.cancel() + } + + private fun shouldVibrate(player: MediaPlayer?, ringerMode: Int, vibrate: Boolean): Boolean { + player ?: return true + + if (vibrator == null || !vibrator.hasVibrator()) return false + + return if (vibrate) ringerMode != AudioManager.RINGER_MODE_SILENT + else ringerMode == AudioManager.RINGER_MODE_VIBRATE + } + + private fun createMediaPlayer(): MediaPlayer? { + try { + val defaultRingtone = try { + RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE) + } catch (e: Exception) { + Log.e(TAG, "Failed to get default system ringtone", e) + null + } ?: return null + + try { + val mediaPlayer = MediaPlayer() + mediaPlayer.setDataSource(context, defaultRingtone) + return mediaPlayer + } catch (e: SecurityException) { + Log.w(TAG, "Failed to create player with ringtone the normal way", e) + } + } catch (e: Exception) { + Log.e(TAG,"Failed to create mediaPlayer") + } + + return null + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.kt new file mode 100644 index 0000000000..465866931a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/OutgoingRinger.kt @@ -0,0 +1,56 @@ +package org.thoughtcrime.securesms.webrtc.audio + +import android.content.Context +import android.media.AudioAttributes +import android.media.MediaPlayer +import android.net.Uri +import network.loki.messenger.R +import org.session.libsignal.utilities.Log +import java.io.IOException +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException + +class OutgoingRinger(private val context: Context) { + enum class Type { + RINGING, BUSY + } + + private var mediaPlayer: MediaPlayer? = null + fun start(type: Type) { + val soundId: Int = if (type == Type.RINGING) R.raw.redphone_outring else if (type == Type.BUSY) R.raw.redphone_busy else throw IllegalArgumentException("Not a valid sound type") + if (mediaPlayer != null) { + mediaPlayer!!.release() + } + mediaPlayer = MediaPlayer() + mediaPlayer!!.setAudioAttributes(AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build()) + mediaPlayer!!.isLooping = true + val packageName = context.packageName + val dataUri = Uri.parse("android.resource://$packageName/$soundId") + try { + mediaPlayer!!.setDataSource(context, dataUri) + mediaPlayer!!.prepare() + mediaPlayer!!.start() + } catch (e: IllegalArgumentException) { + Log.e(TAG, e) + } catch (e: SecurityException) { + Log.e(TAG, e) + } catch (e: IllegalStateException) { + Log.e(TAG, e) + } catch (e: IOException) { + Log.e(TAG, e) + } + } + + fun stop() { + if (mediaPlayer == null) return + mediaPlayer!!.release() + mediaPlayer = null + } + + companion object { + private val TAG: String = Log.tag(OutgoingRinger::class.java) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt new file mode 100644 index 0000000000..89eba2a3aa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.webrtc.audio + +import android.os.Handler +import android.os.Looper + +/** + * Handler to run all audio/bluetooth operations. Provides current thread + * assertion for enforcing use of the handler when necessary. + */ +class SignalAudioHandler(looper: Looper) : Handler(looper) { + + fun assertHandlerThread() { + if (!isOnHandler()) { + throw AssertionError("Must run on audio handler thread.") + } + } + + fun isOnHandler(): Boolean { + return Looper.myLooper() == looper + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt new file mode 100644 index 0000000000..2b4d34807c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt @@ -0,0 +1,393 @@ +package org.thoughtcrime.securesms.webrtc.audio + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioManager +import android.media.SoundPool +import android.os.Build +import android.os.HandlerThread +import network.loki.messenger.R +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.AudioManagerCommand +import org.thoughtcrime.securesms.webrtc.audio.SignalBluetoothManager.State as BState + +private val TAG = Log.tag(SignalAudioManager::class.java) + +/** + * Manage all audio and bluetooth routing for calling. Primarily, operates by maintaining a list + * of available devices (wired, speaker, bluetooth, earpiece) and then using a state machine to determine + * which device to use. Inputs into the decision include the [defaultAudioDevice] (set based on if audio + * only or video call) and [userSelectedAudioDevice] (set by user interaction with UI). [autoSwitchToWiredHeadset] + * and [autoSwitchToBluetooth] also impact the decision by forcing the user selection to the respective device + * when initially discovered. If the user switches to another device while bluetooth or wired headset are + * connected, the system will not auto switch back until the audio device is disconnected and reconnected. + * + * For example, call starts with speaker, then a bluetooth headset is connected. The audio will automatically + * switch to the headset. The user can then switch back to speaker through a manual interaction. If the + * bluetooth headset is then disconnected, and reconnected, the audio will again automatically switch to + * the bluetooth headset. + */ +class SignalAudioManager(private val context: Context, + private val eventListener: EventListener?, + private val androidAudioManager: AudioManagerCompat) { + + private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio").apply { start() } + private var handler: SignalAudioHandler? = null + + private var signalBluetoothManager: SignalBluetoothManager? = null + + private var state: State = State.UNINITIALIZED + + private var savedAudioMode = AudioManager.MODE_INVALID + private var savedIsSpeakerPhoneOn = false + private var savedIsMicrophoneMute = false + private var hasWiredHeadset = false + private var autoSwitchToWiredHeadset = true + private var autoSwitchToBluetooth = true + + private var defaultAudioDevice: AudioDevice = AudioDevice.EARPIECE + private var selectedAudioDevice: AudioDevice = AudioDevice.NONE + private var userSelectedAudioDevice: AudioDevice = AudioDevice.NONE + + private var audioDevices: MutableSet = mutableSetOf() + + private val soundPool: SoundPool = androidAudioManager.createSoundPool() + private val connectedSoundId = soundPool.load(context, R.raw.webrtc_completed, 1) + private val disconnectedSoundId = soundPool.load(context, R.raw.webrtc_disconnected, 1) + + private val incomingRinger = IncomingRinger(context) + private val outgoingRinger = OutgoingRinger(context) + + private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null + + fun handleCommand(command: AudioManagerCommand) { + if (command == AudioManagerCommand.Initialize) { + initialize() + return + } + handler?.post { + when (command) { + is AudioManagerCommand.UpdateAudioDeviceState -> updateAudioDeviceState() + is AudioManagerCommand.Start -> start() + is AudioManagerCommand.Stop -> stop(command.playDisconnect) + is AudioManagerCommand.SetDefaultDevice -> setDefaultAudioDevice(command.device, command.clearUserEarpieceSelection) + is AudioManagerCommand.SetUserDevice -> selectAudioDevice(command.device) + is AudioManagerCommand.StartIncomingRinger -> startIncomingRinger(command.vibrate) + is AudioManagerCommand.SilenceIncomingRinger -> silenceIncomingRinger() + is AudioManagerCommand.StartOutgoingRinger -> startOutgoingRinger(command.type) + } + } + } + + private fun initialize() { + Log.i(TAG, "Initializing audio manager state: $state") + + if (state == State.UNINITIALIZED) { + commandAndControlThread = HandlerThread("call-audio").apply { start() } + handler = SignalAudioHandler(commandAndControlThread!!.looper) + + signalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!) + + handler!!.post { + + savedAudioMode = androidAudioManager.mode + savedIsSpeakerPhoneOn = androidAudioManager.isSpeakerphoneOn + savedIsMicrophoneMute = androidAudioManager.isMicrophoneMute + hasWiredHeadset = androidAudioManager.isWiredHeadsetOn + + androidAudioManager.requestCallAudioFocus() + + setMicrophoneMute(false) + + audioDevices.clear() + + signalBluetoothManager!!.start() + + updateAudioDeviceState() + + wiredHeadsetReceiver = WiredHeadsetReceiver() + context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG)) + + state = State.PREINITIALIZED + + Log.d(TAG, "Initialized") + } + } + } + + private fun start() { + Log.d(TAG, "Starting. state: $state") + if (state == State.RUNNING) { + Log.w(TAG, "Skipping, already active") + return + } + + incomingRinger.stop() + outgoingRinger.stop() + + state = State.RUNNING + + androidAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION + + val volume: Float = androidAudioManager.ringVolumeWithMinimum() + soundPool.play(connectedSoundId, volume, volume, 0, 0, 1.0f) + + Log.d(TAG, "Started") + } + + private fun stop(playDisconnect: Boolean) { + Log.d(TAG, "Stopping. state: $state") + if (state == State.UNINITIALIZED) { + Log.i(TAG, "Trying to stop AudioManager in incorrect state: $state") + return + } + + handler?.post { + incomingRinger.stop() + outgoingRinger.stop() + stop(false) + if (commandAndControlThread != null) { + Log.i(TAG, "Shutting down command and control") + commandAndControlThread?.quitSafely() + commandAndControlThread = null + } + } + + if (playDisconnect) { + val volume: Float = androidAudioManager.ringVolumeWithMinimum() + soundPool.play(disconnectedSoundId, volume, volume, 0, 0, 1.0f) + } + + state = State.UNINITIALIZED + + wiredHeadsetReceiver?.let { receiver -> + try { + context.unregisterReceiver(receiver) + } catch (e: Exception) { + Log.e(TAG, "error unregistering wiredHeadsetReceiver", e) + } + } + wiredHeadsetReceiver = null + + signalBluetoothManager?.stop() + + setSpeakerphoneOn(savedIsSpeakerPhoneOn) + setMicrophoneMute(savedIsMicrophoneMute) + androidAudioManager.mode = savedAudioMode + + androidAudioManager.abandonCallAudioFocus() + Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams") + + Log.d(TAG, "Stopped") + } + + private fun updateAudioDeviceState() { + handler!!.assertHandlerThread() + + Log.i( + TAG, + "updateAudioDeviceState(): " + + "wired: $hasWiredHeadset " + + "bt: ${signalBluetoothManager!!.state} " + + "available: $audioDevices " + + "selected: $selectedAudioDevice " + + "userSelected: $userSelectedAudioDevice" + ) + + if (signalBluetoothManager!!.state.shouldUpdate()) { + signalBluetoothManager!!.updateDevice() + } + + val newAudioDevices = mutableSetOf(AudioDevice.SPEAKER_PHONE) + + if (signalBluetoothManager!!.state.hasDevice()) { + newAudioDevices += AudioDevice.BLUETOOTH + } + + if (hasWiredHeadset) { + newAudioDevices += AudioDevice.WIRED_HEADSET + } else { + autoSwitchToWiredHeadset = true + if (androidAudioManager.hasEarpiece(context)) { + newAudioDevices += AudioDevice.EARPIECE + } + } + + var audioDeviceSetUpdated = audioDevices != newAudioDevices + audioDevices = newAudioDevices + + if (signalBluetoothManager!!.state == BState.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { + userSelectedAudioDevice = AudioDevice.NONE + } + + if (hasWiredHeadset && autoSwitchToWiredHeadset) { + userSelectedAudioDevice = AudioDevice.WIRED_HEADSET + autoSwitchToWiredHeadset = false + } + + if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET) { + userSelectedAudioDevice = AudioDevice.NONE + } + + val btState = signalBluetoothManager!!.state + val needBluetoothAudioStart = btState == BState.AVAILABLE && + (userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) + + val needBluetoothAudioStop = (btState == BState.CONNECTED || btState == BState.CONNECTING) && + (userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH) + + if (btState.hasDevice()) { + Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager!!.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop") + } + + if (needBluetoothAudioStop) { + signalBluetoothManager!!.stopScoAudio() + signalBluetoothManager!!.updateDevice() + } + + if (!autoSwitchToBluetooth && signalBluetoothManager!!.state == BState.UNAVAILABLE) { + autoSwitchToBluetooth = true + } + + if (needBluetoothAudioStart && !needBluetoothAudioStop) { + if (!signalBluetoothManager!!.startScoAudio()) { + Log.e(TAG,"Failed to start sco audio") + audioDevices.remove(AudioDevice.BLUETOOTH) + audioDeviceSetUpdated = true + } + } + + if (autoSwitchToBluetooth && signalBluetoothManager!!.state == BState.CONNECTED) { + userSelectedAudioDevice = AudioDevice.BLUETOOTH + autoSwitchToBluetooth = false + } + + val newAudioDevice: AudioDevice = when { + audioDevices.contains(userSelectedAudioDevice) -> userSelectedAudioDevice + audioDevices.contains(defaultAudioDevice) -> defaultAudioDevice + else -> AudioDevice.SPEAKER_PHONE + } + + if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { + setAudioDevice(newAudioDevice) + Log.i(TAG, "New device status: available: $audioDevices, selected: $newAudioDevice") + eventListener?.onAudioDeviceChanged(selectedAudioDevice, audioDevices) + } + } + + private fun setDefaultAudioDevice(newDefaultDevice: AudioDevice, clearUserEarpieceSelection: Boolean) { + Log.d(TAG, "setDefaultAudioDevice(): currentDefault: $defaultAudioDevice device: $newDefaultDevice clearUser: $clearUserEarpieceSelection") + defaultAudioDevice = when (newDefaultDevice) { + AudioDevice.SPEAKER_PHONE -> newDefaultDevice + AudioDevice.EARPIECE -> { + if (androidAudioManager.hasEarpiece(context)) { + newDefaultDevice + } else { + AudioDevice.SPEAKER_PHONE + } + } + else -> throw AssertionError("Invalid default audio device selection") + } + + if (clearUserEarpieceSelection && userSelectedAudioDevice == AudioDevice.EARPIECE) { + Log.d(TAG, "Clearing user setting of earpiece") + userSelectedAudioDevice = AudioDevice.NONE + } + + Log.d(TAG, "New default: $defaultAudioDevice userSelected: $userSelectedAudioDevice") + updateAudioDeviceState() + } + + private fun selectAudioDevice(device: AudioDevice) { + val actualDevice = if (device == AudioDevice.EARPIECE && audioDevices.contains(AudioDevice.WIRED_HEADSET)) AudioDevice.WIRED_HEADSET else device + + Log.d(TAG, "selectAudioDevice(): device: $device actualDevice: $actualDevice") + if (!audioDevices.contains(actualDevice)) { + Log.w(TAG, "Can not select $actualDevice from available $audioDevices") + } + userSelectedAudioDevice = actualDevice + updateAudioDeviceState() + } + + private fun setAudioDevice(device: AudioDevice) { + Log.d(TAG, "setAudioDevice(): device: $device") + if (!audioDevices.contains(device)) return + when (device) { + AudioDevice.SPEAKER_PHONE -> setSpeakerphoneOn(true) + AudioDevice.EARPIECE -> setSpeakerphoneOn(false) + AudioDevice.WIRED_HEADSET -> setSpeakerphoneOn(false) + AudioDevice.BLUETOOTH -> setSpeakerphoneOn(false) + else -> throw AssertionError("Invalid audio device selection") + } + selectedAudioDevice = device + } + + private fun setSpeakerphoneOn(on: Boolean) { + if (androidAudioManager.isSpeakerphoneOn != on) { + androidAudioManager.isSpeakerphoneOn = on + } + } + + private fun setMicrophoneMute(on: Boolean) { + if (androidAudioManager.isMicrophoneMute != on) { + androidAudioManager.isMicrophoneMute = on + } + } + + private fun startIncomingRinger(vibrate: Boolean) { + Log.i(TAG, "startIncomingRinger(): vibrate: $vibrate") + androidAudioManager.mode = AudioManager.MODE_RINGTONE + + incomingRinger.start(vibrate) + } + + private fun silenceIncomingRinger() { + Log.i(TAG, "silenceIncomingRinger():") + incomingRinger.stop() + } + + private fun startOutgoingRinger(type: OutgoingRinger.Type) { + Log.i(TAG, "startOutgoingRinger(): currentDevice: $selectedAudioDevice") + + androidAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION + setMicrophoneMute(false) + + outgoingRinger.start(type) + } + + private fun onWiredHeadsetChange(pluggedIn: Boolean, hasMic: Boolean) { + Log.i(TAG, "onWiredHeadsetChange state: $state plug: $pluggedIn mic: $hasMic") + hasWiredHeadset = pluggedIn + updateAudioDeviceState() + } + + fun isSpeakerphoneOn(): Boolean = androidAudioManager.isSpeakerphoneOn + + fun isBluetoothScoOn(): Boolean = androidAudioManager.isBluetoothScoOn + + fun isWiredHeadsetOn(): Boolean = androidAudioManager.isWiredHeadsetOn + + private inner class WiredHeadsetReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val pluggedIn = intent.getIntExtra("state", 0) == 1 + val hasMic = intent.getIntExtra("microphone", 0) == 1 + + handler?.post { onWiredHeadsetChange(pluggedIn, hasMic) } + } + } + + enum class AudioDevice { + SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE + } + + enum class State { + UNINITIALIZED, PREINITIALIZED, RUNNING + } + + interface EventListener { + @JvmSuppressWildcards + fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt new file mode 100644 index 0000000000..84a36ee821 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt @@ -0,0 +1,364 @@ +package org.thoughtcrime.securesms.webrtc.audio + +import android.Manifest +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothHeadset +import android.bluetooth.BluetoothProfile +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioManager +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.AudioManagerCommand +import java.util.concurrent.TimeUnit + +/** + * Manages the bluetooth lifecycle with a headset. This class doesn't make any + * determination on if bluetooth should be used. It determines if a device is connected, + * reports that to the [SignalAudioManager], and then handles connecting/disconnecting + * to the device if requested by [SignalAudioManager]. + */ +class SignalBluetoothManager( + private val context: Context, + private val audioManager: SignalAudioManager, + private val androidAudioManager: AudioManagerCompat, + private val handler: SignalAudioHandler +) { + + var state: State = State.UNINITIALIZED + get() { + handler.assertHandlerThread() + return field + } + private set + + private var bluetoothAdapter: BluetoothAdapter? = null + private var bluetoothHeadset: BluetoothHeadset? = null + private var scoConnectionAttempts = 0 + + private val bluetoothListener = BluetoothServiceListener() + private var bluetoothReceiver: BluetoothHeadsetBroadcastReceiver? = null + + private val bluetoothTimeout = { onBluetoothTimeout() } + + fun start() { + handler.assertHandlerThread() + + Log.d(TAG, "start(): $state") + + if (state != State.UNINITIALIZED) { + Log.w(TAG, "Invalid starting state") + return + } + + bluetoothHeadset = null + scoConnectionAttempts = 0 + + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() + if (bluetoothAdapter == null) { + Log.i(TAG, "Device does not support Bluetooth") + return + } + + if (!androidAudioManager.isBluetoothScoAvailableOffCall) { + Log.w(TAG, "Bluetooth SCO audio is not available off call") + return + } + + if (bluetoothAdapter?.getProfileProxy(context, bluetoothListener, BluetoothProfile.HEADSET) != true) { + Log.e(TAG, "BluetoothAdapter.getProfileProxy(HEADSET) failed") + return + } + + val bluetoothHeadsetFilter = IntentFilter().apply { + addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED) + addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) + } + + bluetoothReceiver = BluetoothHeadsetBroadcastReceiver() + context.registerReceiver(bluetoothReceiver, bluetoothHeadsetFilter) + + Log.i(TAG, "Headset profile state: ${bluetoothAdapter?.getProfileConnectionState(BluetoothProfile.HEADSET)?.toStateString()}") + Log.i(TAG, "Bluetooth proxy for headset profile has started") + state = State.UNAVAILABLE + } + + fun stop() { + handler.assertHandlerThread() + + Log.d(TAG, "stop(): state: $state") + + if (bluetoothAdapter == null) { + return + } + + stopScoAudio() + + if (state == State.UNINITIALIZED) { + return + } + + bluetoothReceiver?.let { receiver -> + try { + context.unregisterReceiver(receiver) + } catch (e: Exception) { + Log.e(TAG,"error unregistering bluetoothReceiver", e) + } + } + bluetoothReceiver = null + + cancelTimer() + + bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset) + bluetoothHeadset = null + + bluetoothAdapter = null + state = State.UNINITIALIZED + } + + fun startScoAudio(): Boolean { + handler.assertHandlerThread() + + Log.i(TAG, "startScoAudio(): $state attempts: $scoConnectionAttempts") + + if (scoConnectionAttempts >= MAX_CONNECTION_ATTEMPTS) { + Log.w(TAG, "SCO connection attempts maxed out") + return false + } + + if (state != State.AVAILABLE) { + Log.w(TAG, "SCO connection failed as no headset available") + return false + } + + state = State.CONNECTING + androidAudioManager.startBluetoothSco() + scoConnectionAttempts++ + startTimer() + + return true + } + + fun stopScoAudio() { + handler.assertHandlerThread() + + Log.i(TAG, "stopScoAudio(): $state") + + if (state != State.CONNECTING && state != State.CONNECTED) { + return + } + + cancelTimer() + androidAudioManager.stopBluetoothSco() + androidAudioManager.isBluetoothScoOn = false + state = State.DISCONNECTING + } + + fun updateDevice() { + handler.assertHandlerThread() + + Log.d(TAG, "updateDevice(): state: $state") + + if (state == State.UNINITIALIZED || bluetoothHeadset == null) { + return + } + + if (bluetoothAdapter!!.getProfileConnectionState(BluetoothProfile.HEADSET) !in arrayOf(BluetoothProfile.STATE_CONNECTED)) { + state = State.UNAVAILABLE + Log.i(TAG, "No connected bluetooth headset") + } else { + state = State.AVAILABLE + Log.i(TAG, "Connected bluetooth headset.") + } + } + + private fun updateAudioDeviceState() { + audioManager.handleCommand(AudioManagerCommand.UpdateAudioDeviceState) + } + + private fun startTimer() { + handler.postDelayed(bluetoothTimeout, SCO_TIMEOUT) + } + + private fun cancelTimer() { + handler.removeCallbacks(bluetoothTimeout) + } + + private fun onBluetoothTimeout() { + Log.i(TAG, "onBluetoothTimeout: state: $state bluetoothHeadset: $bluetoothHeadset") + + if (state == State.UNINITIALIZED || bluetoothHeadset == null || state != State.CONNECTING) { + return + } + + var scoConnected = false + + if (audioManager.isBluetoothScoOn()) { + Log.d(TAG, "Connected with device") + scoConnected = true + } else { + Log.d(TAG, "Not connected with device") + } + + if (scoConnected) { + Log.i(TAG, "Device actually connected and not timed out") + state = State.CONNECTED + scoConnectionAttempts = 0 + } else { + Log.w(TAG, "Failed to connect after timeout") + stopScoAudio() + } + + updateAudioDeviceState() + } + + private fun onServiceConnected(proxy: BluetoothHeadset?) { + bluetoothHeadset = proxy + androidAudioManager.isBluetoothScoOn = true + updateAudioDeviceState() + } + + private fun onServiceDisconnected() { + stopScoAudio() + bluetoothHeadset = null + state = State.UNAVAILABLE + updateAudioDeviceState() + } + + private fun onHeadsetConnectionStateChanged(connectionState: Int) { + Log.i(TAG, "onHeadsetConnectionStateChanged: state: $state connectionState: ${connectionState.toStateString()}") + + when (connectionState) { + BluetoothHeadset.STATE_CONNECTED -> { + scoConnectionAttempts = 0 + updateAudioDeviceState() + } + BluetoothHeadset.STATE_DISCONNECTED -> { + stopScoAudio() + updateAudioDeviceState() + } + } + } + + private fun onAudioStateChanged(audioState: Int, isInitialStateChange: Boolean) { + Log.i(TAG, "onAudioStateChanged: state: $state audioState: ${audioState.toStateString()} initialSticky: $isInitialStateChange") + + if (audioState == AudioManager.SCO_AUDIO_STATE_CONNECTED) { + cancelTimer() + if (state == State.CONNECTING) { + Log.d(TAG, "Bluetooth audio SCO is now connected") + state = State.CONNECTED + scoConnectionAttempts = 0 + updateAudioDeviceState() + } else { + Log.w(TAG, "Unexpected state ${audioState.toStateString()}") + } + } else if (audioState == AudioManager.SCO_AUDIO_STATE_CONNECTING) { + Log.d(TAG, "Bluetooth audio SCO is now connecting...") + } else if (audioState == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { + Log.d(TAG, "Bluetooth audio SCO is now disconnected") + if (isInitialStateChange) { + Log.d(TAG, "Ignore ${audioState.toStateString()} initial sticky broadcast.") + return + } + updateAudioDeviceState() + } + } + + private inner class BluetoothServiceListener : BluetoothProfile.ServiceListener { + override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) { + if (profile == BluetoothProfile.HEADSET) { + handler.post { + if (state != State.UNINITIALIZED) { + onServiceConnected(proxy as? BluetoothHeadset) + } + } + } + } + + override fun onServiceDisconnected(profile: Int) { + if (profile == BluetoothProfile.HEADSET) { + handler.post { + if (state != State.UNINITIALIZED) { + onServiceDisconnected() + } + } + } + } + } + + private inner class BluetoothHeadsetBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED) { + val connectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED) + handler.post { + if (state != State.UNINITIALIZED) { + onHeadsetConnectionStateChanged(connectionState) + } + } + } else if (intent.action == BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED) { +// val connectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED) +// handler.post { +// if (state != State.UNINITIALIZED) { +// onAudioStateChanged(connectionState, isInitialStickyBroadcast) +// } +// } + } else if (intent.action == AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) { + val scoState: Int = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.ERROR) + handler.post { + if (state != State.UNINITIALIZED) { + onAudioStateChanged(scoState, isInitialStickyBroadcast) + } + } + } + } + } + + enum class State { + UNINITIALIZED, + UNAVAILABLE, + AVAILABLE, + DISCONNECTING, + CONNECTING, + CONNECTED, + ERROR; + + fun shouldUpdate(): Boolean { + return this == AVAILABLE || this == UNAVAILABLE || this == DISCONNECTING + } + + fun hasDevice(): Boolean { + return this == CONNECTED || this == CONNECTING || this == AVAILABLE + } + } + + companion object { + private val TAG = Log.tag(SignalBluetoothManager::class.java) + private val SCO_TIMEOUT = TimeUnit.SECONDS.toMillis(4) + private const val MAX_CONNECTION_ATTEMPTS = 2 + } +} + +private fun Int.toStateString(): String { + return when (this) { + BluetoothAdapter.STATE_DISCONNECTED -> "DISCONNECTED" + BluetoothAdapter.STATE_CONNECTED -> "CONNECTED" + BluetoothAdapter.STATE_CONNECTING -> "CONNECTING" + BluetoothAdapter.STATE_DISCONNECTING -> "DISCONNECTING" + BluetoothAdapter.STATE_OFF -> "OFF" + BluetoothAdapter.STATE_ON -> "ON" + BluetoothAdapter.STATE_TURNING_OFF -> "TURNING_OFF" + BluetoothAdapter.STATE_TURNING_ON -> "TURNING_ON" + else -> "UNKNOWN" + } +} + +private fun Int.toScoString(): String = when (this) { + AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> "DISCONNECTED" + AudioManager.SCO_AUDIO_STATE_CONNECTED -> "CONNECTED" + AudioManager.SCO_AUDIO_STATE_CONNECTING -> "CONNECTING" + AudioManager.SCO_AUDIO_STATE_ERROR -> "ERROR" + else -> "UNKNOWN" +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt new file mode 100644 index 0000000000..dc9f07d051 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt @@ -0,0 +1,11 @@ +package org.thoughtcrime.securesms.webrtc.data + +// get the video rotation from a specific rotation, locked into 90 degree +// chunks offset by 45 degrees +fun Int.quadrantRotation() = when (this % 360) { + in 315 .. 360, + in 0 until 45 -> 0 + in 45 until 135 -> 90 + in 135 until 225 -> 180 + else -> 270 +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/StateMachine.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/StateMachine.kt new file mode 100644 index 0000000000..9b60d5a563 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/StateMachine.kt @@ -0,0 +1,112 @@ +package org.thoughtcrime.securesms.webrtc.data + +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.data.State.Companion.CAN_DECLINE_STATES +import org.thoughtcrime.securesms.webrtc.data.State.Companion.CAN_HANGUP_STATES + +sealed class State { + object Idle : State() + object RemotePreOffer : State() + object RemoteRing : State() + object LocalPreOffer : State() + object LocalRing : State() + object Connecting : State() + object Connected : State() + object Reconnecting : State() + object PendingReconnect : State() + object Disconnected : State() + companion object { + + val ALL_STATES = arrayOf( + Idle, RemotePreOffer, RemoteRing, LocalPreOffer, LocalRing, + Connecting, Connected, Reconnecting, Disconnected + ) + + val CAN_DECLINE_STATES = arrayOf(RemotePreOffer, RemoteRing) + val PENDING_CONNECTION_STATES = arrayOf( + LocalPreOffer, + LocalRing, + RemotePreOffer, + RemoteRing, + Connecting, + ) + val OUTGOING_STATES = arrayOf( + LocalPreOffer, + LocalRing, + ) + val CAN_HANGUP_STATES = + arrayOf( + RemotePreOffer, + RemoteRing, + LocalPreOffer, + LocalRing, + Connecting, + Connected, + Reconnecting + ) + val CAN_RECEIVE_ICE_STATES = + arrayOf(RemoteRing, LocalRing, Connecting, Connected, Reconnecting) + } + + fun withState(vararg expectedState: State, body: () -> Unit) { + if (this in expectedState) { + body() + } + } + +} + +sealed class Event(vararg val expectedStates: State, val outputState: State) { + object ReceivePreOffer : + Event(State.Idle, outputState = State.RemotePreOffer) + + object ReceiveOffer : + Event(State.RemotePreOffer, State.Reconnecting, outputState = State.RemoteRing) + + object SendPreOffer : Event(State.Idle, outputState = State.LocalPreOffer) + object SendOffer : Event(State.LocalPreOffer, outputState = State.LocalRing) + object SendAnswer : Event(State.RemoteRing, outputState = State.Connecting) + object ReceiveAnswer : + Event(State.LocalRing, State.Reconnecting, outputState = State.Connecting) + + object Connect : Event(State.Connecting, State.Reconnecting, outputState = State.Connected) + object IceFailed : Event(State.Connecting, outputState = State.Disconnected) + object IceDisconnect : Event(State.Connected, outputState = State.PendingReconnect) + object NetworkReconnect : Event(State.PendingReconnect, outputState = State.Reconnecting) + object PrepareForNewOffer : Event(State.PendingReconnect, outputState = State.Reconnecting) + object TimeOut : + Event( + State.Connecting, + State.LocalRing, + State.RemoteRing, + State.Reconnecting, + outputState = State.Disconnected + ) + + object Error : Event(*State.ALL_STATES, outputState = State.Disconnected) + object DeclineCall : Event(*CAN_DECLINE_STATES, outputState = State.Disconnected) + object Hangup : Event(*CAN_HANGUP_STATES, outputState = State.Disconnected) + object Cleanup : Event(State.Disconnected, outputState = State.Idle) +} + +open class StateProcessor(initialState: State) { + private var _currentState: State = initialState + val currentState get() = _currentState + + open fun processEvent(event: Event, sideEffect: () -> Unit = {}): Boolean { + if (currentState in event.expectedStates) { + Log.i( + "Loki-Call", + "succeeded transitioning from ${currentState::class.simpleName} to ${event.outputState::class.simpleName} with ${event::class.simpleName}" + ) + _currentState = event.outputState + sideEffect() + return true + } + Log.e( + "Loki-Call", + "error transitioning from ${currentState::class.simpleName} to ${event.outputState::class.simpleName} with ${event::class.simpleName}" + ) + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/AccelerometerListener.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/AccelerometerListener.java new file mode 100644 index 0000000000..39afab0cb2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/AccelerometerListener.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thoughtcrime.securesms.webrtc.locks; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Message; + +import org.session.libsignal.utilities.Log; + +/** + * This class is used to listen to the accelerometer to monitor the + * orientation of the phone. The client of this class is notified when + * the orientation changes between horizontal and vertical. + */ +public final class AccelerometerListener { + private static final String TAG = "AccelerometerListener"; + private static final boolean DEBUG = true; + private static final boolean VDEBUG = false; + + private SensorManager mSensorManager; + private Sensor mSensor; + + // mOrientation is the orientation value most recently reported to the client. + private int mOrientation; + + // mPendingOrientation is the latest orientation computed based on the sensor value. + // This is sent to the client after a rebounce delay, at which point it is copied to + // mOrientation. + private int mPendingOrientation; + + private OrientationListener mListener; + + // Device orientation + public static final int ORIENTATION_UNKNOWN = 0; + public static final int ORIENTATION_VERTICAL = 1; + public static final int ORIENTATION_HORIZONTAL = 2; + + private static final int ORIENTATION_CHANGED = 1234; + + private static final int VERTICAL_DEBOUNCE = 100; + private static final int HORIZONTAL_DEBOUNCE = 500; + private static final double VERTICAL_ANGLE = 50.0; + + public interface OrientationListener { + public void orientationChanged(int orientation); + } + + public AccelerometerListener(Context context, OrientationListener listener) { + mListener = listener; + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + + public void enable(boolean enable) { + if (DEBUG) Log.d(TAG, "enable(" + enable + ")"); + synchronized (this) { + if (enable) { + mOrientation = ORIENTATION_UNKNOWN; + mPendingOrientation = ORIENTATION_UNKNOWN; + mSensorManager.registerListener(mSensorListener, mSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else { + mSensorManager.unregisterListener(mSensorListener); + mHandler.removeMessages(ORIENTATION_CHANGED); + } + } + } + + private void setOrientation(int orientation) { + synchronized (this) { + if (mPendingOrientation == orientation) { + // Pending orientation has not changed, so do nothing. + return; + } + + // Cancel any pending messages. + // We will either start a new timer or cancel alltogether + // if the orientation has not changed. + mHandler.removeMessages(ORIENTATION_CHANGED); + + if (mOrientation != orientation) { + // Set timer to send an event if the orientation has changed since its + // previously reported value. + mPendingOrientation = orientation; + Message m = mHandler.obtainMessage(ORIENTATION_CHANGED); + // set delay to our debounce timeout + int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE + : HORIZONTAL_DEBOUNCE); + mHandler.sendMessageDelayed(m, delay); + } else { + // no message is pending + mPendingOrientation = ORIENTATION_UNKNOWN; + } + } + } + + private void onSensorEvent(double x, double y, double z) { + if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")"); + + // If some values are exactly zero, then likely the sensor is not powered up yet. + // ignore these events to avoid false horizontal positives. + if (x == 0.0 || y == 0.0 || z == 0.0) return; + + // magnitude of the acceleration vector projected onto XY plane + double xy = Math.sqrt(x * x + y * y); + // compute the vertical angle + double angle = Math.atan2(xy, z); + // convert to degrees + angle = angle * 180.0 / Math.PI; + int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL); + if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation); + setOrientation(orientation); + } + + SensorEventListener mSensorListener = new SensorEventListener() { + public void onSensorChanged(SensorEvent event) { + onSensorEvent(event.values[0], event.values[1], event.values[2]); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // ignore + } + }; + + Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case ORIENTATION_CHANGED: + synchronized (this) { + mOrientation = mPendingOrientation; + if (DEBUG) { + Log.d(TAG, "orientation: " + + (mOrientation == ORIENTATION_HORIZONTAL ? "horizontal" + : (mOrientation == ORIENTATION_VERTICAL ? "vertical" + : "unknown"))); + } + mListener.orientationChanged(mOrientation); + } + break; + } + } + }; +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java new file mode 100644 index 0000000000..a7fac62bbc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java @@ -0,0 +1,148 @@ +package org.thoughtcrime.securesms.webrtc.locks; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.os.PowerManager; +import android.provider.Settings; + +import org.session.libsignal.utilities.Log; + +/** + * Maintains wake lock state. + * + * @author Stuart O. Anderson + */ +public class LockManager { + + private static final String TAG = LockManager.class.getSimpleName(); + + private final PowerManager.WakeLock fullLock; + private final PowerManager.WakeLock partialLock; + private final WifiManager.WifiLock wifiLock; + private final ProximityLock proximityLock; + + private final AccelerometerListener accelerometerListener; + private final boolean wifiLockEnforced; + + + private int orientation = AccelerometerListener.ORIENTATION_UNKNOWN; + private boolean proximityDisabled = false; + + public enum PhoneState { + IDLE, + PROCESSING, //used when the phone is active but before the user should be alerted. + INTERACTIVE, + IN_CALL, + IN_VIDEO + } + + private enum LockState { + FULL, + PARTIAL, + SLEEP, + PROXIMITY + } + + public LockManager(Context context) { + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full"); + partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial"); + proximityLock = new ProximityLock(pm); + + WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi"); + + fullLock.setReferenceCounted(false); + partialLock.setReferenceCounted(false); + wifiLock.setReferenceCounted(false); + + accelerometerListener = new AccelerometerListener(context, new AccelerometerListener.OrientationListener() { + @Override + public void orientationChanged(int newOrientation) { + orientation = newOrientation; + Log.d(TAG, "Orentation Update: " + newOrientation); + updateInCallLockState(); + } + }); + + wifiLockEnforced = isWifiPowerActiveModeEnabled(context); + } + + private boolean isWifiPowerActiveModeEnabled(Context context) { + int wifi_pwr_active_mode = Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1); + Log.d(TAG, "Wifi Activity Policy: " + wifi_pwr_active_mode); + + if (wifi_pwr_active_mode == 0) { + return false; + } + + return true; + } + + private void updateInCallLockState() { + if (orientation != AccelerometerListener.ORIENTATION_HORIZONTAL && wifiLockEnforced && !proximityDisabled) { + setLockState(LockState.PROXIMITY); + } else { + setLockState(LockState.FULL); + } + } + + public void updatePhoneState(PhoneState state) { + switch(state) { + case IDLE: + setLockState(LockState.SLEEP); + accelerometerListener.enable(false); + break; + case PROCESSING: + setLockState(LockState.PARTIAL); + accelerometerListener.enable(false); + break; + case INTERACTIVE: + setLockState(LockState.FULL); + accelerometerListener.enable(false); + break; + case IN_VIDEO: + proximityDisabled = true; + accelerometerListener.enable(false); + updateInCallLockState(); + break; + case IN_CALL: + proximityDisabled = false; + accelerometerListener.enable(true); + updateInCallLockState(); + break; + } + } + + private synchronized void setLockState(LockState newState) { + switch(newState) { + case FULL: + fullLock.acquire(); + partialLock.acquire(); + wifiLock.acquire(); + proximityLock.release(); + break; + case PARTIAL: + partialLock.acquire(); + wifiLock.acquire(); + fullLock.release(); + proximityLock.release(); + break; + case SLEEP: + fullLock.release(); + partialLock.release(); + wifiLock.release(); + proximityLock.release(); + break; + case PROXIMITY: + partialLock.acquire(); + proximityLock.acquire(); + wifiLock.acquire(); + fullLock.release(); + break; + default: + throw new IllegalArgumentException("Unhandled Mode: " + newState); + } + Log.d(TAG, "Entered Lock State: " + newState); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/ProximityLock.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/ProximityLock.java new file mode 100644 index 0000000000..ab91437c7d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/ProximityLock.java @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms.webrtc.locks; + +import android.os.PowerManager; + +import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.guava.Optional; + +/** + * Controls access to the proximity lock. + * The proximity lock is not part of the public API. + * + * @author Stuart O. Anderson + */ +class ProximityLock { + + private static final String TAG = ProximityLock.class.getSimpleName(); + + private final Optional proximityLock; + + ProximityLock(PowerManager pm) { + proximityLock = getProximityLock(pm); + } + + private Optional getProximityLock(PowerManager pm) { + if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { + return Optional.fromNullable(pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:proximity")); + } else { + return Optional.absent(); + } + } + + public void acquire() { + if (!proximityLock.isPresent() || proximityLock.get().isHeld()) { + return; + } + + proximityLock.get().acquire(); + } + + public void release() { + if (!proximityLock.isPresent() || !proximityLock.get().isHeld()) { + return; + } + + proximityLock.get().release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); + + Log.d(TAG, "Released proximity lock:" + proximityLock.get().isHeld()); + } + +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/Camera.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/Camera.kt new file mode 100644 index 0000000000..421c144199 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/Camera.kt @@ -0,0 +1,78 @@ +package org.thoughtcrime.securesms.webrtc.video + +import android.content.Context +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.video.CameraState.Direction.* +import org.webrtc.Camera2Enumerator +import org.webrtc.CameraEnumerator +import org.webrtc.CameraVideoCapturer + +class Camera(context: Context, + private val cameraEventListener: CameraEventListener): CameraVideoCapturer.CameraSwitchHandler { + + companion object { + private val TAG = Log.tag(Camera::class.java) + } + + val capturer: CameraVideoCapturer? + val cameraCount: Int + var activeDirection: CameraState.Direction = PENDING + var enabled: Boolean = false + set(value) { + field = value + capturer ?: return + try { + if (value) { + capturer.startCapture(1280,720,30) + } else { + capturer.stopCapture() + } + } catch (e: InterruptedException) { + Log.e(TAG,"Interrupted while stopping video capture") + } + } + + init { + val enumerator = Camera2Enumerator(context) + cameraCount = enumerator.deviceNames.size + capturer = createVideoCapturer(enumerator, FRONT)?.apply { + activeDirection = FRONT + } ?: createVideoCapturer(enumerator, BACK)?.apply { + activeDirection = BACK + } ?: run { + activeDirection = NONE + null + } + } + + fun dispose() { + capturer?.dispose() + } + + fun flip() { + if (capturer == null || cameraCount < 2) { + Log.w(TAG, "Tried to flip camera without capturer or less than 2 cameras") + return + } + activeDirection = PENDING + capturer.switchCamera(this) + } + + override fun onCameraSwitchDone(isFrontFacing: Boolean) { + activeDirection = if (isFrontFacing) FRONT else BACK + cameraEventListener.onCameraSwitchCompleted(CameraState(activeDirection, cameraCount)) + } + + override fun onCameraSwitchError(errorMessage: String?) { + Log.e(TAG,"onCameraSwitchError: $errorMessage") + cameraEventListener.onCameraSwitchCompleted(CameraState(activeDirection, cameraCount)) + + } + + private fun createVideoCapturer(enumerator: CameraEnumerator, direction: CameraState.Direction): CameraVideoCapturer? = + enumerator.deviceNames.firstOrNull { device -> + (direction == FRONT && enumerator.isFrontFacing(device)) || + (direction == BACK && enumerator.isBackFacing(device)) + }?.let { enumerator.createCapturer(it, null) } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/CameraEventListener.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/CameraEventListener.kt new file mode 100644 index 0000000000..d52342815b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/CameraEventListener.kt @@ -0,0 +1,18 @@ +package org.thoughtcrime.securesms.webrtc.video + +interface CameraEventListener { + fun onCameraSwitchCompleted(newCameraState: CameraState) +} + +data class CameraState(val activeDirection: Direction, val cameraCount: Int) { + companion object { + val UNKNOWN = CameraState(Direction.NONE, 0) + } + + val enabled: Boolean + get() = activeDirection != Direction.NONE + + enum class Direction { + FRONT, BACK, NONE, PENDING + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt new file mode 100644 index 0000000000..2b0caef89c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.webrtc.video + +import org.thoughtcrime.securesms.webrtc.data.quadrantRotation +import org.webrtc.VideoFrame +import org.webrtc.VideoSink + +class RemoteRotationVideoProxySink: VideoSink { + + private var targetSink: VideoSink? = null + + var rotation: Int = 0 + + override fun onFrame(frame: VideoFrame?) { + val thisSink = targetSink ?: return + val thisFrame = frame ?: return + + val quadrantRotation = rotation.quadrantRotation() + val modifiedRotation = thisFrame.rotation - quadrantRotation + + val newFrame = VideoFrame(thisFrame.buffer, modifiedRotation, thisFrame.timestampNs) + thisSink.onFrame(newFrame) + } + + fun setSink(videoSink: VideoSink) { + targetSink = videoSink + } + + fun release() { + targetSink = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt new file mode 100644 index 0000000000..ec43daf2ef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms.webrtc.video + +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.webrtc.data.quadrantRotation +import org.webrtc.CapturerObserver +import org.webrtc.VideoFrame +import org.webrtc.VideoProcessor +import org.webrtc.VideoSink +import java.lang.ref.SoftReference +import java.util.concurrent.atomic.AtomicBoolean + +class RotationVideoSink: CapturerObserver, VideoProcessor { + + var rotation: Int = 0 + var mirrored = false + + private val capturing = AtomicBoolean(false) + private var capturerObserver = SoftReference(null) + private var sink = SoftReference(null) + + override fun onCapturerStarted(ignored: Boolean) { + capturing.set(true) + } + + override fun onCapturerStopped() { + capturing.set(false) + } + + override fun onFrameCaptured(videoFrame: VideoFrame?) { + // rotate if need + val observer = capturerObserver.get() + if (videoFrame == null || observer == null || !capturing.get()) return + + val quadrantRotation = rotation.quadrantRotation() + + val newFrame = VideoFrame(videoFrame.buffer, (videoFrame.rotation + quadrantRotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1) % 360, videoFrame.timestampNs) + val localFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1, videoFrame.timestampNs) + + observer.onFrameCaptured(newFrame) + sink.get()?.onFrame(localFrame) + } + + override fun setSink(sink: VideoSink?) { + this.sink = SoftReference(sink) + } + + fun setObserver(videoSink: CapturerObserver?) { + capturerObserver = SoftReference(videoSink) + } +} \ No newline at end of file diff --git a/app/src/main/res/color/state_list_call_action_background.xml b/app/src/main/res/color/state_list_call_action_background.xml new file mode 100644 index 0000000000..c1a337ee76 --- /dev/null +++ b/app/src/main/res/color/state_list_call_action_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/state_list_call_action_foreground.xml b/app/src/main/res/color/state_list_call_action_foreground.xml new file mode 100644 index 0000000000..c312df2a58 --- /dev/null +++ b/app/src/main/res/color/state_list_call_action_foreground.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/state_list_call_action_mic_background.xml b/app/src/main/res/color/state_list_call_action_mic_background.xml new file mode 100644 index 0000000000..1e40a3a054 --- /dev/null +++ b/app/src/main/res/color/state_list_call_action_mic_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi-v24/ic_call_end_grey600_32dp.webp b/app/src/main/res/drawable-anydpi-v24/ic_call_end_grey600_32dp.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f2c8b5d6e9b2f020401da4259fc5c048c9bf231 GIT binary patch literal 834 zcmV-I1HJrGNk&FG0{{S5MM6+kP&iC20{{Rof50CQ_rxfYBq_Pe^+p{2r4Zd58Wc5> z6xE)*5r=;%C~71rsy%rl4*ybM7)g>6ZvG#4hkVFC7}qNXZW}3Dh6DE-@F3TFocD5^ z1A?WB5doqRk&FnCq>c!S6D>dj5FrDI(8D5%AO!RnF-C)IL=XoOSbzX@7fk`k;6q{n za1clg02VIKRZ#?}13pXuAwl43fGMP3dROoxfM9A2tWc~Y0u)J?@&5lqmTXA_+elBW z1cpsY%t&VDf&z?)6*?m9zPm&5L7|YaZJQZa-|kLr+dZzWt<&__Ms0qzZJTNSY|kb$ zlc^Qa{|SJH|6k8O8lLb>_RF$b3yX^jt=25R1<#4$s58ObGrb!8p%N)qdOwNW^F)& zHy68`jWaO!1%>!+WyctMIrS*ay6j#4VytZ_)Yc#;zV?FJQMhe|G5k1WwIJK3ujTpv zvmP!Zdxtx!x^uWU;xaMozfu0BkqKh+ngbt}rD|kY_cm*$R|V>7k5}gF8hVITsV5wa zeM0IDWy#Je@YwBBQb!B%;}cc~`JG7ld!`2gjxDa~5tOiV!-0w0FQmQ#+^^6={5wg{ z&Wa30f>^vmY4j(a75HPEhAE{5&kk%`Rdwx@A!E@XO7=Rszu;Me5xtt8*5KKLb_#6iY9t~$96VOUzYBh5z7}}IrNv%`??TSRHtPczy9YjecpqI!r z{=EPpy8oe3s(^k)(zoRZg4_?)CljElNIE*#Eg($OO1lo309_($s(%JTov77RssL?8 z((wB+2zL0p2AKeTMY8d62!z}9-iAzorIOo$h@C}8AmHP`PDE_WvH;`W<*c+=5VW&z Mz~U}E{QuT*0G#`P0{{R3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-anydpi-v24/ic_close_grey600_32dp.webp b/app/src/main/res/drawable-anydpi-v24/ic_close_grey600_32dp.webp new file mode 100644 index 0000000000000000000000000000000000000000..c1431deff98a38742dac3dc07b7bd94554b34a7d GIT binary patch literal 404 zcmV;F0c-wJNk&GD0RRA3MM6+kP&iD00RR9mf50CQXJDpn8@F%Sj=wY-X4AG&vgX_V z4>g;%jgmFr_J62?06-Gz3hy|`AZtKp0DyO=Nu=#gQvW}o;NrtEw;9~j+V&&tbbSY@ZTj894uij-8WFDWU=PARD&gp@FiZfvTg6ilm8(rHz87kG(yu>?!JH zA5(%|ssuY+8MezpY_)|LZJ-!&tNXr=^#g3}eG4!~bFs2qZnG22hyrGmfEiK1j1n;U z2A|+p;dg{TxA=R*zqj~zga2;f-%b9zjsG|J{}%q=5kX@RYPws`=CE~`pMAWF42w=y#L;w+L06+pKdjKA61c*X{2cQMe=L1Ab1cN~M z@Mi*L9{~^;+@!n%0}3;E&Y(I6KYlR2Q3U@I;UmF- z1gLJv(09)SL6eXnK!WUhZ$kh7KO)+@2|HALp&*usA};sd%g_zdwrx8b=^op*ZQEGe z-X~3GZQHhO@90O)v2XHBbAR0YYsbWy1bDY5f9cz-n zDjkU-uvuvhf$vI#1U@KL5x{AkU7-Zt=}0nxLZxv6_mwUaNK}$v-WPm8Da-rNpfo~Y zsnQsMdrA)pOzu%R{+9qsH0-Tln4{oZ4~lW_`5uDgdAEEI!E%vHRyJsUb;o)PqL27#*zC>F3vE*kcm8|^v5SmqyaVX!?qgJSBwKT}Bfh3E_p2Tu&f0TW2KaBKY9P5hE!N=Ytg6NUA^Z!7?*> zH_a!g-AwBL(m~i&GpPyJy0La-BRdA>SpeV>^~ z@1!gDZBgzF3LvcUIgpCqF5I_8{eB?fzTbfydgv&_7X8|Q!sa~=L>h0IrTJwZz7H&X z-tR;XuQ%CX%e)LQY(MQpr1{rBHrT=dH!B^ZUc$OS6`TRXO;MTw{a=?#% zKRMv{@IG=tz>% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/call_controls_background.xml b/app/src/main/res/drawable/call_controls_background.xml new file mode 100644 index 0000000000..34137af62c --- /dev/null +++ b/app/src/main/res/drawable/call_controls_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml index bab545a707..2a31b2ef3e 100644 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml +++ b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml @@ -3,7 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="?attr/colorControlNormal" + android:autoMirrored="true"> diff --git a/app/src/main/res/drawable/ic_baseline_call_24.xml b/app/src/main/res/drawable/ic_baseline_call_24.xml new file mode 100644 index 0000000000..567e303fc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_call_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_call_end_24.xml b/app/src/main/res/drawable/ic_baseline_call_end_24.xml new file mode 100644 index 0000000000..dd6ff40926 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_call_end_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_call_made_24.xml b/app/src/main/res/drawable/ic_baseline_call_made_24.xml new file mode 100644 index 0000000000..2183fbc7f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_call_made_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_call_missed_24.xml b/app/src/main/res/drawable/ic_baseline_call_missed_24.xml new file mode 100644 index 0000000000..28958d07ab --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_call_missed_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_call_received_24.xml b/app/src/main/res/drawable/ic_baseline_call_received_24.xml new file mode 100644 index 0000000000..05192f9f30 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_call_received_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml b/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml new file mode 100644 index 0000000000..951aa1f621 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_mic_off_24.xml b/app/src/main/res/drawable/ic_baseline_mic_off_24.xml new file mode 100644 index 0000000000..8e199f115a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_mic_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_videocam_24.xml b/app/src/main/res/drawable/ic_baseline_videocam_24.xml new file mode 100644 index 0000000000..340bff20c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_videocam_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml b/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml new file mode 100644 index 0000000000..7977a645c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml new file mode 100644 index 0000000000..fc41db8b9f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_volume_up_24.xml b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml new file mode 100644 index 0000000000..0db34695f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_incoming_call.xml b/app/src/main/res/drawable/ic_incoming_call.xml new file mode 100644 index 0000000000..da1a78fe50 --- /dev/null +++ b/app/src/main/res/drawable/ic_incoming_call.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_missed_call.xml b/app/src/main/res/drawable/ic_missed_call.xml new file mode 100644 index 0000000000..4d3aee2a09 --- /dev/null +++ b/app/src/main/res/drawable/ic_missed_call.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_outgoing_call.xml b/app/src/main/res/drawable/ic_outgoing_call.xml new file mode 100644 index 0000000000..ad27f18d52 --- /dev/null +++ b/app/src/main/res/drawable/ic_outgoing_call.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_outline_mic_none_24.xml b/app/src/main/res/drawable/ic_outline_mic_none_24.xml new file mode 100644 index 0000000000..2810bc9a2e --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_mic_none_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_mic_off_24.xml b/app/src/main/res/drawable/ic_outline_mic_off_24.xml new file mode 100644 index 0000000000..631ced9615 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_mic_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_videocam_24.xml b/app/src/main/res/drawable/ic_outline_videocam_24.xml new file mode 100644 index 0000000000..cce4a0b74f --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_videocam_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_videocam_off_24.xml b/app/src/main/res/drawable/ic_outline_videocam_off_24.xml new file mode 100644 index 0000000000..e3ebd9e65b --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_videocam_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml b/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml new file mode 100644 index 0000000000..f595e4f38b --- /dev/null +++ b/app/src/main/res/drawable/prominent_outline_button_medium_background_accent.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_webrtc.xml b/app/src/main/res/layout/activity_webrtc.xml new file mode 100644 index 0000000000..e1c7c21d78 --- /dev/null +++ b/app/src/main/res/layout/activity_webrtc.xml @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_call_bottom_sheet.xml b/app/src/main/res/layout/fragment_call_bottom_sheet.xml new file mode 100644 index 0000000000..ec555bc847 --- /dev/null +++ b/app/src/main/res/layout/fragment_call_bottom_sheet.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_conversation_call.xml b/app/src/main/res/menu/menu_conversation_call.xml new file mode 100644 index 0000000000..8ebfeb8c8d --- /dev/null +++ b/app/src/main/res/menu/menu_conversation_call.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/redphone_busy.mp3 b/app/src/main/res/raw/redphone_busy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..865005e5126291085599d965c2981de70835eae0 GIT binary patch literal 169612 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 C%rLe9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/redphone_outring.mp3 b/app/src/main/res/raw/redphone_outring.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7442bf7d5b404922c6eefab8730ff5ff47e8d747 GIT binary patch literal 97200 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

0rZb`x7$@vP2ShKzQF@Pe*5DmEWO0m^ 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 T@Bca4`u~7z|DVtQo)P~CU=H=K literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/webrtc_disconnected.mp3 b/app/src/main/res/raw/webrtc_disconnected.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..99851513155919048ade8967e9cc8834b09a4063 GIT binary patch literal 13884 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7375a81ba5..037c4186e0 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -40,6 +40,12 @@ #404040 #B3B3B3 + #DD353535 + #FFFFFF + #D8D8D8 + #171717 + #171717 + #5ff8b0 #26cdb9 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5243437970..7209f3ea51 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -12,6 +12,7 @@ 34dp 38dp + 54dp 22dp 4dp 26dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3205620dde..9b315026d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ No Delete Ban - Please wait... + Please wait… Save Note to Self Version %s @@ -99,6 +99,9 @@ %1$d of %2$d + Call Permissions Required + You can enable the \'Voice and video calls\' permission in the Privacy Settings. + Delete selected message? @@ -560,6 +563,7 @@ Ban and delete all Resend message Reply to message + Call Save attachment @@ -899,5 +903,26 @@ Closed Group Open Group You have a new message request + Connecting… + Incoming call + Deny call + Answer call + Call in progress + Cancel call + Establishing call + End call + Accept Call + Decline call + Voice and video calls + Allow access to accept voice and video calls from other users + Voice / video calls + The current implementation of voice / video calls will expose your IP address to the Oxen Foundation servers and the calling / called user + Call Missed + You missed a call because you need to enable the \'Voice and video calls\' permission in the Privacy Settings. + Session Call + Reconnecting… + Notifications + Having notifications disabled will prevent you from receiving calls, go to Session notification settings? + Dismiss diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1622da9e32..4ad16737c1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -83,6 +83,12 @@ @color/accent + + + + + +