Getting Started with Android Facade

Acrobits Facade SDK provides an easier way to interact with the native libSoftphone SDK. Facade SDK enables the most complex features such as the SDK setup, lifecycle registration, or calls to be handled automatically.

This section describes the steps to create a simple Android application using Facade SDK.

Installing Facade SDK

Ensure the Android Studio installation is complete. Then, to add Facade SDK to the Android Gradle project:

  1. Locate and open the project-level build.gradle file in the root directory of your Android project.

  2. Add Acrobits Maven repository in the repository block and include your application identifier and license key:

    1maven {
    2    url ""
    3    credentials {
    4        <username>
    5        <password>
    6    }


    • Replace <username> with your application identifier, <password> with your license key.

    • <password>, the 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.

  3. Add the libSoftphone dependency for the SDK in the application-level build.gradle file:

    1dependencies {
    2    implementation 'cz.acrobits.libsoftphone:facade:<version>'


    Replace <version> with the version of the SDK.

Initializing the SDK

The setup process for Facade SDK is simpler compared to that of the native SDK. To initialize and prepare Facade SDK for operation, set up the SDK configuration and then the runtime.

Setting up the SDK Configuration

In general, you only have to set up the configuration once during the lifetime of your application process.


Acrobits recommends you create a custom Application class and run the setup upon calling the onCreate method. The setup is fast as only a few constructors are called so the application startup time is not impacted.

Use the following configuration block to set up the SDK configuration. The configuration blocks follow a builder pattern and are not required to be in a specific order.

 2    [ context ],
 3    [ your license key ],
 4    configuration -> configuration
 5            .pushMessage(pushMessage -> {
 6                // push messaging setup
 7            })
 8            .preferences(preferences -> preferences
 9                // preferences setup
10            )
11            .account(account -> {
12                // account setup
13            })
14            .callbacks(callbacks -> {
15                // callbacks registration
16            })
17            .ui(ui -> {
18                // UI configuration
19            })
20            .notification(notification -> {
21                // notification configuration
22            })
23    );


The native methods of libSoftphone SDK are accessible only after calling the SoftphoneFacade.start() method. One of the affected classes by this is the cz.acrobits.ali.Xml class, which uses native code for fast parsing.


  • The license key can be obtained in the Acrobits licensing portal after completing step 1 in the Quick Start Guide.

  • Developers should at least implement the push message push message and call assets blocks for the SDK to handle outgoing and incoming calls.

  • All configuration blocks are lazily evaluated only when they are needed by Facade SDK.

The following sections explain in more detail the individual blocks within this configuration block.

The Push Message Block

Notifies the Facade SDK changes in push token and new push messages.

Go to the Incoming Calls Over Push section for more information to manage push notifications.

The Preferences Block

Changes the default values of individual preference keys.


1preferences -> {
2    preferences.overrideDefault(prefs -> prefs.userAgentOverride, "Acrobits DemoPhone")


Preferences are also accessible when configuring the runtime.

Refer to the Managing Preferences section for more information.

The Account Block

Configures the initial and default SIP account.

The account options are passed through cz.acrobits.ali.Xml.


The cz.acrobits.ali.Xml class uses native code to achieve fast parsing. However, the native methods are available only after SoftphoneFacade.start() is being called. Therefore, you need to construct the cz.acrobits.ali.Xml object inside a lambda expression. This is because the lambda expression is evaluated lazily and is executed only when SoftphoneFacade.start() is called.


1account -> {
2    // This is evaluated only after the native methods are loaded
4    Xml xml = new Xml("account");
5    xml.setAttribute(Account.Attributes.ID, "Testing account");
6    xml.setChildValue(Account.HOST, "");
7    account.set(xml);


Accounts are also accessible when configuring the runtime.

Refer to the Managing SIP Account section for more information on account management.

The Callbacks Block

Registers listeners for SDK events.


1Set<Disposable> disposables = new HashSet();
5callbacks -> {
6    disposables.add(callbacks.registration(regState -> {}))


Accounts are also accessible when configuring the runtime.

Refer to the Call Assets Delegate section for more information on callbacks.

The Call Assets Block

Manages the appearance of the active call screen, which refers to the user interface during a call. Provide Acrobits with all the assets for calls to enhance user experience.


1configuration.callAssets(new ExampleCallAssetsDelegate())

Refer to the Call Assets Delegate section for more information.

The UI Block

Configures the behavior of elements on the active call screen.


  • To customize the audio route option when the user presses the speaker button:

    1ui.speakerButtonPolicy(new CustomSpeakerButtonPolicy());

    CustomSpeakerButtonPolicy implements the UiDelegate.SpeakerButtonPolicy interface. By default, the CyclingSpeakerButtonPolicy is used.

  • The label on the incoming call screen displays the application name by default. To customize the label to display your brand name, pass the string to the ui.brandName() function:

    1ui.brandName("Your brand");
The Notification Block

Customizes all displayed notifications.


  • Facade SDK uses the default small icon as the app icon for all notifications. To override with another drawable icon, pass it to the notification.defaultSmallIcon function:

  • To customize the individual notifications, pass the drawable resources specific that is to a desired context and communication channel to the notification.inCallTemplate function:

    1notification.inCallTemplate((context, channelId) -> {
    2    NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
    3    builder.setSmallIcon(R.drawable.your_small_icon);
    4    return builder;


    This might cause Facade SDK to simultaneously override multiple notification properties.

SDK Lifecycle

Facade SDK has its own lifecycle, which can be gotten through .getLifecycle(), and transitions through the following states:

  • Initialized - the initial state, Facade is ready to be started.

  • Stopped - Facade stops. Do not start it yet.

  • Running - Facade is fully initialized and started.

Setting up the Runtime

After setting up the configuration, start Facade SDK by transitioning it from Initialized to Running.

  1. Call .start() to activate Facade SDK based on the configuration.


    • Reporting a push remote message to an Initialized Facade instance invokes .start().

    • We recommend you to call the .start() method in the Activity.onCreate() function.

    • You can call this method more than once as needed.

    The SDK begins to execute using the specified configuration and register the provided SIP account.

  2. Observe the registration state changes with the callbacks.registration(regState -> {}) function. You should also report the push token received from Firebase. Refer the Incoming Calls Over Push section for more information.

  3. To terminate the SDK, call .terminate(). This unregisters all accounts and shuts down the SDK.

    The SDK transitions to the Stopped state and then back to Initialized.

  4. To restart the SDK, call the .start() method.

    However, restart should only be done when the SDK reaches the Initialized state.

Foreground Service

Acrobits handles the transition from foreground to background, such as when the user leaves the app, to ensure a smooth user experience.

When the app transitions to the background, libSoftphone SDK unregisters from SIP and activates its SIPIS instance. To prevent the Android OS from terminating the process while waiting for backend responses, we register a foreground service to extend the process lifetime for a few seconds after the app moves to the background.

Foreground services are completely managed by libSoftphone SDK. By default, foreground services display a status bar notification to inform the user about ongoing tasks. In addition to the default OS notification that does not provide much information, the Facade SDK enables developers to customize the foreground service notification.

Customizing Foreground Service Notification

Use the Facade SDK updateStatusNotification() to customize the foreground service notification.


1// Example notification builder
2Notification.Builder builder = new Notification.Builder(context)
3        .setSmallIcon(R.drawable.icon_notification)
4        .setOngoing(true)
5        .setContentTitle("Title!")
6        .setContentText("Content!");


You need to pass a Notification.Builder so that we can change the notification channel.

Integrating with Firebase

This section describes how to enable push calls with the integration of Firebase Cloud Messaging (FCM), which allows incoming calls to be received even when the app is asleep or in the background.


Ensure you already set up the VoIP push notifications in both your project and server prior to enabling push notifications. If the setup is not completed yet, go to the Android Push notifications section for detailed instructions.

libSoftphone SDK is fully integrated with the SIPIS push ecosystem. When a softphone app goes to the background, SIPIS takes over the SIP registration on behalf of the app. When SIPIS gets an incoming call INVITE, it sends a high priority push notification to wakes up the app.

To learn more about Acrobits SIPIS, go to

Incoming Calls Over Push

SIPIS relies on FCM to deliver incoming call notifications to apps that are asleep or in the background. You are required to integrate FCM into your project. Learn the steps at

With FCM integration, you need to ensure the following for the push calls feature to work:

  • Reporting FCM token to Facade SDK.

  • Reporting RemoteMessages, also known as push payload, to Facade SDK.

The mechanisms of the token and push payload reporting, along with their associated components such as FirebaseViewModel and PushMessageDelegate, are discussed in the following sections.

Implementing FirebaseViewModel

The FirebaseViewModel can hold the FCM token and notify the SDK immediately about the token even if the SDK is initialized later in the app’s runtime.

We recommend developers to implement a view model to manage push-related events. This view model should expose LiveData for tokens, remote messages, and push test requests. An example of the FirebaseViewModel interface is as follows:

 1public interface FirebaseViewModel
 3    MutableLiveData<SoftphoneRemoteMessage> getRemoteMessages();
 5    MutableLiveData<String> getToken();
 7    MutableLiveData<Duration> getPushTestRequest();
 9    default void schedulePushTest(Duration delay)
10    {
11        getPushTestRequest().postValue(delay);
12    }
17firebaseViewModel = new FirebaseViewModelImpl();


The FirebaseViewModel class name comes from the MVVM architectural pattern, and its implementation does not have to inherit from androidx.lifecycle.ViewModel.

Getting the PushMessage Delegate

The PushMessage delegate is an entity within Facade SDK that processes incoming push tokens and remote messages.

To access the PushMessageDelegate, access the pushMessage block from the initial configuration builder of the SDK.

Facade SDK has its own RemoteMessage implementation called SoftphoneRemoteMessage. SoftphoneRemoteMessage handles remote messages independently of any specific Firebase package version. To convert the Firebase RemoteMessage to the SoftphoneRemoteMessage, use the following code snippet:

1RemoteMessage remoteMessage;
3SoftphoneRemoteMessage softphoneRemoteMessage = SoftphoneRemoteMessage.of(remoteMessage.getData(), remoteMessage.getPriority());

Using the FirebaseViewModel simplifies the process of integrating push notifications into the initial configuration of the SDK. The following example attaches the individual callbacks to the FirebaseViewModel:

1pushMessage -> {
2    firebaseViewModel.getPushToken().observeForever(pushMessage::onNewToken);
3    firebaseViewModel.getPushMessage().observeForever(pushMessage::onMessageReceived);
4    firebaseViewModel.getPushTestRequest().observeForever(pushMessage::schedulePushTest);

Reporting FCM Push Token

One of the FirebaseViewModel benefits is that it can receive and process the push token regardless of whether the token is obtained before or after the SDK initialization.

We recommend you configure the app to check and store the current FirebaseMessaging token when the app starts up. To do so, implement the following code:

 1void loadToken(FirebaseViewModel model)
 3    FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
 4        if (!task.isSuccessful())
 5            return;
 7        var token = task.getResult();
 8        if (!TextUtils.isEmpty(token))
 9            model.getToken().postValue(token);
10    });

Additionally, to capture token updates from FirebaseMessagingService and ensure that they are reported to the SDK, implement the following code:

 1public static class Acceptor extends FirebaseMessagingService
 4    ...
 6    public ViewModel getFirebaseViewModel()
 7    {
 8        ...
 9    }
11    @Override
12    public void onNewToken(@NotNull String token)
13    {
14        getFirebaseViewModel().getToken().postValue(token);
15    }

Reporting FCM Remote Messages

Ensuring the RemoteMessage payload to be delivered from your FirebaseMessagingService to the SDK is very important.

To capture token updates from FirebaseMessagingService and ensure that they are reported, implement the following code:

 1public static class Acceptor extends FirebaseMessagingService
 4    ...
 6    public ViewModel getFirebaseViewModel()
 7    {
 8        ...
 9    }
11    @Override
12    public void onNewToken(@NotNull String token)
13    {
14        getFirebaseViewModel().getToken().postValue(token);
15    }


Before reporting the newly received push payload, you must ensure that the Facade SDK instance is created prior via the create() method. Therefore, if you try to report the push payload to the instance in the CREATED state, it automatically invokes start() on that instance.

Once the push payload or the RemoteMessage is delivered to the SDK, the content, particularly the Selector field, is checked if it belongs to one of the stored accounts configured in the SDK. If it is, the SDK launches a foreground service and attempts to retrieve the call from SIPIS.

Testing the Push Call Implementation

Schedule a push test to validate if the communication with SIPIS functions correctly and that the delivery of push notifications to the device.

The following code snippet demonstrates the setup for testing push call implementation:

 1final MutableLiveData<Duration> mPushTestRequest = new MutableLiveData<>();
 2final Set<Disposable> mDisposables = new HashSet();
 4mSoftphone = SoftphoneFacade.create(
 5        context,
 6        licenseKey,
 7        configuration -> configuration
 8                .pushMessage(firebase -> {
 9                    ...
10                    firebaseViewModel.getPushTestRequest().observeForever(firebase :: schedulePushTest);
11                })
12                .callbacks(
13                        callbacks -> {
14                            mDisposables.add(callbacks.pushTest(new PushDelegate()
15                            {
16                                @Override
17                                public void pushTestArrived()
18                                {
19                                    Toast.makeText(getInstance(), "Push test arrived \u2714", Toast.LENGTH_LONG).show();
20                                }
22                                @Override
23                                public void pushTestScheduled(PushTestScheduleResult result)
24                                {
25                                    Toast.makeText(getInstance(), "Push test schedule status: " +, Toast.LENGTH_SHORT).show();
26                                }
27                            }));
28                        }
29                )

Push Token Reporter API

For deployments that require reporting selector, push token, and app ID to the backend, use to the Push Token Reporter API reference guide at This API is particularly relevant in deployments that do not rely on SIPIS for push call functionality.

Managing SIP Account

SIP accounts are specified in the 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

Creating a SIP Account

Set up SIP accounts using the cz.acrobits.ali.Xml class.

The following code snippets demonstrate different example setups for creating an account:

  • To include the following code with a basic configuration containing user name, password, and SIP domain in the Xml class:

    1Xml xml = new Xml("account");
    2xml.setAttribute(Account.Attributes.ID, "Testing account");
    3xml.setChildValue(Account.USERNAME, TESTING_URI);
    4xml.setChildValue(Account.PASSWORD, TestOnlyUtil.getTestingSipPassword());
    5xml.setChildValue(Account.HOST, "");
  • To supply the account configuration via String using an XML:

     1final String acct =
     2"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
     3"<account id=\"Testing account\">\n" +
     4"    <title>My account</title>\n" +
     5"    <username>1984</username>\n" +
     6"    <password>misscom</password>\n" +
     7"    <host></host>\n" +
     8"    <transport>udp</transport>\n" +
     9"    <expires>600</expires>\n" +
    10"    <dtmfOrder>\"rfc2833,audio\"</dtmfOrder>\n" +
    12Xml account = Xml.parse(acct);
  • To load an account configuration from an InputStream, specifically from the app’s assets:

    1Xml xml = Xml.parse(getAssets().open("account.xml"));

Saving an Account Configuration

You can configure to register a SIP account, as well as to set up and save a default account during the SDK initialization. To do so, use the account block to set this up in the initial SDK configuration.

  • Specify an Account.Attributes.ID attribute to give a unique identifier to an account. If this attribute is not specified in Xml, a unique one is generated for that account.

  • To initialize and modify accounts during the runtime of the SDK, use SoftphoneFacade.saveSingleAccount() and SoftphoneFacade.saveAccount(). These two methods differ as follows:

    • To save or update an account only, call the SoftphoneFacade.saveSingleAccount() method. This method ensures that only one SIP account is stored and used by the SDK.

    • To save or update multiple accounts, call the SoftphoneFacade.saveAccount() method. Calling this method with different XMLs and IDs results in multiple SIP accounts being registered by the SDK.


  • If the application uses an account only, use SoftphoneFacade.saveSingleAccount() to save or update accounts after the SDK initialization.

  • When you save a new account, the SDK registers the account asynchronously.

Updating Account Configuration

You can update an existing account configuration using Facade SDK.


  • Currently, Facade SDK does not provide an accessor for stored accounts. Developers should use the low-level Instance.Registration APIs to access and modify accounts.

  • When you update the configuration of an account, the SDK asynchronously re-registers that account. Meaning, any changes made to the account takes effect without requiring the SDK to be restarted.

The following code snippet illustrates changing the incoming call mode to push for the Testing account:

1AccountXml accountXml = Instance.Registration.getAccount("Testing account").clone();
2accountXml.mergeValue("icm", "push", MergeableNodeAttributes.gui());

To change the configuration of an account using libSoftphone SDK:

  1. Call the Instance.Registration.getAccount("Testing account").clone() method to clone the account you want to update.

  2. Use appropriate methods or properties of the AccountXml object to modify on the account configuration. In this example, the incoming call mode is changed to push by merging the value push into the icm attribute.

  3. Call the Instance.Registration.saveAccount(accountXml) method to save changes.

    When Instance.Registration.saveAccount(accountXml) is called with the Account.Attributes.ID matches the ID of an existing account, this is considered as updating that account configuration.

Deleting an Account

Developers can delete accounts by using the account ID in the SoftphoneFacade::removeAccount: method.

The following code snippet illustrates deleting the Testing account:

1mSoftphone = SoftphoneFacade.create(context, licenseKey, config);
3mSoftphone.removeAccount("Testing account");

Managing Preferences

The following section explains how to manage global SDK preferences, also known as preference keys. For managing account level preferences, refer to the Managing SIP Account section.

Preference Key Types

Facade SDK provides two types of preferences:

  • Read only - represented by the cz.acrobits.libsoftphone.Preferences.ROKey class.

  • Writable - represented by the cz.acrobits.libsoftphone.Preferences.Key class.

  • You can perform the following actions when the SDK is in the running state:

    • Read the values of both types of preferences.

    • Change the value of writable Preferences.Key.

  • The current value of a read-only Preference.ROKey cannot be changed.

  • The default values of both Preferences.Key and Preference.ROKey can be overridden using the preferences block in the initial SDK configuration.

Reading Preference Key Values

Use the get() method to retrieve the current value of a preference key.


Call get() after invoking SoftphoneFacade.start().

The following code snippet illustrates how to check if SIP traffic logging is enabled:

1SoftphoneFacade instance = SoftphoneFacadeImpl.getInstance();
2boolean enabled = instance.getPreferences().trafficLogging.get();
3android.util.Log.d("Preferences", String.format("Logging is %s", enabled ? "enabled" : "disabled" ));

Changing Preference Key Values

Use the set(Type value) method to change the value of editable preference keys represented by the Key class.


You are unable to change the value of read-only Preference.ROKey, but you can override their default values.

Using the example in the former section, the following code snippet illustrates enabling logging by modifying the trafficLogging preference key:

1SoftphoneFacade instance = SoftphoneFacadeImpl.getInstance();
2boolean enabled = instance.getPreferences().trafficLogging.set(true);

Observing Preference Changes

To observe changes to specific Preferences.Key, implement a PreferencesDelegate and register it with the Facade’s callbacks.

The following code snippet illustrates observing prefs.volumeBoostPlayback and handling its change, for example by updating a volume slider position.

 1Set<Disposable> disposables = new HashSet();
 2mSoftphone = SoftphoneFacade.create(context, licenseKey, config);
 5PreferencesDelegate delegate = new PreferencesDelegate() {
 6    @Override
 7    public void onChange(Preferences.Key key)
 8    {
10    }
13CallbacksBuilder callbacks = mSoftphone.getCallbacks();
15        prefs -> prefs.volumeBoostPlayback,
16        key ->
17        {
18            // handle the key change
19            // e.g. update volume slider position
20        }

Observing Events from the SDK

Developers can register callbacks to observe the desired events from the SDK.

Callback Groups

Callbacks are categorized into different groups based on the type or nature of the events they represent. The categorization makes it easier for developers to register and handle specific types of events.

Currently, the available callback groups are as follows:

  • audio

  • calls

  • preferences

  • pushTest

  • registration

Using Callbacks

When registering callbacks to observe the desired events from the SDK, you can selectively implement a delegate interface for the desired callback group and pass the instance of the delegate to that group. The following code snippet illustrates an example of implementing CallDelegate handle call-related events:

 1Set<Disposable> disposables = new HashSet();
 2mSoftphone = SoftphoneFacade.create(context, licenseKey, config);
 5CallDelegate delegate = new CallDelegate() {
 6    @Override
 7    public void callFinished(String remoteUri) {
 8        Log.d("CallDelegate", "Call finished");
 9    }

Callback registration returns a Disposable object. The Disposable object, lightweight and idempotent callable, provides a mechanism to unregister the previously registered callback.

To unregister the callback, use .dispose() on the returned Disposable.


The Disposable implements the same API as the disposables from RxJava. Go to for more information.

Managing Calls

Facade SDK handles calls by using your delegate implementation to manage the assets and information displayed during calls.

Call Assets Delegate

When configuring Facade SDK, provide an implementation of CallAssetsDelegate interface which can resolve assets required for calls, such as ringtones, contact information, and push notification titles. The CallAssetsDelegate has a collection of functions that map specific assets with a CallEvent object.

The following snippet code uses the ExampleCallAssetsDelegate class as an example implementation of the CallAssetsDelegate interface:

 1configuration.callAssets(new ExampleCallAssetsDelegate())
 5public class ExampleCallAssetsDelegate implements CallAssetsDelegate
 7    @Override
 8    public AssetFileDescriptor getRingtone(CallEvent callEvent)
 9    {
10        // load ringtone for the call
11    }
13    @Override
14    public ContactInfo getContactInfo(CallEvent callEvent)
15    {
16        return ContactInfo.of(
17                [ avatar image ],
18                [ display name shown under the avatar ],
19                [ avatar background color for transparent images ]
20        );
21    }
23    @Override
24    public String getPushNotificationTitle(CallEvent callEvent)
25    {
26        return getUriFromCallEvent(callEvent);
27    }

Call-Assets-Delegate Methods

Implement the following methods when implementing your CallAssetsDelegate class:

  • getRingtone() - provide a ringtone that plays for an incoming call.

  • getContactInfo() - provide information about the contact that displays on the incoming call screen and on the user interface during a call.

  • getPushNotificationTitle() - change the text displayed in the push notification title for the call.

Outgoing Calls

Once Facade SDK is initialized and the configured account is registered, placing an outgoing call is simple. As shown in the following snippet code, use the placeCall method on the SoftphoneFacade and pass the SIP address of the remote party as a parameter.

1private SoftphoneFacade mSoftphone;

When a call is placed, the SDK invokes your CallAssetsDelegate implementation to load information about the remote party. The active call screen is displayed automatically.

Incoming Calls

The SDK automatically handles incoming calls. The incoming call screen displays when the SDK receives a SIP INVITE or an incoming call Firebase notification. The SDK again uses your CallAssetsDelegate implementation to load information about the remote party on the incoming call screen.

Enabling Logging

Enable logging to capture important events and activities that occur within the SDK. By enabling the logger with Facade SDK, developers can determine whether to control the logging behavior at specific moments during runtime, or to start logging whenever the application starts operating.

The following snippet code illustrates the methods contained in the Logger interface:

1public interface Logger
3    CharSequence getLogs();
4    void startLogging();
5    void stopLogging();
6    boolean isLogging();

By default, the logger is disabled. There are two ways to enable the logger:

  • To enable the logger during runtime, call facade.getLogger() to access the logging controller. The controller allows you to control the logger and get current logs.

  • To enable the logger and start logging whenever the application runs, include the trafficLogging preference key in the configuration block. Setting the trafficLogging to true enables logging from the start, as shown in the following code snippet:

    2    .preferences(preferences -> preferences
    3        .overrideDefault(prefs -> prefs.trafficLogging, true)
    4    )

The logger is by default disabled, which means no logs are printed to LogCat, and the getLogs() function returns null. Once the logger is enabled, all logs are printed to LogCat, and you can access them with getLogs().

Accessing Telemetry

You can access telemetry information through Facade SDK and register to receive specific or all telemetry events.

  • To register for individual telemetry events, use the .on(Class, Consumer) function. The events all inherit from a common TelemetryEvent interface.

  • To listen for all events, use the .onAny(Consumer) function.

The following snippet code demonstrates examples of using these functions to log events:

1TelemetryProvider telemetry = mSoftphone.getTelemetry();
3telemetry.on(InAppCallErrorCaused.class, event -> android.util.Log.i("EVENT-ERR", event.getErrorMessage()));
5telemetry.onAny(anyEvent -> android.util.Log.i("EVENT", event));

All the events also implement the .toString() method to convert an event object to its string representation.