Skip to content

Third-party Integration

Note

For API v0.1 and API v1.0 users: support will be removed in future releases of the Android Service. Please consider switching to API v1.1 as soon as possible.

uf-android-service has a set of APIs that allow a third party application to interact with it.

To integrate the uf-android-service into your application you must:

  1. Import the module uf-client-service-api (see Module uf-client-service-api section)
  2. Bind to the uf-android-service service (see Bind to uf-android-service service section)

The module uf-client-ui-example is provided as an example of a third party application. The uf-client-ui-example is able to:

  1. show the current service status
  2. open the preferences page of uf-android-service (Settings page)
  3. grant / deny authorization to execute an action during a soft update

API compatibility

Newer APIs are supported only by recent versions of the UF Android Client Service. The compatibility between the APIs and the Client Service versions is specified in the following table:

API UF Android Client Service
v0.1 any
v1.0 ≥ v1.0.0 (version code 23)
v1.1 ≥ v1.3.0 (version code 31)

Using updated versions of the API (e.g. v1.1) requires running on the device an UF Android Client Service version (e.g. 1.3.0) which supports them. A strategy to perform API upgrades is to:

  • create a rollout to update to the latest UF Android Client Service (e.g. v1.3.0)
  • create a target filter to filter devices that have an out of date application and a UF Android Client Service that supports the specified API (e.g. attribute.client_version_code=ge=31 ensures v1.3.0 or later is installed and thus API v1.1 are supported). This filter can then be used to auto assign a distribution with the latest version of the application that uses the desired API version

Migration from API v1.0 to v1.1

To migrate your application from API v1.0 to v1.1:

  1. upgrade the uf-client-service-api dependency to v1.3.0
  2. use the ConfigureServiceV2 message to configure the client instead of the ConfigureService message (still supported but deprecated)
  3. handle the CurrentServiceConfigurationV2 message in your handler. The service sends the CurrentServiceConfigurationV2 message as response of sync request instead of the CurrentServiceConfiguration message
  4. handle the new WaitingUpdateWindow service state when your app receives the ServiceNotification

Bind to uf-android-service

uf-android-service is a bound service, it allows components (such as activities) to bind to the service, send requests, receive responses, and perform inter-process communication.

To bind with the service create an intent with:

  1. action = UFServiceInfo.SERVICE_ACTION
  2. package = UFServiceInfo.SERVICE_PACKAGE_NAME

This is an example of how to bind an Application with the service:

private val mService:Messenger;

private val mConnection = object : ServiceConnection {
    override fun onServiceConnected(
        className: ComponentName,
        service: IBinder
    ) {
        mService = Messenger(service)

        Toast.makeText(this@MainActivity, R.string.ui_connected,
            Toast.LENGTH_SHORT).show()

        handleRemoteException {
            mService!!.send(Communication.V1.In.RegisterClient(mMessenger).toMessage())
            mService!!.send(Communication.V1.In.Sync(mMessenger).toMessage())
        }
        mIsBound = true
    }

    override fun onServiceDisconnected(className: ComponentName) {
        Log.i(TAG, "Service is disconnected")
        doUnbindService()
    }

    override fun onBindingDied(name: ComponentName?) {
        Log.i(TAG, "Service binding is died")
        mIsBound = false
    }
}

private fun doBindService() {
    val intent = Intent(UFServiceInfo.SERVICE_ACTION)
    intent.setPackage(UFServiceInfo.SERVICE_PACKAGE_NAME)
    intent.flags = FLAG_INCLUDE_STOPPED_PACKAGES
    val serviceExist = bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    if (!serviceExist && !mServiceExist) {
        Toast.makeText(applicationContext, "UpdateFactoryService not found", Toast.LENGTH_LONG).show()
        unbindService(mConnection)
        this.finish()
    } else {
        mServiceExist = true
    }
}

private fun doUnbindService() {
    if (mIsBound) {
        if (mService != null) {
            try {
                mService!!.send(Communication.V1.In.UnregisterClient(mMessenger).toMessage())
            } catch (e: RemoteException) {
            }
        }
        unbindService(mConnection)
        mIsBound = false
    }
}

Messages

The Android client Service uses Android Messenger to receive user inputs and to notify its status. The uf-client-service-api provides a set of help classes that must be used to communicate with the service (see communication).

For more details see the official documentation.

Module uf-client-service-api

The module uf-client-service-api contains all of the classes that a third party application must include to handle the communication with the uf-android-service.

uf-client-service-api contains:

  1. UFServiceInfo: object class with the main information about the service
  2. Communication: a set of classes to build (parse) messages to send (receive) to the service
  3. UFServiceConfiguration: a data class that contains the current configuration of the service
  4. UFServiceMessageV1: a set of classes that represents the state of the service

To import this module:

  1. configure your project to use jitpack (see jitpack documentation).
  2. add implementation 'com.github.kynetics:uf-android-client:v1.y.z', for example implementation 'com.github.kynetics:uf-android-client:v1.3.0' (see jitpack uf page)
  3. if your application is obfuscated, add the following ProGuard rule:
-keep class com.kynetics.uf.android.api.*{
    *;
}

See kdoc API documentation for additional info.

UFServiceInfo

UFServiceInfo is an object class with the main information about the service

object UFServiceInfo {
    /**
     * Package name of the Update Factory Service
     */
    const val SERVICE_PACKAGE_NAME = "com.kynetics.uf.service"

    /**
     * Action to bind with the Update Factory Service
     */
    const val SERVICE_ACTION = "com.kynetics.action.BIND_UF_SERVICE"

    /**
     * Action to open the Update Factory Settings
     */
    const val ACTION_SETTINGS = "com.kynetics.action.SETTINGS"
}

UFServiceConfigurationV2

UFServiceConfigurationV2 contains:

  1. tenant = the name of the tenant
  2. controllerId = device id
  3. url = url of update factory server
  4. targetToken = device's token to communicate with uf server
  5. gatewayToken = a token in common with all targets to communicate with uf server
  6. isApiMode = if false, the uf service shows a pop up window asking for user authorization. If true the service sends an AuthorizationRequest message (see In messages)
  7. isUpdateFactoryServe = if true the service is optimized for UFServer, if false the service is optimized for hawkBit server
  8. targetAttributes = a map of string to string that the service sends to the server.
  9. timeWindows = configuration of the time slots during which a force update can be applied on the device, the timeSlots are defined using a cron expression and a windows size duration:
    1. cronExpression = a cron expression (QUARTZ format) that defines when the time windows begins (in the device time zone)
    2. duration = windows size in seconds

The targetAttributes and timeWindows properties can be omitted when building a UFServiceConfigurationV2, as they have a default value.

UFServiceConfigurationV2(
                tenant = <tenant_name>,
                controllerId = <controller_id>,
                url =  <uf_server_url>,
                targetToken = <target_token>,
                gatewayToken = <gateway_token>,
                isApiMode = true,
                isEnable = true,
                isUpdateFactoryServe = true,
                targetAttributes = mutableMapOf("DeviceOS" to "Android"),
                timeWindows = TimeWindows(
                    cronExpression = "* * * ? * *",
                    duration = 3600
                )
            )

Communication

Communication classes are hierarchy organized: Communication.V1.[<In> | <Out>].<message_type>

In classes represent all messages that can be sent to the service, instead, Out classes represent all messages that can be received from the service.

In

To send a message to the UF service, you must create an instance of an In class and then invoke the method toMessage

    //mService:Messenger;
    mService!!.send(Communication.V1.In.RegisterClient(mMessenger).toMessage())

ConfigureServiceV2

Class to build a message to configure the service.

Arguments:

  1. conf: new service configuration (instance of UFServiceConfigurationV2)
RegisterClient

Class to build a message to register the client (the service will notify any update to the client)

Constructor Arguments:

  1. replyTo: Messenger where reply
UnregisterClient

Class to build a message to unregister the client to the service

Constructor Arguments:

  1. replyTo: Messenger where reply
AuthorizationResponse

Class to build a message to grant or deny the authorization to download or apply an update.

Constructor Arguments:

  1. granted: true to immediately grant authorization, false to deny authorization until a force ping is called, or the device is rebooted

Note

An AuthorizationResponse message with granted = false will cause subsequent AuthorizationResponse messages with granted = true to be ignored, until a force ping is called, or the device is rebooted. A third-party application is not required to provide an immediate answer to an AuthorizationRequest message, and can send an AuthorizationResponse message with granted = true at a later time.

Sync

Class to build a message which requires a sync with the service. The service will respond with its configuration and its state (see Out section)

Constructor Arguments:

  1. replyTo: Messenger where reply
ForcePing

Class to build a message to force a poll to the Update Factory Server

Out

When you receive a message from the service you must use toOutV1Message() (see kotlin extension function) function that returns an instance of Out class.

override fun handleMessage(msg: Message) {
    runCatching{
        val v1Msg = msg.toOutV1Message()
        when (v1Msg) {

            is Communication.V1.Out.CurrentServiceConfigurationV2 -> handleServiceConfigurationMsg(v1Msg)

            is Communication.V1.Out.AuthorizationRequest -> handleAuthorizationRequestMsg(v1Msg)

            is Communication.V1.Out.ServiceNotification -> handleServiceNotificationMsg(v1Msg)

            else -> { Log.i(TAG, "Unexpected message received") }
        }
    }.onFailure{ error ->
        Log.w(TAG, "Unexpected message received", error)
    }
}

ServiceNotification

ServiceNotification contains the current status of the service. It has a field called content of type UFServiceMessageV1

AuthorizationRequest

AuthorizationRequest has a field called authNamethat contains the type of authorization (typically one between download and update).

CurrentServiceConfigurationV2

CurrentServiceConfigurationV2 has a field called conf that contains the current service configuration (an instance of UFServiceConfigurationV2)

UFServiceMessageV1

Classes that map states and events of Update factory service:

States:

Downloading

Client is downloading artifacts from server.

Fields:

  1. artifacts: list of artifacts to download
Artifact

Fields:

  1. name: artifact name
  2. size: artifact size
  3. md5: artifact MD5 checksum
Updating

The update process is started. Any request to cancel an update will be rejected.

CancellingUpdate

Last update request is being cancelled.

WaitingDownloadAuthorization

Waiting authorization to start download.

WaitingUpdateAuthorization

Waiting authorization to start update.

WaitingUpdateWindow

Waiting for a valid time slot to apply the force update

Idle

Client is waiting for new requests from the server.

ConfigurationError

Bad service configuration.

Fields:

  1. details: additional information

Events:

Polling

Client is contacting server to retrieve new action to execute.

StartDownloadFile

A file downloading is started.

Fields:

  1. fileName: name of the file to download
FileDownloaded

A file is downloaded.

Fields:

  1. fileDownloaded: the file that is downloaded
DownloadProgress

Percentage of the file downloaded.

Fields:

  1. fileName: the file that is downloaded
  2. percentage: the percentage downloaded (in [0,1])
AllFilesDownloaded

All needed file are downloaded.

UpdateFinished

The update is finished.

Fields:

  1. successApply: true if the update is successfully applied, false otherwise.
  2. details: additional details
Error

An error has occurred.

Fields:

  1. details: additional details
UpdateProgress

Percentage of phase updated

Fields:

  1. phaseName: the name of the update phase
  2. phaseDescription: the description of the update phase
  3. percentage: the percentage of phase
UpdateAvailable

An update is available on cloud.

Fields:

  1. id: update id

References

Kynetics - uf-android-client repository

Kynetics - Update Factory

Kynetics Technical note on Update Factory

Kynetics Slideshare