Bort SDK

The purpose of the Bort SDK is to make it really easy to periodically capture bug reports on AOSP devices and get them uploaded automatically to Memfault.

This document provides a detailed reference of the SDK, how it works, and some ways to configure it.

For a detailed guide on how to integrate Memfault into your devices, see the Getting Started guide.

Components

At a high level, the process of capturing and uploading involves these components that work together:

The Bort app

A privileged app that is responsible for periodically invoking bug reports. When it receives a bug report back from the MemfaultDumpstateRunner, the bort app schedules that bug report to be uploaded when internet is available.

The UsageReporter app (com.memfault.usagereporter)

A system app that is responsible for invoking the MemfaultDumpstateRunner, whenever it receives a request to do so from the Bort app.

/system/bin/MemfaultDumpstateRunner

This native program is exposed as an "init.rc service" (see memfault_init.rc for details). This is so that it can be executed in the required dumpstate security context, but get triggered by the Bort app, via the UsageReporter system app. The Bort app runs in the much less capable security context compared to MemfaultDumpstateRunner.

  1. trigger /system/bin/dumpstate (through another init.rc service, memfault_dumpstatez),
  2. copy the bugreport.zip file out of the shell file system sandbox and into Bort's file system sandbox,
  3. broadcast an Intent with the final path such that Bort can process it further.

To permit MemfaultDumpstateRunner to do all the things it needs to do, it is labelled with the existing/builtin dumpstate sepolicy label and is broadened a little further as well (see memfault_dumpstate_runner.te). It's possible to make a tail-made policy that is narrower in scope but this requires more changes to the builtin AOSP system/sepolicy, so we choose to piggy-back on the existing dumpstate type instead. This is also the approach that the AOSP Car product (Android Auto) takes. See sepolicy/file_contexts for reference.

/system/bin/dumpstate

This is the AOSP-built-in bugreport capturing program; it requires no modification.

Note that it is not triggered through the builtin dumpstatez init.rc service, but through the slightly specialized memfault_dumpstatez. See memfault_init.rc for details on the differences.

Bug report capture period

The bort app periodically generates bug reports by scheduling a periodic task to trigger the MemfaultDumpstateRunner.

The bort app registers a handler (broadcast receiver) for the system boot event as well as when the app itself is updated or installed. When either of these happens, the app will register the periodic task if one is not registered.

There are configuration options to set the period of this task as well as the initial delay for when the first bug report is generated (e.g. if you wish to wait until more data is available) is described below.

Bug reports can also be triggered programmatically via an Intent.

Triggering a bug report programmatically

In addition to generating bug reports at regular intervals, you may also wish to capture a bug report if a significant event occurs. Doing this will not affect the scheduled bug reports.

Note that if the dumpstate runner is busy capturing a bug report already, the in-flight bug report will continue and the interrupting request will be ignored.

Triggering a bug report requires that the sender hold the permission specified in the BORT_CONTROL_PERMISSION property in bort.properties. The default is the com.memfault.bort.permission.CONTROL.

Intent("com.memfault.intent.action.REQUEST_BUG_REPORT").apply {
component = ComponentName(
APPLICATION_ID_BORT,
"com.memfault.bort.receivers.ControlReceiver"
)
}.also {
context.sendBroadcast(it)
}

If you wish to generate a bug report using the SDK on a development device over ADB, bort_cli.py provides a convenience command:

./bort_cli.py request-bug-report --bort-app-id your.app.id

Enabling the SDK at Runtime

By default, the SDK assumes it is running on a device that may contain Personally Identifiable Information (PII). As such, it will not run until it is explicitly told to do so (e.g. when user consent has been obtained). Once enabled, that value will be persisted in preferences. If the bort app's data is cleared, this value must be set again.

To enable the SDK, send an intent to the ControlReceiver. Note that the sender must hold the permission specified in the BORT_CONTROL_PERMISSION property in bort.properties. The default is com.memfault.bort.permission.CONTROL, which may only be may only be granted to applications signed with the same signing key as MemfaultBort (signature) or applications that are installed as privileged apps on the system image (privileged). See Android protectionLevel for further documentation.

Intent("com.memfault.intent.action.BORT_ENABLE").apply {
component = ComponentName(
APPLICATION_ID_BORT, // Whatever you have chosen for the application ID
"com.memfault.bort.receivers.ControlReceiver"
)
putExtra("com.memfault.intent.extra.BORT_ENABLED", true)
}.also {
context.sendBroadcast(it)
}

To disable the SDK, for example if the user later revokes consent, simply send the same intent with the opposite boolean extra

Intent("com.memfault.intent.action.BORT_ENABLE").apply {
component = ComponentName(
APPLICATION_ID_BORT,
"com.memfault.bort.receivers.ControlReceiver"
)
putExtra("com.memfault.intent.extra.BORT_ENABLED", false) // <-- Now disabled
}.also {
context.sendBroadcast(it)
}

If the SDK is running on a device that does not require user consent, this requirement can be disabled by changing a property in bort.properties:

RUNTIME_ENABLE_REQUIRED=false

If you wish to enable the SDK on a development device over ADB, bort_cli.py provides a convenience command:

./bort_cli.py enable-bort --bort-app-id your.app.id

Software Versions, Hardware Versions and Device Serials

This section explains how to optionally configure the software versions, hardware versions and device serial numbers based on build parameters. For background on software and hardware versions, see Software and Hardware Versions.

NOTE: This section is optional. We recommend using the default software and hardware versions when possible.

Customize the reported android-build version

The Android system software (everything that gets built as part of the AOSP build process) is represented in Memfault by the software component called android-build.

By default, the value of the ro.build.version.incremental system property is used as the version of each android-build software component. This value contains the "incremental" part of the build fingerprint.

It is possible to customize which system property value to use as the version by changing the ANDROID_BUILD_VERSION_KEY property in bort.properties.

Note however, that the value is expected to uniquely identify the Android system software. So, separate builds should never (re)use the same version.

Before changing this, please consult with the Memfault team.

Customize the reported Hardware Version

The system property containing the value to use to identify the type of hardware which Bort is running on. In Memfault terms, this is the "Hardware Version". A given device will always map to exactly one "Hardware Version". It can be used to differentiate different device types being tracked in the same project (i.e smart-toaster and smart-oven) and different board revisions of the same device type (i.e smart-toaster-evt, smart-toaster-pvt, etc)

By default, the value of the ro.product.board system property is used as "Hardware Version".

It is possible to customize which system property value to use as the version by changing the ANDROID_HARDWARE_VERSION_KEY property in bort.properties.

Note however, that the value is expected to uniquely identify the hardware (board) design, including its physical configuration and peripherals. So, different hardware should never (re)use the same hardware version.

Before changing this, please consult with the Memfault team.

Customize the reported Device Serial

By default, the value of the ro.serialno system property is used.

It is possible to customize which system property value to use as the version by changing the ANDROID_DEVICE_SERIAL_KEY property in bort.properties.

Note, the Device Serial is expected to uniquely identify a device, therefore it must be unique per device. It is also expected to remain the same for the entire lifetime of the device.

Before changing this, please consult with the Memfault team.

Upload to a custom endpoint

If you wish to upload bug reports to Memfault via your own server, you can enable this by providing a custom FileUploader.

Implement the uploader and set the factory in the application's onCreate:

+import retrofit2.Retrofit
+import vnd.myandroid.bortappid.SampleBugReportUploader
open class Bort : Application(), Configuration.Provider {
@@ -38,7 +40,15 @@ open class Bort : Application(), Configuration.Provider {
}
}
- open fun initComponents(): AppComponents.Builder = AppComponents.Builder(this)
+ open fun initComponents(): AppComponents.Builder = AppComponents.Builder(this).apply {
+ fileUploaderFactory = object : FileUploaderFactory {
+ override fun create(retrofit: Retrofit, projectApiKey: String): FileUploader =
+ SampleBugReportUploader(
+ retrofit = retrofit,
+ apiKey = projectApiKey
+ )
+ }
+ }
companion object {
private var appComponentsBuilder: AppComponents.Builder? = null

a sample uploader is provided in SampleBugReportUploader.kt.

Install the SDK to an alternate location

The default SDK location is in vendor/memfault/bort. If you would prefer to place the SDK at a different location in your source tree, first clone the SDK to the desired location in the source tree, then find and replace instances of vendor/memfault/bort with your target directory.

For example, to place the SDK in the system partition, clone the SDK to packages/apps/bort then run find and replace (e.g. on BSD/macOS):

LC_ALL=c find . \( -type d -name .git -prune \) -o -type f -exec sed -i '' "s/vendor\/memfault\/bort/packages\/apps\/bort/g" {} +