feat: implementing more WebRtcCallService.kt functions and handlers for actions as well as lifecycle
This commit is contained in:
parent
1af9b8ba46
commit
2e3f46ff9f
|
@ -1,21 +0,0 @@
|
|||
package org.thoughtcrime.securesms.dependencies
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface CallComponent {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun get(context: Context) = ApplicationContext.getInstance(context).callComponent
|
||||
}
|
||||
|
||||
fun audioManagerCompat(): AudioManagerCompat
|
||||
|
||||
}
|
|
@ -25,8 +25,8 @@ abstract class CallModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCallManager(@ApplicationContext context: Context, storage: Storage) =
|
||||
CallManager(context)
|
||||
fun provideCallManager(@ApplicationContext context: Context, storage: Storage, audioManagerCompat: AudioManagerCompat) =
|
||||
CallManager(context, audioManagerCompat)
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
|
|
|
@ -4,18 +4,21 @@ import android.app.Notification
|
|||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.content.ContextCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.thoughtcrime.securesms.dependencies.CallComponent
|
||||
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
||||
import org.thoughtcrime.securesms.webrtc.CallManager
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import java.sql.CallableStatement
|
||||
import org.session.libsession.utilities.FutureTaskListener
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.webrtc.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.properties.Delegates.observable
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WebRtcCallService: Service() {
|
||||
|
@ -23,37 +26,47 @@ class WebRtcCallService: Service() {
|
|||
@Inject lateinit var callManager: CallManager
|
||||
|
||||
companion object {
|
||||
private const val ACTION_UPDATE = "UPDATE"
|
||||
private const val ACTION_STOP = "STOP"
|
||||
private const val ACTION_DENY_CALL = "DENY_CALL"
|
||||
private const val ACTION_LOCAL_HANGUP = "LOCAL_HANGUP"
|
||||
private const val ACTION_CHANGE_POWER_BUTTON = "CHANGE_POWER_BUTTON"
|
||||
private const val ACTION_SEND_AUDIO_COMMAND = "SEND_AUDIO_COMMAND"
|
||||
const val ACTION_INCOMING_CALL = "CALL_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_IS_IN_CALL_QUERY = "IS_IN_CALL"
|
||||
|
||||
private const val EXTRA_UPDATE_TYPE = "UPDATE_TYPE"
|
||||
private const val EXTRA_RECIPIENT_ID = "RECIPIENT_ID"
|
||||
private const val EXTRA_ENABLED = "ENABLED"
|
||||
private const val EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND"
|
||||
const val ACTION_RESPONSE_MESSAGE = "RESPONSE_MESSAGE"
|
||||
const val ACTION_ICE_MESSAGE = "ICE_MESSAGE"
|
||||
const val ACTION_ICE_CANDIDATE = "ICE_CANDIDATE"
|
||||
const val ACTION_CALL_CONNECTED = "CALL_CONNECTED"
|
||||
const val ACTION_REMOTE_HANGUP = "REMOTE_HANGUP"
|
||||
const val ACTION_REMOTE_BUSY = "REMOTE_BUSY"
|
||||
const val ACTION_REMOTE_VIDEO_MUTE = "REMOTE_VIDEO_MUTE"
|
||||
const val ACTION_ICE_CONNECTED = "ICE_CONNECTED"
|
||||
|
||||
private const val INVALID_NOTIFICATION_ID = -1
|
||||
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"
|
||||
|
||||
private var lastNotificationId: Int = INVALID_NOTIFICATION_ID
|
||||
private var lastNotification: Notification? = null
|
||||
const val DATA_CHANNEL_NAME = "signaling"
|
||||
|
||||
const val INVALID_NOTIFICATION_ID = -1
|
||||
|
||||
fun update(context: Context, type: Int, callId: UUID) {
|
||||
val intent = Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_UPDATE)
|
||||
.putExtra(EXTRA_RECIPIENT_ID, callId)
|
||||
.putExtra(EXTRA_UPDATE_TYPE, type)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_STOP)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_ANSWER_CALL)
|
||||
|
||||
fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
|
||||
|
||||
|
@ -61,35 +74,157 @@ class WebRtcCallService: Service() {
|
|||
|
||||
fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) {
|
||||
val intent = Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_SEND_AUDIO_COMMAND)
|
||||
.setAction(ACTION_UPDATE_AUDIO)
|
||||
.putExtra(EXTRA_AUDIO_COMMAND, command)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun changePowerButtonReceiver(context: Context, register: Boolean) {
|
||||
@JvmStatic
|
||||
fun isCallActive(context: Context, resultReceiver: ResultReceiver) {
|
||||
val intent = Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_CHANGE_POWER_BUTTON)
|
||||
.putExtra(EXTRA_ENABLED, register)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
.setAction(ACTION_IS_IN_CALL_QUERY)
|
||||
.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastNotificationId: Int = INVALID_NOTIFICATION_ID
|
||||
private var lastNotification: Notification? = null
|
||||
|
||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||
private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener {
|
||||
startService(hangupIntent(this))
|
||||
}
|
||||
|
||||
private var callReceiver: IncomingPstnCallReceiver? = null
|
||||
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
||||
|
||||
@Synchronized
|
||||
private fun terminate() {
|
||||
stopForeground(true)
|
||||
callManager.stop()
|
||||
}
|
||||
|
||||
private fun isBusy() = callManager.isBusy(this)
|
||||
|
||||
private fun initializeVideo() {
|
||||
callManager.initializeVideo(this)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
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
|
||||
when {
|
||||
action == ACTION_INCOMING_CALL && isBusy() -> handleBusyCall(intent)
|
||||
action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent)
|
||||
action == ACTION_INCOMING_CALL -> handleIncomingCall(intent)
|
||||
action == ACTION_OUTGOING_CALL -> 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_BLUETOOTH_CHANGE -> handleBluetoothChange(intent)
|
||||
// action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChange(intent)
|
||||
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
|
||||
action == ACTION_REMOTE_VIDEO_MUTE -> handleRemoteVideoMute(intent)
|
||||
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
|
||||
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
|
||||
action == ACTION_ICE_CANDIDATE -> handleLocalIceCandidate(intent)
|
||||
action == ACTION_CALL_CONNECTED -> handleCallConnected(intent)
|
||||
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
|
||||
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
|
||||
}
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
callManager.initializeResources(this)
|
||||
// create audio manager
|
||||
registerIncomingPstnCallReceiver()
|
||||
registerWiredHeadsetStateReceiver()
|
||||
getSystemService(TelephonyManager::class.java)
|
||||
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
||||
// reset call notification
|
||||
// register uncaught exception handler
|
||||
// register network receiver
|
||||
// telephony listen to call state
|
||||
}
|
||||
|
||||
private fun registerIncomingPstnCallReceiver() {
|
||||
callReceiver = IncomingPstnCallReceiver()
|
||||
registerReceiver(callReceiver, IntentFilter("android.intent.action.PHONE_STATE"))
|
||||
}
|
||||
|
||||
private fun registerWiredHeadsetStateReceiver() {
|
||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver()
|
||||
registerReceiver(wiredHeadsetStateReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG))
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
callReceiver?.let { receiver ->
|
||||
unregisterReceiver(receiver)
|
||||
}
|
||||
callReceiver = null
|
||||
// unregister exception handler
|
||||
// shutdown audiomanager
|
||||
// unregister network receiver
|
||||
// unregister power button
|
||||
}
|
||||
|
||||
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 StateAwareListener<V>(
|
||||
private val expectedState: CallManager.CallState,
|
||||
private val expectedCallId: UUID,
|
||||
private val getState: ()->Pair<CallManager.CallState, UUID>): FutureTaskListener<V> {
|
||||
|
||||
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?)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,23 @@
|
|||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import android.content.Context
|
||||
import com.android.mms.transaction.MessageSender
|
||||
import android.telephony.TelephonyManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.dependencies.CallComponent
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
||||
import org.webrtc.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
|
||||
class CallManager(private val context: Context): PeerConnection.Observer,
|
||||
class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConnection.Observer,
|
||||
SignalAudioManager.EventListener,
|
||||
CallDataListener {
|
||||
|
||||
|
@ -22,19 +25,62 @@ class CallManager(private val context: Context): PeerConnection.Observer,
|
|||
STATE_IDLE, STATE_DIALING, STATE_ANSWERING, STATE_REMOTE_RINGING, STATE_LOCAL_RINGING, STATE_CONNECTED
|
||||
}
|
||||
|
||||
|
||||
val signalAudioManager: SignalAudioManager by lazy {
|
||||
SignalAudioManager(context, this, CallComponent.get(context).audioManagerCompat())
|
||||
sealed class StateEvent {
|
||||
data class AudioEnabled(val isEnabled: Boolean): StateEvent()
|
||||
data class VideoEnabled(val isEnabled: Boolean): StateEvent()
|
||||
data class CallStateUpdate(val state: CallState): StateEvent()
|
||||
}
|
||||
|
||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||
companion object {
|
||||
private val TAG = Log.tag(CallManager::class.java)
|
||||
val CONNECTED_STATES = arrayOf(CallState.STATE_CONNECTED)
|
||||
val PENDING_CONNECTION_STATES = arrayOf(
|
||||
CallState.STATE_DIALING,
|
||||
CallState.STATE_ANSWERING,
|
||||
CallState.STATE_LOCAL_RINGING,
|
||||
CallState.STATE_REMOTE_RINGING
|
||||
)
|
||||
val OUTGOING_STATES = arrayOf(
|
||||
CallState.STATE_DIALING,
|
||||
CallState.STATE_REMOTE_RINGING,
|
||||
CallState.STATE_CONNECTED
|
||||
)
|
||||
val DISCONNECTED_STATES = arrayOf(CallState.STATE_IDLE)
|
||||
}
|
||||
|
||||
|
||||
private val signalAudioManager: SignalAudioManager = SignalAudioManager(context, this, audioManager)
|
||||
|
||||
private val _audioEvents = MutableStateFlow(StateEvent.AudioEnabled(false))
|
||||
val audioEvents = _audioEvents.asSharedFlow()
|
||||
private val _videoEvents = MutableStateFlow(StateEvent.VideoEnabled(false))
|
||||
val videoEvents = _videoEvents.asSharedFlow()
|
||||
private val _remoteVideoEvents = MutableStateFlow(StateEvent.VideoEnabled(false))
|
||||
val remoteVideoEvents = _remoteVideoEvents.asSharedFlow()
|
||||
private val _connectionEvents = MutableStateFlow<StateEvent>(StateEvent.CallStateUpdate(CallState.STATE_IDLE))
|
||||
val connectionEvents = _connectionEvents.asSharedFlow()
|
||||
private var localCameraState: CameraState = CameraState.UNKNOWN
|
||||
private var microphoneEnabled = true
|
||||
private var remoteVideoEnabled = false
|
||||
private var bluetoothAvailable = false
|
||||
|
||||
private val currentCallState = (_connectionEvents.value as StateEvent.CallStateUpdate).state
|
||||
|
||||
private val networkExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
private val eglBase: EglBase = EglBase.create()
|
||||
private var eglBase: EglBase? = null
|
||||
|
||||
private var callId: UUID? = null
|
||||
private var recipient: Recipient? = null
|
||||
private var peerConnectionWrapper: PeerConnectionWrapper? = null
|
||||
private var dataChannel: DataChannel? = null
|
||||
|
||||
private val currentCallState: MutableStateFlow<CallState> = MutableStateFlow(CallState.STATE_IDLE)
|
||||
private val pendingOutgoingIceUpdates = ArrayDeque<IceCandidate>()
|
||||
private val pendingIncomingIceUpdates = ArrayDeque<IceCandidate>()
|
||||
|
||||
private var localRenderer: SurfaceViewRenderer? = null
|
||||
private var remoteRenderer: SurfaceViewRenderer? = null
|
||||
private var peerConnectionFactory: PeerConnectionFactory? = null
|
||||
|
||||
private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? {
|
||||
val deviceNames = enumerator.deviceNames
|
||||
|
@ -82,67 +128,99 @@ class CallManager(private val context: Context): PeerConnection.Observer,
|
|||
|
||||
}
|
||||
|
||||
fun isBusy(context: Context) = currentCallState != CallState.STATE_IDLE
|
||||
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE
|
||||
|
||||
fun initializeVideo(context: Context) {
|
||||
Util.runOnMainSync {
|
||||
val base = EglBase.create()
|
||||
eglBase = base
|
||||
localRenderer = SurfaceViewRenderer(context)
|
||||
remoteRenderer = SurfaceViewRenderer(context)
|
||||
|
||||
localRenderer?.init(base.eglBaseContext, null)
|
||||
remoteRenderer?.init(base.eglBaseContext, null)
|
||||
|
||||
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() {
|
||||
peerConnectionWrapper?.()
|
||||
peerConnectionWrapper?.dispose()
|
||||
peerConnectionWrapper = null
|
||||
}
|
||||
|
||||
fun setAudioEnabled(isEnabled: Boolean) {
|
||||
|
||||
currentCallState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
|
||||
peerConnectionWrapper?.setAudioEnabled(isEnabled)
|
||||
_audioEvents.value = StateEvent.AudioEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun setVideoEnabled(isEnabled: Boolean) {
|
||||
currentCallState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
|
||||
peerConnectionWrapper?.setVideoEnabled(isEnabled)
|
||||
_audioEvents.value = StateEvent.AudioEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSignalingChange(newState: PeerConnection.SignalingState) {
|
||||
|
||||
}
|
||||
|
||||
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {
|
||||
TODO("Not yet implemented")
|
||||
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState) {
|
||||
|
||||
}
|
||||
|
||||
override fun onIceConnectionChange(p0: PeerConnection.IceConnectionState?) {
|
||||
TODO("Not yet implemented")
|
||||
override fun onIceConnectionReceivingChange(receiving: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
override fun onIceConnectionReceivingChange(p0: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun onIceGatheringChange(newState: PeerConnection.IceGatheringState) {
|
||||
|
||||
override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onIceCandidate(p0: IceCandidate?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onIceCandidatesRemoved(p0: Array<out IceCandidate>?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onAddStream(p0: MediaStream?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onRemoveStream(p0: MediaStream?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onDataChannel(p0: DataChannel?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onRenegotiationNeeded() {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
}
|
||||
|
||||
override fun onAudioDeviceChanged(activeDevice: SignalAudioManager.AudioDevice, devices: Set<SignalAudioManager.AudioDevice>) {
|
||||
TODO("Not yet implemented")
|
||||
signalAudioManager.handleCommand(AudioManagerCommand())
|
||||
}
|
||||
|
||||
private fun CallMessage.iceCandidates(): List<IceCandidate> {
|
||||
|
@ -152,4 +230,36 @@ class CallManager(private val context: Context): PeerConnection.Observer,
|
|||
}
|
||||
}
|
||||
|
||||
private fun CallState.withState(vararg expected: CallState, transition: ()->Unit) {
|
||||
if (this in expected) transition()
|
||||
else Log.w(TAG,"Tried to transition state $this but expected $expected")
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
signalAudioManager.stop(currentCallState in OUTGOING_STATES)
|
||||
peerConnectionWrapper?.dispose()
|
||||
peerConnectionWrapper = null
|
||||
|
||||
localRenderer?.release()
|
||||
remoteRenderer?.release()
|
||||
eglBase?.release()
|
||||
|
||||
localRenderer = null
|
||||
remoteRenderer = null
|
||||
eglBase = null
|
||||
|
||||
_connectionEvents.value = StateEvent.CallStateUpdate(CallState.STATE_IDLE)
|
||||
localCameraState = CameraState.UNKNOWN
|
||||
recipient = null
|
||||
callId = null
|
||||
microphoneEnabled = true
|
||||
remoteVideoEnabled = false
|
||||
pendingOutgoingIceUpdates.clear()
|
||||
pendingIncomingIceUpdates.clear()
|
||||
}
|
||||
|
||||
fun initializeResources(webRtcCallService: WebRtcCallService) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
import org.webrtc.*
|
||||
|
@ -13,28 +14,14 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class CallViewModel @Inject constructor(private val callManager: CallManager): ViewModel() {
|
||||
|
||||
sealed class StateEvent {
|
||||
data class AudioEnabled(val isEnabled: Boolean): StateEvent()
|
||||
data class VideoEnabled(val isEnabled: Boolean): StateEvent()
|
||||
}
|
||||
|
||||
val audioEnabledState = MutableStateFlow(
|
||||
callManager.audioEnabled.let { isEnabled ->
|
||||
|
||||
}
|
||||
)
|
||||
val videoEnabledState = MutableStateFlow(
|
||||
callManager.videoEnabled.let { isEnabled ->
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
val localAudioEnabledState = callManager.audioEvents.map { it.isEnabled }
|
||||
val localVideoEnabledState = callManager.videoEvents.map { it.isEnabled }
|
||||
val remoteVideoEnabledState = callManager.remoteVideoEvents.map { it.isEnabled }
|
||||
// set up listeners for establishing connection toggling video / audio
|
||||
init {
|
||||
audioEnabledState.onEach { (enabled) -> callManager.setAudioEnabled(enabled) }
|
||||
callManager.audioEvents.onEach { (enabled) -> callManager.setAudioEnabled(enabled) }
|
||||
.launchIn(viewModelScope)
|
||||
videoEnabledState.onEach { (enabled) -> callManager.setVideoEnabled(enabled) }
|
||||
callManager.videoEvents.onEach { (enabled) -> callManager.setVideoEnabled(enabled) }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,4 +72,30 @@ class PeerConnectionWrapper(context: Context,
|
|||
peerConnection.addStream(mediaStream)
|
||||
}
|
||||
|
||||
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 setAudioEnabled(isEnabled: Boolean) {
|
||||
audioTrack.setEnabled(isEnabled)
|
||||
}
|
||||
|
||||
fun setVideoEnabled(isEnabled: Boolean) {
|
||||
videoTrack?.let { track ->
|
||||
track.setEnabled(isEnabled)
|
||||
camera.enabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import android.telephony.PhoneStateListener
|
|||
import android.telephony.TelephonyManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
@ -31,19 +32,38 @@ class NetworkReceiver: BroadcastReceiver() {
|
|||
@Inject
|
||||
lateinit var callManager: CallManager
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class PowerButtonReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
TODO("Not yet implemented")
|
||||
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: Thread.UncaughtExceptionHandler {
|
||||
companion object {
|
||||
private val TAG = Log.tag(ProximityLockRelease::class.java)
|
||||
}
|
||||
override fun uncaughtException(t: Thread, e: Throwable) {
|
||||
TODO("Not yet implemented")
|
||||
Log.e(TAG,"Uncaught exception - releasing proximity lock", e)
|
||||
// lockManager update phone state
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -126,7 +126,7 @@ class SignalAudioManager(private val context: Context,
|
|||
Log.d(TAG, "Started")
|
||||
}
|
||||
|
||||
private fun stop(playDisconnect: Boolean) {
|
||||
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")
|
||||
|
|
|
@ -45,6 +45,10 @@ class Camera(context: Context,
|
|||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
|
Loading…
Reference in New Issue