feat: implementing more WebRtcCallService.kt functions and handlers for actions as well as lifecycle

This commit is contained in:
jubb 2021-11-03 17:09:21 +11:00
parent 1af9b8ba46
commit 2e3f46ff9f
10 changed files with 450 additions and 115 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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?)
}
}

View File

@ -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")
}
}

View File

@ -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)
}

View File

@ -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);
}
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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")

View File

@ -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")