Skip to content

Client Usage

The Client Usage classes allow you to enable your mobile applications to collect and upload client usage data. Administrators can use the SAP mobile service cockpit to view reports or download client usage data and create custom reports using this data. See Usage Reporting in the mobile services documentation for more information on the server and its functionality.

There are three options for managing sessions:

  • UsageService - A facade introduced in SAP BTP SDK for Android version 3.0.0. It builds on the two layers below to provide a simpler way to manage the sessions, usage upload, and other tasks.

  • UsageBroker - A higher-level convenience helper that builds on AppUsage. It simplifies the initialization, collection, storage, and uploading of usage data. It can be enabled to automatically capture lifecycle events such as "Session/Event start/stop". The UsageBroker is configurable for a multi-user environment within an application.

  • AppUsage - Works with direct access to the lower-level objects. This option provides the ability to directly control each component but requires greater effort to set up and maintain.

Note

Only one usage provider should be initialized at a time.

Using the UsageService Facade

UsageService provides auto-session and auto-upload features, which are both enabled by default, and it also defines a default usage store name and the default threshold of usage records (10KB) to trigger the auto-upload process. If you want to use a different store name, or don't want to use the auto-upload feature, you can use the APIs before providing the UsageService instance to the SDKInitializer.start method for initialization.

public UsageService setStoreName(@NonNull UUID storeName) {...}
public UsageService setAutoSession(@NonNull boolean autoSession) {...}
public UsageService setAutoUpload(@NonNull boolean autoUpload) {...}
public UsageService setUploadThresholdKB(@NonNull int sizeOfKB) {...}

The client code may look like:

UsageService usageService = new UsageService();
usageService
        .setStoreName(UUID.fromString("xxxxxxx"))
        .setAutoSession(true)
        .setUploadThresholdKB(20);
SDKInitializer.INSTANCE.start(application, new MobileService[]{usageService}, null);
val usageService = UsageService().apply {
    this.setStoreName(UUID.fromString("xxxxxxx"))
            .setAutoSession(true)
            .setUploadThresholdKB(20)
}
SDKInitializer.start(application, usageService)

For the auto-session feature, session start and end events will be recorded when the application is brought up to the foreground and put into the background respectively. If you want to manage your own usage session, you can disable the auto-session feature in UsageService, then call the 2 methods below to start/end the usage session in your application. If you want to use the auto upload feature, please use the method recordSessionEnd below instead of the corresponding version in AppUsage.

fun recordSessionStart(context: Context) { ... }
fun recordSessionEnd(context: Context) { ... }

If you want to use the auto-upload feature without the auto-session feature, please make sure to call the above method when recording the session end.

UsageService also provides APIs to help the client code log events.

fun logDeviceInfoEvents(context: Context) { ... }
fun eventBehaviorUserInteraction(
        viewId: String, elementId: String? = null,
        action: String? = null, interactionValue: String? = null
    ) { ... }
fun eventBehaviorViewDisplayed(
        viewId: String, elementId: String? = null,
        action: String? = null, interactionValue: String? = null
    ) { ... }
fun eventStart(info: AppUsageInfo, type: String? = null, key: String? = null) { ... }
fun eventEnd(info: AppUsageInfo, type: String? = null, key: String? = null) { ... }
fun event(
        info: AppUsageInfo, type: String? = null, key: String? = null, duration: Long? = null
    ) { ... }

The table below provides an overview of the APIs.

API Description
logDeviceInfoEvents Logs the device-related information: screen size, orientation, memory, etc.
eventBehaviorUserInteraction Logs an action performed by the users: button clicks, menu item clicks, etc.
eventBehaviorViewDisplayed Logs the screen navigations, for example when an Activity is being displayed.
eventStart Starts a custom event. When used together with eventEnd, the duration of this event will automatically be recorded.
eventEnd Ends a custom event. The duration of the event will automatically be recorded if eventStart with the same key was called before.
event Logs a custom event. The client code is responsible for the duration of this event.

Using the UsageBroker Convenience Helper

To collect, store, and upload client usage event data:

  1. Start the broker. Add this call to the application's onCreate().
  2. Collect any events. Additional analytic data can be captured and reported to the server using the AppUsage class.
  3. Call Upload (anywhere and often). It will only upload at the requested frequency.

Start UsageBroker

try {
    // In your Application class, start the UsageBroker
    UsageBroker.start(this, getApplicationContext(), "1.0", true);
} catch (Exception ex) {
    sLogger.error(ex.getMessage());
}
try {
    // In your Application class, start the UsageBroker
    UsageBroker.start(this, getApplicationContext(), "1.0", true)
} catch (Exception ex) {
    sLogger.error(ex.getMessage())
}

Multi-User Support for UsageBroker

When developing a multi-user app, the UsageBroker can be reconfigured on a per-user basis. This introduces the concept of "unattributed" and "attributed" stores. The unattributed store is used to capture all usage events when a user has not been onboarded. The attributed store is used for all usage events once a user has been onboarded successfully.

By calling one of the provided configure() methods, you can reconfigure the UsageBroker to switch between existing and new users. When moving to an attributed user, call the configure() method passing in a UUID (Universal Unique Identifier). This creates or opens the store associated with this UUID, which should uniquely identify a user.

Likewise, to switch to an unattributed user, call one of the configure() overloads that does not specify a UUID. You can also optionally pass in an encryption key when calling configure for an attributed user. This key should remain constant after the initial call, otherwise an OpenFailureException is thrown.

The retainLastUnattributedSession parameter defaults to true if not provided when calling configure(). This is useful for scenarios when a user is being onboarded. When configure() is called and you are switching from an unattributed store to an attributed store, this will take all the events from the beginning of the onboarding session and transfer them to the newly onboarded user's usage store.

// Configure UsageBroker for an attributed user
try {
    UsageBroker.configure(context, uuid);
} catch (OpenFailureException | EncryptionError e) {
    // Handle Error
}
try {
    UsageBroker.configure(context, uuid)
} catch (e: OpenFailureException) {
    // Handle error
} catch (e: EncryptionError) {
    // Handle error
}

Collect Any Events

See Reporting Event Metrics for more information about custom events.

// Single event call.
AppUsage.event(
    "eventType1",          // Event type, optional.
    "testEvent1Key",       // Event key. When not specified, `other` will be used.
    4L,                    // Duration.
    new AppUsageInfo()     // Event metrics using AppUsageInfo.
        .screen("firstScreen")
        .value("randomValue")
        .category("category1");
// Single event call.
AppUsage.event(
        "eventType1", // Event type, optional.
        "testEvent1Key", // Event key. When not specified, `other` will be used.
        4L, // Duration.
        AppUsageInfo()     // Event metrics using AppUsageInfo.
                .screen("firstScreen")
                .value("randomValue")
                .category("category1"))

See AppUsage Behavior Events for more information about Behavior events.

if(AppUsage.isInitialized()){
    AppUsage.eventBehaviorViewDisplayed("MainActivity", "", "", "");
}

if(AppUsage.isInitialized()){
    AppUsage.eventBehaviorUserInteraction("MainActivity","FloatingActionButton","Button Pressed", "");
}
if (AppUsage.isInitialized()) {
    AppUsage.eventBehaviorViewDisplayed("MainActivity", "", "", "")
}

if (AppUsage.isInitialized()) {
    AppUsage.eventBehaviorUserInteraction("MainActivity", "FloatingActionButton", "Button Pressed", "")
}

Upload Using UsageBroker

Uploading using the UsageBroker gives you convenience mechanisms that are not available when using the underlying components. The UsageBroker can control upload frequency, as well as the ability to upload an attributed and unattributed usage store together.

When uploading using the UsageBroker, the sendUnattributed flag is set to true by default unless specified in the appropriate method call. The sendUnattributed flag determines whether to upload the unattributed store. If configured on an attributed store, the flag uploads both the attributed store and the unattributed store. If configured on the unattributed store, this flag has no effect.

AppUsageUploader.addUploadListener(new AppUsageUploader.UploadListener() {
    @Override
    public void onSuccess() {
        sLogger.debug("Usage upload complete, time taken (in nanos): " + (System.nanoTime() - startTime));
    }

    @Override
    public void onError(Throwable error) {
        Toast.makeText(getActivity(), "Upload Err:\n"+error.getMessage(), Toast.LENGTH_LONG).show();
        if (error instanceof HttpException) {
            sLogger.debug("Upload server error: {}, code = {}",
                        ((HttpException) error).message(), ((HttpException) error).code());
        } else {
            sLogger.debug("Upload error: {}", error.getMessage());
        }
    }

    @Override
    public void onProgress(int i) {
        sLogger.debug("Usage upload progress: " + i);
    }
});
UsageBroker.upload(false, true, getApplicationContext());
AppUsageUploader.addUploadListener(object : AppUsageUploader.UploadListener {
    override fun onSuccess() {
        sLogger.debug("Usage upload complete, time taken (in nanos): " + (System.nanoTime() - startTime))
    }

    override fun onError(error: Throwable) {
        Toast.makeText(activity, "Upload Err:\n" + error.message, Toast.LENGTH_LONG).show()
        if (error is HttpException) {
            sLogger.debug("Upload server error: {}, code = {}",
                    error.message(), error.code())
        } else {
            sLogger.debug("Upload error: {}", error.message)
        }
    }

    override fun onProgress(i: Int) {
        sLogger.debug("Usage upload progress: $i")
    }
})
UsageBroker.upload(false, true, getApplicationContext())

Using Lower-Level Components

Sessions

For the purposes of reporting, define a user session as the duration the mobile application is in the foreground. The session acts as a container for event metrics data. In addition, it contains information such as:

  • Device platform (Android/iOS)
  • Device platform version
  • Device Id
  • Application Id
  • Application version

Event Metrics Data

An application can report event metrics data using AppUsageInfo. SAP Mobile Services supports a standard schema to collect event metrics. The values for the metrics and how to interpret them for a custom report is up to your mobile application designers and product managers.

The AppUsageInfo class consists of the following fields and can collect metrics for the following events:

Field Description
Type The event type
Key The key to identify the event
Action The action associated with the event
Behavior User-defined behavior properties of the event
Category User-defined event category type
Duration The duration of the event in milliseconds
Element The SDK/UI element associated with the event
Measurement User-defined measurement of the event, such as UI element size
Others Any other user-defined properties associated with the event
Result User-defined result associated with the event. This is usually used with Action.
Screen The properties of the screen
Case User-defined case
Unit The unit associated with Measurement
Value User-defined value properties
View Android View related properties

Reporting

During the reporting phase, client data is collected and persisted in an encrypted store on the device. The data can then be uploaded to the mobile services server and viewed in client usage reports. See the uploading section in this topic for more information.

Reporting involves:

  1. Initializing the AppUsage Class
  2. Recording the Session Start
  3. Reporting Event Metrics
  4. AppUsage Behavior Events
  5. Recording the Session End

Initializing the AppUsage Class

A one-time initialization is required. An encryption key is required to encrypt the underlying persistence store.

We recommend that you use the encryption utility to obtain the encryption key. If it is the first time AppUsage is being configured and null is provided as the encryption key, a key is generated transparently and is used for subsequent configure calls. See Encryption Utility for more information.

This must occur before any AppUsage related calls occur.

// SettingsParameter is retrieved from SettingsProvider class and application Id and version in SettingsParameter will be
// used during reporting and uploading to form the mobile services URL.
// Encryption key is used to encrypt the underlying persistence store.
byte[] encrytionKey = EncryptionUtil.getEncryptionKey("aliasForAppUsage", passcodeFromUser);

try {
    AppUsage.initialize(appContext, "myUsageStore", encrytionKey);

    // Or passes null and the AppUsage will generate an encryption key to encrypt the persistence store
    // and subsequent `initialize` calls.
    //      AppUsage.initialize(appContext, "myUsageStore", settingsParameters, null);
} catch (OpenFailureException ex) {
    logger.error("Failed to open Usage store.", ex);
}
// SettingsParameter is retrieved from SettingsProvider class and application Id and version in SettingsParameter will be
// used during reporting and uploading to form the mobile services URL.
// Encryption key is used to encrypt the underlying persistence store.
val encrytionKey = EncryptionUtil.getEncryptionKey("aliasForAppUsage", passcodeFromUser)

try {
    AppUsage.initialize(appContext!!, "myUsageStore", encrytionKey)

    // Or passes null and the AppUsage will generate an encryption key to encrypt the persistence store
    // and subsequent `initialize` calls.
    //      AppUsage.initialize(appContext, "myUsageStore", settingsParameters, null);
} catch (ex: OpenFailureException) {
    logger.error("Failed to open Usage store.", ex)
}

Recording the Session Start

Reporting must begin with a sessionStart and end with a sessionEnd. Any calls to event reporting without a sessionStart are ignored. Event metrics reporting using eventStart, eventEnd, and event must happen after calling sessionStart and before sessionEnd is called.

A call to sessionStart marks the start of a session with the session-related attributes such as "Platform" and "Platform Version". Consecutive calls to sessionStart without calling sessionEnd ends the current session and creates a new session.

AppUsage.sessionStart();
AppUsage.sessionStart()

Reporting Event Metrics

After a sessionStart is called, one or more event records can be reported. Events can be reported using a single call to event() or a pair of eventStart() and eventEnd() calls. In the single call, your application is responsible for providing duration information if applicable. In the paired call, the event duration is automatically calculated.

Each of the event metrics fields accepts a string value. It is up to you to decide what value should be recorded in each of the fields.

AppUsage.eventStart("filterEventType", "productFilter",
        new AppUsageInfo()
                .screen("Product Filter")); // Uses one or more of the event metrics methods-- screen, element, view,
                                            // category, etc.

AppUsage.eventEnd("filterEventType", "productFilter",
    new AppUsageInfo()
        .screen("Product Filter"));

// Single event call.
AppUsage.event(
    "eventType1",          // Event type, optional.
    "testEvent1Key",       // Event key. When not specified, `other` will be used.
    4L,                    // Duration.
    new AppUsageInfo()     // Event metrics using AppUsageInfo.
        .screen("firstScreen")
        .value("randomValue")
        .category("category1");    
AppUsage.eventStart("filterEventType", "productFilter",
        AppUsageInfo()
                .screen("Product Filter"))  // Uses one or more of the event metrics methods-- screen, element, view,
                                            // category, etc.

AppUsage.eventEnd("filterEventType", "productFilter",
        AppUsageInfo()
                .screen("Product Filter"))

// Single event call.
AppUsage.event(
        "eventType1", // Event type, optional.
        "testEvent1Key", // Event key. When not specified, `other` will be used.
        4L, // Duration.
        AppUsageInfo()     // Event metrics using AppUsageInfo.
                .screen("firstScreen")
                .value("randomValue")
                .category("category1"))

AppUsage Behavior Events

Analytic Behavior events simplify the consistent capturing of two categories of event types: "View Displayed" and "User Interaction".

  • The "View Displayed" method is called by the developer to record that a particular UI control is displayed (MainActivity, for example). It only requires the view name/id.
AppUsage.eventBehaviorViewDisplayed(...)
if (AppUsage.isInitialized()){
    AppUsage.eventBehaviorViewDisplayed("MainActivity", "", "", "");
}
if (AppUsage.isInitialized()) {
    AppUsage.eventBehaviorViewDisplayed("MainActivity", "", "", "")
}
  • The "User Interaction" method is called to record some type of user interaction (for example, a button click or selecting a row).
AppUsage.eventBehaviorUserInteraction(...)
if (AppUsage.isInitialized()){
    AppUsage.eventBehaviorUserInteraction("MainActivity","FloatingActionButton","Button Pressed", "");
}
if (AppUsage.isInitialized()) {
    AppUsage.eventBehaviorUserInteraction("MainActivity", "FloatingActionButton", "Button Pressed", "")
}

Recording the Session End

A call to sessionEnd marks the end of a session.

AppUsage.sessionEnd();
AppUsage.sessionEnd()

Uploading

Before using AppUsageUploader, enable the upload of application-specific usage statistics and reports from mobile devices using the SAP mobile service cockpit. See Defining Usage Report Policy.

AppUsageUploader retrieves the usage data reported prior to the last session end from the underlying persistence store, converts them to the format required by the mobile service, and then uploads them.

Any events that are in the current open session are uploaded on the next upload call.

Registering the Upload Listener

To be notified whether the upload succeeds or fails, or to get upload progress, implement AppUsageUploader.UploadListener. This is an optional but recommended step.

Note that all callback methods are invoked in UI(main) thread.

// All callback methods will be invoked in UI(main) thread.
class MyUploadListener implements AppUsageUploader.UploadListener {
    @Override
    public void onSuccess() {
        logger.debug("Usage Upload completed successfully");

        // For example, put a toast message.
    }

    @Override
    public void onError(Throwable error) {
        if (error instanceof HttpException) {
            logger.debug("Usage Upload server error: {}, code = {}", ((HttpException) error).getMessage(),
                    ((HttpException) error).getCause());
        } else {
            logger.debug("Usage Upload error: {}", error.getMessage());
        }
    }

    @Override
    public void onProgress(int percentage) {
        // For example, updates the progress dialog/bar.
    }
}

MyUploadListener myUploadListener = new MyUploadListener();
internal var myUploadListener = MyUploadListener()

// All callback methods will be invoked in UI(main) thread.
internal inner class MyUploadListener : AppUsageUploader.UploadListener {
    override fun onSuccess() {
        logger.debug("Usage Upload completed successfully")

        // For example, put a toast message.
    }

    override fun onError(error: Throwable) {
        if (error is HttpException) {
            logger.debug("Usage Upload server error: {}, code = {}", error.message,
                    error.cause)
        } else {
            logger.debug("Usage Upload error: {}", error.message)
        }
    }

    override fun onProgress(percentage: Int) {
        // For example, updates the progress dialog/bar.
    }
}

Register the listener by calling:

AppUsageUploader.addUploadListener(myUploadListener);
AppUsageUploader.addUploadListener(myUploadListener)

If automatic upload is enabled by UsageService and triggered at some time, the listeners will be notified too.

Starting the Upload

To start the upload, call upload from the UI(main) thread. All usage data retrieval and uploading is performed in an AsyncTask.

Note

Make sure the mobile application has already authenticated with the server before the upload call. This call requires authentication.

AppUsageUploader.upload();
AppUsageUploader.upload()

Viewing Usage Reports on the Server

After a successful client data upload, you can view the data in the SAP mobile service cockpit. See Viewing the Client Data Report.


Last update: April 20, 2022