This is a library that handles the challenges involved in integrating VoIP functionality into the Android platform (hence Platform Integration Layer, or PIL for short).
No, it relies on AndroidVoIPLib. A library that we also maintain which currently uses Linphone as the underlying SIP technology.
- Starting and stopping the VoIP layer based on application state
- Managing registrations
- Managing call objects
- Running a foreground service
- Call notifications
- Incoming call notifications
- Responding to user input on notifications
- Integration with the Android Telecom Framework
- Audio Routing
- Bluetooth calling
- Responding to bluetooth headset input
- Fetching contact data
- Displaying activities appropriately
- Implement an activity to show an active call
- Implement an activity to show an incoming call
- [Optional] Implement a middleware class if required by your VoIP architecture
This repo contains an example application with implementations of the basic functionality of the library, please check that if there are any questions not answered by this document.
Install using Jitpack.io.
In your Application Class under the onCreate method, you must start the PIL:
val pil = startAndroidPIL {
ApplicationSetup (
applicationClass = this@Application
...
)
}
The ApplicationSetup class takes a number of parameters, these are all Application level options that provide the PIL with some information about how it can manage VoIP in your application. These parameters are expected to be static and won't change after your application is created.
Mandatory parameters:
- applicationClass = The context of your application, this will be used when we need a Context and also for application lifecycle tracking.
- activities = You must provide a CallActivity and an IncomingCallActivity, the user will be directed to these when interacting with a notification. The CallActivity will also be launched when appropriate unless you set automaticallyStartCallActivity to FALSE.
Optional parameters:
- logger = Receive logs from the PIL and the underlying VoIP library
- middleware = If your VoIP architecture uses a middleware, in that you use FCM notifications to wake the phone, you must provide an implementation of Middleware. While this isn't required, incoming calls will not work in the background without it.
It is possible to get an instance of the PIL at any point:
val pil = PIL.instance
To actually authenticate and make/receive calls you must authenticate your VoIP account, this is done by providing an Authentication objecto the PIL instance.
pil.auth = Auth(username = "", password = "" ...)
Where you decide to place this depends on the structure of your application and how the authentication details are recovered, but it is worth keeping in mind that updating the Auth object will trigger a re-registration so you probably do not want to update this constantly.
If you have the authentication details when starting the PIL, you should provide them in the application's onCreate method:
val pil = startAndroidPIL {
auth = Auth(username = "", password = "" ...)
ApplicationSetup (
applicationClass = this@Application
)
}
The following run-time permissions are essentially required by the application (as in, this serves no purpose without these permissions):
- CALL_PHONE
- RECORD_AUDIO
- READ_PHONE_STATE
The READ_CONTACTS is also used but not required.
pil.call("0123456789")
If configured correctly, everything else should be handled for you, including launching your activity.
To retrieve a call object, simply request it from the PIL instance:
val call: Call? = pil.call
This call object is immutable and is a snap-shot of the call at the time it was requested.
The PIL will emit events, when displaying a call you should also re-render when receiving the CALL_UPDATED event.
To listen to events, you should implement the PILEventListener interface.
pil.events.listen(this)
An example implementation that will display the call or close the activity depending on the event received:
override fun onEvent(event: Event) = when(event) {
is CallEvent.CallEnded -> {
if (pil.call == null) {
finish()
} else {
displayCall()
}
}
is CallEvent.CallUpdated -> displayCall()
else -> {}
}
There is a PIL class that can provide common call screen functionality that has been implemented as a LifecycleObserver.
It will handle:
- Proximity, to hide the screen if the user puts the phone close to their face.
- Turning on the screen when it is locked for an incoming call.
- Automatically registering events (as long as the Activity you provide implements the PILCallListener interface)
In your activities onCreate method simply add:
lifecycle.addObserver(CallScreenLifecycleObserver(this))
The audio state can be requested by querying:
val audioState: AudioState = pil.audio.state
Like the call object, this is also an immutable snap-shot at the time it was requested.
To check where you are currently routing audio simply call:
when (pil.audio.state.currentRoute) {
SPEAKER ->
PHONE -> )
BLUETOOTH ->
}
or if you need to know if Bluetooth is available:
pil.audio.state.availableRoutes.contains(AudioRoute.BLUETOOTH)
All call interactions can be found on the CallActions object which is accessed via the actions property on the PIL.
pil.actions.end()
pil.actions.toggleHold()
Audio is not necessarily directly tied to a call so it can be found under the audio property on the PIL.
pil.audio.mute()
pil.audio.routeAudio(AudioRoute.BLUETOOTH)
Preferences are intended to be options that may be configurable by the user. You can set preferences simply by replacing the Preferences object on the PIL instance. However, because it contains sensible defaults, it is recommended to make use of Kotlin's copy feature:
pil.preferences = pil.preferences.copy(useApplicationProvidedRingtone = true)
This means that you do not need to update all preferences when you make a change.
These preferences are not stored and will need to be loaded whenever the PIL is started, this can be done in the startAndroidPIL method:
val pil = startAndroidPIL {
auth = Auth(username = "", password = "" ...)
preferences = preferences.copy(useApplicationProvidedRingtone = prefs.getBoolean("use_application_provided_ringtone", false))
ApplicationSetup (
applicationClass = this@Application
)
}
The library contains colors.xml and strings.xml, your Application should override these if you wish to change the text and color of notifications.
The app ringtone can be changed by adding a sound file resource in the raw directory with the filename of "ringtone" (e.g. raw/ringtone.ogg).