-
Notifications
You must be signed in to change notification settings - Fork 31
Handle call state for app crash or removed from recents
In this article we will discuss about how to terminate the call in case of certain situations as when an android application crashes or when the android application is removed from recent tasks list.
If not handled properly, and there is an ongoing call in progress, then certain events like the app crashing or the app being removed from the recent tasks list, doesn't terminate the call and the developer has to handle such scenarios. We have explained below how this can be achieved:
Using a global exception handler for your application, you can detect when there is a crash and perform any required cleanup.
In our Kitchen Sink sample application, we have created a custom class named GlobalExceptionHandler which does the same.
The GlobalExceptionHandler class extends the Thread.UncaughtExceptionHandler interface. This interface has a method uncaughtException(Thread t, Throwable e)
, which is triggered when the application throws any uncaught exception. You can set this exception handler by calling the Thread.setDefaultUncaughtExceptionHandler
method of the Thread
class.
The GlobalExceptionHandler will catch any uncaught exception and check if there is any ongoing call. If found, then the call will be terminated. This means that for 1-1 calls, the call will end for both parties and for meetings, the call will end from the user's side and the user will leave the meeting, but the meeting won't end.
In Kitchen Sink, we are setting the global listener when the call is connected and we unregister the listener when the call disconnects.
Also currently the exception handling cannot capture crashes that happens in the native code, i.e. within the .so files.
Example
override fun onConnected(call: Call?) {
// Sets an exception handler on the current thread object for any type of uncaught exception.
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler())
}
override fun onDisconnected(call: Call?, event: CallObserver.CallDisconnectedEvent?) {
Thread.setDefaultUncaughtExceptionHandler(null)
}
Check the GlobalExceptionHandler class for the implementation.
You can also set the exception handler before onConnected
event, by putting it inside the CallObserver.onStartRinging
event and inside the Call.onIncomingCall
callbacks, as discussed in the next section.
In order to detect when an android application is removed from the recent tasks list, we can use a foreground service. The most generic way to remove an application, is by pressing the home button, opening the recent tasks list and then swiping up the application which you intend to remove.
In cases when an ongoing call is present and the application is removed, then the call doesn't terminate and it needs to be manually ended from the developer side. Since in this case, the app will be removed from memory immediately, so trying to end the call inside the onDestroy
method of the activity/fragment won't neccessarily mean that the call will end, since the end call is an asynchronous process. So we can make use of a foreground service in this case. You can capture app removed action inside the onTaskRemoved(rootIntent: Intent?)
method of the service. Here you should check for any ongoing call and end it gracefully.
Start the foreground service when an incoming or outgoing call takes place. The best place to start the service in both the cases would be:
a. For incoming call, start the service inside the Phone.onIncomingCall(call: Call?)
listener, and
b. For outgoing call, start the service inside the CallObserver.onStartRinging(call:Call? , ringerType: Call.RingerType)
event.
NOTE: It is better to start the service when the call is in ringing state instead of the connected state, as it will make sure that the app is removed as soon as the call was dialled and was in the ringing state itself.
You can stop the service when the call is disconnected i.e. inside the CallObserver.onDisconnected(event: CallDisconnectedEvent?)
event.
NOTE: You might need to maintain a list of call objects for the above purpose. You can checkout the CallObjectStorage class in the Kitchen Sink example repository to see how that can be done.
Once you have the active call object inside the onTaskRemoved
method, you can perform the end call option by calling either one of the below mentioned APIs.
a. Call.reject(callback: CompletionHandler<Void>)
: Use this API when your call is in ringing state and the call direction is of type Call.Direction.Incoming
b. Call.hangup(callback: CompletionHandler<Void>)
: Use this API when the direction of the call is of type Call.Direction.Outgoing
or when it is of type Call.Direction.Incoming
but not in the ringing state.
You can determine the state of the call by calling Call.getStatus(): CallStatus
method.
In order to make sure that the call has ended, listen to the onDisconnected(event: CallDisconnectedEvent?)
event. Inside this event you can stop the service by calling stopSelf()
method of the service instance.
Example code snippets:
Start & Stop the foreground service.
var serviceIntent: CallManagementService? = null
webex.phone.setIncomingCallListener(object : Phone.IncomingCallListener {
override fun onIncomingCall(call: Call?) {
...
startCallMonitoringForegroundService()
}
})
webex.phone.dial("[email protected]", MediaOption.audioVideo(local, remote), CompletionHandler {
val call = it.data
call?.setObserver(object : CallObserver {
override fun onStartRinging(call: Call?, ringerType: Call.RingerType) {
...
startCallMonitoringForegroundService()
}
override fun onDisconnected(event: CallDisconnectedEvent?) {
// Perform cleanup and stop the service when call is diconnected.
serviceIntent?.let {
stopService(it)
serviceIntent = null
}
}
})
})
private fun startCallMonitoringForegroundService() {
serviceIntent = Intent(context, CallManagementService::class.java)
startForegroundService(serviceIntent)
}
The foreground service class
class CallManagementService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Start the foreground service
startForeground(1, buildNotification())
return START_STICKY
}
// This is called when the app is removed from recent tasks list.
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
val call = getOngoingCall()
if (call != null) {
endCall(call)
} else {
// Stop the service when no ongoing call is found.
stopSelf()
}
}
private fun getOngoingCall(): Call? {
var call: Call? = null
for (i in 0 until CallObjectStorage.size()) {
val callObj = CallObjectStorage.getCallObjectFromIndex(i)
val status = callObj?.getStatus()
if (status == Call.CallStatus.CONNECTED || status == Call.CallStatus.RINGING
|| status == Call.CallStatus.WAITING || status == Call.CallStatus.INITIATED) {
call = callObj
break
}
}
return call
}
private fun endCall(call: Call) {
call.setObserver(object : CallObserver {
override fun onDisconnected(event: CallObserver.CallDisconnectedEvent?) {
// Call has been disconnected successfully. Stop the service now.
stopSelf()
}
})
if (call.getDirection() == Call.Direction.INCOMING && call.getStatus() == Call.CallStatus.RINGING) {
call.reject {
if (it.isSuccessful) {
// Call rejected successfully. Waiting for call disconnected event
} else {
// Call reject failed, reason = it.error?.errorMessage
stopSelf()
}
}
} else {
call.hangup { result ->
if (result.isSuccessful) {
// Call hung up success. Waiting for call disconnected event
} else {
// Call hangup failed, reason = it.error?.errorMessage
stopSelf()
}
}
}
}
}