Getting Started with Android libSoftphone¶
This section describes the steps to create a simple Android application using Acrobits libSoftphone SDK.
Setting up libSoftphone SDK¶
Ensure the Android Studio installation is complete. Then, to add libSoftphone SDK to the Android Gradle project:
Locate and open the project-level
build.gradle
file in the root directory of your Android project.Add Acrobits Maven repository in the repository block and include your application identifier and license key:
1maven { 2 url "https://maven.acrobits.net/repository/maven-releases/" 3 credentials { 4 username <APPLICATION_IDENTIFIER> 5 password <LICENSE_KEY> 6 } 7}Note
The license key -
<LICENSE_KEY>
- can be obtained from the portal after completing step 1 in the Quick Start Guide.We recommend using
dependencyResolutionManagement
to add the Maven repository.Add the libSoftphone dependency for the SDK in the application-level
build.gradle
file:1dependencies { 2 implementation 'cz.acrobits:libsoftphone:<version>' 3}Note
Replace
<version>
with the version of the SDK.
Java 8 Requirement¶
The SDK is largely marked with the @FunctionalInterface
annotation.
In such cases, Acrobits highly recommends using the Java 8 lambda syntax.
This documentation assumes you are using Java 8 or a higher version of the language.
Note
We highly recommend include Java 8 support to the build.gradle
, but this is not strictly required to do so.
For more information, go to Android Core library desugaring at developer.android.com/studio/write/java8-support.
Dependencies List¶
libSoftphone SDK doesn’t introduce any transitive dependencies to your project.
The compile-time and runtime dependencies can be found by inspecting the artifacts Project Object Model (POM) file.
Initializing the SDK¶
To initialize libSoftphone before using the SDK functionality provided by this, use cz.acrobits.libsoftphone.Instance
. This instance is the entry point to control libSoftphone SDK.
If you are not using the tools provided by the SDK before initializing the instance, go to step 2 to begin the initialization.
To initialize libSoftphone SDK:
Load the libSoftphone SDK library in your Android project by invoking the
Instance.loadLibrary
method.Important
You must perform step 1 if you need to use other classes from the SDK or the SDK’s tools before initializing the SDK instance. For example, if provisioning the instance if required during initialization, you must load the SDK library beforehand.
The library needs a valid context to be loaded successfully. The context typically refers to your activity or application instance, depending on the specific scenario in which the library is being loaded. For example, to load a library in the
onCreate
method, use the following code:1@Override 2void onCreate(@Nullable Bundle savedState) 3{ 4 Instance.loadLibrary(this); 5 super.onCreate(savedState); 6}After the library is loaded, you can use classes from the
cz.acrobits.ali
package.Classes from support package
cz.acrobits.ali.support
may be used even without loading the library.
To initialize the SDK instance, use
Instance.init
.
This step loads the SDK library automatically.
Use different overload versions of
Instance.init
method (cz.acrobits.libsoftphone.Instance.init
) based on the specific requirements of the application. To illustrate, consider using the following examples:
For the Demo SDK, initialize the
onCreate
method with the following code:1@Override 2void onCreate(@Nullable Bundle savedState) 3{ 4 Instance.init(this); 5 //Instance.setObserver(sListeners); // to observe events 6 super.onCreate(savedState); 7}For the libSoftphone SDK, where instance initialization requires a SaaS identifier, load the library, preparing the SaaS provisioning, and then initialize the library with the following code:
1@Override 2void onCreate(@Nullable Bundle savedState) 3{ 4 Instance.loadLibrary(this); 5 Xml provisioning = new Xml("provisioning"); 6 Xml saas = new Xml("saas"); 7 saas.replaceChild("identifier", SAAS_IDENTIFIER); 8 provisioning.replaceChild(saas); 9 Instance.init(this, provisioning); 10 //Instance.setObserver(sListeners); // to observe events 11 super.onCreate(savedState); 12}Another overload version of
Instance.init
allows you to pass the preference class (cz.acrobits.libsoftphone.Preferences
) or factory parameter.After the initialization, libSoftphone SDK loads stored preferences and accounts. Depending on the configuration and application state, the SDK then starts registering to SIP servers and listening for incoming calls.
Set an observer (
cz.acrobits.libsoftphone.Observer
) to receive notifications when certain events occur in the SDK. See Observing Events from SDK for more information.
Managing SIP Account¶
SIP accounts are specified in XML format.
To view all the recognized XML nodes and values that can be used to configure SIP accounts, go to the Account XML documentation at doc.acrobits.net/cloudsoftphone/account.html#account-xml.
Creating a SIP Account¶
Include the following code to create an account with basic configuration containing the username, password, and SIP domain:
1Xml account = new Xml("account");
2account.setAttribute(Account.Attributes.ID, "Test Account");
3account.setChildValue(Account.USERNAME, userName);
4account.setChildValue(Account.PASSWORD, password);
5account.setChildValue(Account.HOST, domain);
6Instance.Registration.saveAccount(new AccountXml(account, MergeableNodeAttributes.gui()));
Note the following:
An
Account.Attributes.ID
attribute must be specified to identify an account. If not, a unique one is generated for that account upon callingInstance.Registration.saveAccount()
.Calling
Instance.Registration.saveAccount()
with an XML that has the sameAccount.Attributes.ID
as an existing account replaces the existing account with the new values.If you save a new account with an
Account.Attributes.ID
that matches the ID of an existing account but different account configuration, the new account (re)registers asynchronously.
Enabling or Disabling a SIP Account¶
Use AccountXml.setEnabled(boolean enabled)
to enable or disable a SIP account, and then call Instance.Registration.saveAccount()
to apply changes:
1AccountXml accountXml = Instance.Registration.getAccount("Test Account").clone();
2accountXml.setEnabled(true);
3Instance.Registration.saveAccount(accountXml);
Default SIP Account¶
Every application should have a default account.
Default application account:
After disabling the default application account, set a new one.
If the application uses an account only, call the
Instance.Registration.setDefaultAccount(null)
method to set it as the default.If the application supports multiple accounts, call the
Instance.Registration.setDefaultAccount(accountId)
method and include a specific ID to set that account as the default.
Updating Account Configuration¶
To change the configuration of an account:
Call the
Instance.Registration.getAccount()
method to obtain a reference to theAccountXml
of the account you want to modify.Before making changes, call the
AccountXml.clone()
method to obtain a mutable reference to the account.Use appropriate methods or properties of the
AccountXml
object to make changes on the account configuration.Call the
Instance.Registration.saveAccount()
method to save changes.
The following code snippet illustrates changing the incoming call mode to push for the Test Account:
1AccountXml accountXml = Instance.Registration.getAccount("Test Account").clone();
2accountXml.mergeValue("icm", "push", MergeableNodeAttributes.gui());
3Instance.Registration.saveAccount(accountXml);
Deleting an Account¶
Call the Instance.Registration.deleteAccount(accountId)
method to delete accounts.
If you delete a default application account, you need set a new one. See the Default SIP Account section for the methods to set a default account.
Observing Events from SDK¶
Set an observer to receive notifications when desired events occur in the SDK.
Setting an Observer¶
Set an observer immediately after calling the cz.acrobits.libsoftphone.Instance.init()
method.
See the Initializing the SDK section for the details to initialize the SDK.
To observe events from the SDK, we recommend deriving a class from the cz.acrobits.support.lifecycle.Listeners
class:
1public static final Listeners sListeners = new Listeners()
2{
3 //******************************************************************
4 @Override
5 public @Nullable Object getRingtone(@NonNull CallEvent call)
6 //******************************************************************
7 {
8 return RingtoneManager.getRingtone(context, Settings.System.DEFAULT_RINGTONE_URI);
9 }
10};
Once you have an instance of your derived cz.acrobits.support.lifecycle.Listeners
class, set it as the observer by calling Instance.setObserver(sListeners)
:
1// Instance.init() ...
2Instance.setObserver(sListeners);
Registering Event Listeners¶
Call the cz.acrobits.support.lifecycle.Listeners.register()
method to register event listeners.
The cz.acrobits.support.lifecycle.Listeners
is not lifecycle-aware and uses hard-references.
Therefore, you must unregister your listeners manually by using the cz.acrobits.support.lifecycle.Listeners.unregister()
method .
Placing a Call¶
Follow the steps in this section to use the SIP calling function provided by libSoftphone SDK.
Create a
StreamParty
for the phone number to which you want to place a call.1StreamParty party = new StreamParty(phoneNumber); 2party.match(Instance.Registration.getDefaultAccountId()); // match number in contacts and normalize StreamParty fieldsCreate a
CallEvent
using theStreamParty
, set the account from which the call should be placed, and set your desired dial action.1CallEvent event = new CallEvent(party.toRemoteUser()); 2event.setAccount(Instance.Registration.getDefaultAccountId()); 3event.transients.put(CallEvent.Transients.DIAL_ACTION, DialAction.VOICE_CALL.id);Place a call. The call is considered successful when
Instance.Events.post()
returnsInstance.Events.PostResult.SUCCESS
.1int result = Instance.Events.post(event); 2if(res == Instance.Events.PostResult.SUCCESS) 3{ 4 Toast.makeToast(context, "Call placed succesfully", Toast.LENGTH_SHORT).show(); 5} 6else 7{ 8 Toast.makeToast(context, String.format("Call failed: %d", res), Toast.LENGTH_SHORT).show(); 9}
Managing Preferences¶
libSoftphone SDK provides the app-specific preferences function that uses to set up and manage the Preferences menu items in the application.
Initializing a Preference Key¶
Two ways to create and initialize a preference key in a class that extends the Preferences
class:
Add the code snippet
final Key<Boolean> trafficLogging = new Key<>("sipTrafficLogging")
, orAdd the code snippet
final Preferences.Key<Boolean> trafficLogging = new Preferences().new Key<>("sipTrafficLogging")
in anywhere of your code.
Reading Preference Key Values¶
Use the get()
method to retrieve the current value of a read-write Preferences.Key
or a read-only Preferences.ROKey
.
For example, to check if SIP traffic logging is enabled, use the following codes:
1if (Instance.preferences.trafficLogging.get())
2{
3 Toast.makeText(context, "Logging is enabled", Toast.LENGTH_SHORT).show();
4}
Changing Preference Key Values¶
Use the set()
method to change the value of a read-write Preferences.Key
.
For instance, to enable SIP traffic logging, use the following codes:
1Instance.preferences.trafficLogging.set(true);
Resetting a Preference Key¶
Use the Preferences.Key.reset()
method to reset a preference key to the default value.
Detecting Changes in Preference-Key Values¶
To detect changes in preference key values, attach a listener by implementing the OnSettingsChanged
interface:
1class SettingsListener implements OnSettingsChanged
2{
3 @Override
4 public void onSettingsChanged()
5 {
6 if (Instance.preferences.trafficLogging.hasChanged())
7 {
8 Toast.makeText(context, String.format("Logging is now set to %b", Instance.preferences.trafficLogging.get()), Toast.LENGTH_SHORT).show();
9 }
10 }
11}
12
13// ...
14
15sListeners.register(new SettingsListener());
The OnSettingsChanged.onSettingsChanged
method is also invoked whenever changes are made to SIP account configuration.
Using Call Event History¶
Use the Call Event History feature to store call history in the device’s local storage and to display a list of recent calls in your app. This feature is disabled by default.
To enable the Call Event History feature, add the following code in the app’s provisioning XML file:
1<root> 2 <saas> 3 <identifier>your license key</identifier> 4 </saas> 5 <prefKeys> 6 <prop name="historyStorageType" default="sql"/> 7 </prefKeys> 8</root>You can refer to the Initializing the SDK section to review the steps for supplying the provisioning file during SDK initialization.
Add the Call Event History listeners. Refer to the Setting an Observer section to set the Java or Kotlin observer for the SDK.
The following code snippet shows how to register an
onEventsChanged
listener for Android:1Listeners listeners = new Listeners() 2{ 3 @Nullable 4 @Override 5 public Object getRingtone(@NonNull CallEvent callEvent) 6 { 7 return null; 8 } 9}; 10// Instance.setObserver(sListeners) ... 11 12listeners.register((Listeners.OnEventsChanged) (changedEvents, changedStreams) -> { 13 // From here, you can trigger a refresh of the call history list in your app 14 15 // get changed events - only set if changedEvents.many is false 16 long[] changedIds = changedEvents.eventIds; // id's of changed events 17 // check if many events changed, if true, changedIds is null 18 boolean manyChanged = changedEvents.many; 19 // check if any call event changed 20 boolean callEventsChanged = changedStreams.streamKeys != null && Arrays.stream(changedStreams.streamKeys) 21 .collect(Collectors.toList()) 22 .contains(StreamQuery.legacyCallHistoryStreamKey()); 23}); 24 25// Register onMissingCalls listener 26listeners.register((Listeners.OnMissedCalls) callEvents -> { 27 // triggers in case of any new missing call entry(s) appears in history) 28 29 List<CallEvent> missingCallsUpdate = Arrays.stream(callEvents) 30 .collect(Collectors.toList()); // new missing calls detected (in most cases is one call) 31 32 // this can be used for display missing call notification for example 33}); 34 35// unregister listener later when not in use anymore 36// listeners.unregister(...); 37 38Instance.setObserver(listeners);To display the call history in your app, perform a query to the device’s local storage and fetch all call events according to your filter and paging requirements, as shown in the following code snipet:
1CallEventQuery query = new CallEventQuery(); // empty query for calls 2query.streamKey = StreamQuery.legacyCallHistoryStreamKey(); // set query only calls 3// apply a filter if needed, here we are filtering only missed calls 4query.resultMask = CallEvent.Result.MISSED; // set query only missing calls 5 6// optional paging for query results 7EventPaging paging = new EventPaging(); 8/*paging.after = ...; 9paging.before = ...; 10paging.olderThan = ...; 11paging.offset = ...; 12paging.limit = ...; 13etc...*/ 14 15// fetch the call events 16EventFetchResult result = Instance.Events.fetch(query, paging); 17 18// get missed calls count 19int totalMissingCalls = result.totalCount; 20// get missed calls list 21List<CallEvent> missingCalls = Arrays.stream(result.events) 22 .map(CallEvent.class::cast) 23 .collect(Collectors.toList());Use the simple
onMissedCalls
listener to show notifications for missed calls in your app. This listener is triggered when a new missed call is detected, including those reported to the app via Firebase push notifications, even when the app is not running.The following code snippet shows how to register the
onMissedCalls
listener and show a notification for each new missed call on Android:1listeners.register((Listeners.OnMissedCalls) callEvents -> { 2 // triggers in case of any new missing call entry(s) appears in history 3 4 List<CallEvent> missingCallsUpdate = Arrays.stream(callEvents) 5 .collect(Collectors.toList()); // new missing calls detected (in most cases is one call) 6 7 // this can be used for display missing call notification for example 8 CallEventQuery missingCallsQuery = new CallEventQuery(); // Query for total count of missing calls 9 query.streamKey = StreamQuery.legacyCallHistoryStreamKey(); 10 query.resultMask = CallEvent.Result.MISSED; 11 12 EventPaging missingCallsPaging = new EventPaging(); 13 missingCallsPaging.limit = 1; // we need only count so can limit page to one item 14 15 int missingCallsTotalCount = Instance.Events.fetch(missingCallsQuery, missingCallsPaging).totalCount; // getting total missing calls count 16 17 missingCallsUpdate.stream().forEach(call -> { 18 19 // showing notification for each new call 20 var builder = new NotificationCompat.Builder(this, CHANNEL_ID) 21 .setSmallIcon(R.drawable.notification_icon) 22 .setContentTitle("Missing calls total : " + missingCallsTotalCount) 23 .setContentText("New missing call : " + call.getRemoteUser().getDisplayName()) 24 .setPriority(NotificationCompat.PRIORITY_DEFAULT); 25 26 notificationManager.notify(NOTIFICATION_ID, builder.build()); 27 }); 28});
Accessing Badge information¶
The SDK provides a simple way to get badge inforation for missed calls.
To get started obtain an instance of cz.acrobits.libsoftphone.badge.BadgeService
from the SDKServices
service holder.
1BadgeService badgeService = SDKServices.get<BadgeService>();
Then request the badge count for the missed calls.
1int missedCallsBadgeCount = badgeService.getBadgeCount(new BadgeChannel.Calls().getAddress());
The SDK provides multiple badge sources (aka channels).
BadgeChannel.Calls
- for missed calls
BadgeChannel.VoiceMail
- for voicemails
BadgeChannel.Messages
- for messages
To reset the badge count for missed calls, use the following code:
1badgeService.clearCount(new BadgeChannel.Calls().getAddress());
The badge counts can also be observed through AndroidX LiveData.
1badgeService.getBadgeCountLiveData(new BadgeChannel.Calls().getAddress()).observe(this, badgeCount -> {
2 // update UI with new badge count
3});
Displaying Missed Call Notifications¶
Very often, you may want to display a notification for each new missed call.
To do this, register the Listeners.OnMissedCalls
listener and show a notification for each new missed call:
1Listeners.OnMissedCalls missedCallsListener = new Listeners.OnMissedCalls()
2{
3 @Override
4 public void onMissedCalls(CallEvent[] callEvents)
5 {
6 List<CallEvent> missingCallsUpdate = Arrays.stream(callEvents).collect(Collectors.toList());
7
8 // this is only an example, you should follow the latest best practices for notifications
9 missingCallsUpdate.stream().forEach(call -> {
10
11 var builder = new NotificationCompat.Builder(this, CHANNEL_ID)
12 .setSmallIcon(R.drawable.notification_icon)
13 .setContentTitle("Missing call from " + call.getRemoteUser().getDisplayName())
14 .setPriority(NotificationCompat.PRIORITY_DEFAULT);
15
16 notificationManager.notify(NOTIFICATION_ID, builder.build());
17 });
18 }
19};
20
21// Register the listener to your Listeners instance
22listeners.register(missedCallsListener);
Enabling Logging¶
Enable logging to capture important events and activities that occur within the SDK.
By default, the logger is disabled. To enable logging, set the
trafficLogging
preference totrue
, as shown in the following code snippet:1Instance.preferences.trafficLogging.set(true);To disable logging, set the
trafficLogging
preference tofalse
.To retrieve logs at any point, use the following code snippet:
1String log = Instance.Log.get()
Integrating with Firebase¶
To add the push calls functionality using both libSoftphone SDK and Firebase, add Firebase Cloud Messaging (FCM) to your project, and then enable push notifications.
The FCM integration allows incoming calls to be received even when the app is asleep or in the background. The details are explain in the SIPIS section at doc.acrobits.net/sipis/index.html.
Prerequisite: Setting Up Firebase Integration¶
Add FCM to your project prior to implementing push calls functionality. To do so, refer to the FCM documentation at firebase.google.com/docs/cloud-messaging/android/client for the details. Once you obtain the Firebase server key of the application, contact an Acrobits representative to upload the key to Acrobits servers. The Firebase server key is required for the push calls functionality to work.
Note
For more information on setting up the VoIP push notifications in both your project and server, go to the Android Push notifications section.
Once FCM is added to your project, go to the following section to implement the push calls functionality.
Enabling Push Notifications¶
The push notifications function is disabled by default in libSoftphone SDK. Modify the provisioning XML
file to enable and use this function with the application.
To set the SDK registration with the push servers, add the following snippet to the
prefKeys
node in your provisioning XML.1<prop name="sipisDisabled" default="0"/> 2<prop name="pushNotificationsEnabled" default="1"/>This snippet is passed to the
Instance.init
method to enable push notifications when libSoftphone SDK initializes.Once push notifications is enabled, the
Instance.init
method is called, and the SDK is running, perform the following steps to use the push functionality:
To set the incoming calls mode to push, use the following snippet:
1Instance.preferences.incomingCallsMode.set(IncomingCallsMode.PUSH);</code>To report the Firebase push token to the SDK, use the following snippet:
1FirebaseInstanceId.getInstance().getInstanceId() 2 .addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() 3 { 4 @Override 5 public void onComplete(@NonNull Task<InstanceIdResult> task) 6 { 7 // Get new Instance ID token 8 final String token = task.getResult().getToken(); 9 // Report token to the libSoftphone SDK 10 AndroidUtil.handler.post(() -> Instance.Notifications.Push.setRegistrationId(token)); 11 } 12 });When implementing the
FirebaseMessagingService
, overrideonMessageReceived
to notify libSoftphone SDK about incoming push messages with the following snippet :1@Override 2public void onMessageReceived(RemoteMessage remoteMessage) 3{ 4 AndroidUtil.rendezvous(new Runnable() 5 { 6 @Override 7 public void run() 8 { 9 // make sure libsoftphone is initialized - call Instance.loadLibrary and Instance.init if needed 10 Instance.Notifications.Push.handle(Xml.toXml("pushMessage", remoteMessage.getData()), remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH); 11 } 12 }); 13}To report the renewed Firebase push token to libSoftphone SDK, use the following snippet:
1@Override 2public void onNewToken(String s) 3{ 4 AndroidUtil.rendezvous(new Runnable() 5 { 6 @Override 7 public void run() 8 { 9 // make sure libsoftphone is initialized - call Instance.loadLibrary and Instance.init if needed 10 Instance.Notifications.Push.setRegistrationId(s); 11 } 12 }); 13}When the push notification of a call arrives, you are notified as in when regular calls through the registered
Listeners.OnNewCall
callbacks.
Testing Push Notifications¶
To test if push messaging are working, perform the following steps:
Include the following snippet to implement the
Listeners.OnPushTestArrived
interface and register the callback:1Listeners.OnPushTestArrived onPushTestArrived = (accountId) -> Toast.makeToast(context, "Push test arrived!", Toast.LENGTH_SHORT).show(); 2mListeners.register(onPushTestArrived);Schedule a push test using the following snippet:
1Instance.Notifications.Push.scheduleTest(null, 0);.
When the registered Listeners.OnPushTestArrived
callback is triggered and displays the “Push test arrived!” message, this indicates that push notifications are working correctly.