Android default dialer replacement (part II)
After implementing a default dialer replacement app in the first part of this series we should have an app that can be selected by the user as the default phone app, but if we select it, we might lose all our calls, because we didn’t implement a UI for it. In this post we will create a new Activity that listens to all the changes in the InCallService
and renders the state of the call, the phone number of the other call party and a couple of buttons to answer and reject/cancel a call.
InCallService callbacks
Remember in part I we implemented an InCallService
, but it was an empty implementation, the bare minimum to be able to qualify as dialer replacement.
This service is going to receive the callbacks about the state of the current call, so let’s implement the methods we need to.
We implemented methods to know when a call starts and when it ends. When the call starts we can start our Activity
to render the UI. In this method we receive a Call
instance which contains all the info relevant to the current call. We want to listen to changes in the state of this call, so we can register a Call.Callback
and send updates to the Activity
whenever the state changes. In the onCallRemoved
method we can just unregister the call callback.
Send call updates
In order to send call updates to the Activity
we chose Rx, since it seems very natural to have a stream of Call
objects generated from this InCallService
callbacks and then any body can subscribe to that stream and react to it.
We created a CallManager
object that will receive every Call
update and will transform them into a stream of Calls
.
- It’s an object so we can easily share the instance and call it both from the
Service
and from theActivity
. - We’ll use a
BehaviorSubject
to create the stream of calls so anybody can subscribe at any given moment and always receive the current call state. - We are also mapping the
Call
instance to aGsmCall
instance.GsmCall
is a model with only the information we’re going to render later so we simplify a bit the info received by theActivity
later.
Here are the GsmCall
data class and the corresponding mapper.
Now we will feed the stream from the InCallService
.
We’ll send updates from onCallAdded
, onCallRemoved
and onStateChanged
in the Call.Callback
. That way we won’t lose any update potentially useful for our UI.
Call screen
And now that we finished with the plumbing, we can start rendering stuff. You can have a look at the call Activity
layout XML in the repo, here’s just the layout preview so you get the idea and see the TextViews
and Buttons
we’re going to deal with.
And the Activity
itself.
We subscribe to call updates in the onResume
function and unsubscribe in onPause
like good Rx citizens. And in the updateView
function we just update the TextViews
with the state of the call and the phone number of the other party.
Finally, we want to be able to answer and reject calls from our UI, so let’s go back to CallManager
object and create a couple of new methods.
We need to call the corresponding accept/reject functions in a Call
instance, so we’re going to store one inside CallManager
and update it every time updateCall
is invoked. Since there might be no call at all at some moments, currentCall
will be nullable.
Check that we also added a couple of public functions, acceptCall
and cancelCall
. In the cancelCall
function we check the state of the current call and call Call.rejectCall
only if the state is “ringing”, and Call.disconnectCall
otherwise. In the answer method we need to indicate the videoState value
, since in this case we’re not supporting video calls (for now), we just use the same videoState
currentCall
has. Now we can call these two functions from CallActivity
when we click on the answer and cancel buttons respectively.
And that’s it, thanks for reading. If you have a look at the GitHub repo you will find some other details like a small trick to update the duration TextView
using Rx, or how to make the call screen go full-screen with Themes and hiding the bottom system navigation bar.