Skip to content

RoadSave SDK 4.0.0 Integration Guide

SDK version: 4.0.0 — Java and Kotlin compatible.

Last updated: Phase 7 (April 2026)


Table of contents

  1. Platform requirements
  2. Add the SDK to your project
  3. Manifest entries
  4. Platform detection (GMS vs. HMS)
  5. Initialization
  6. SDK lifecycle
  7. Handling callbacks
  8. Foreground service notifications
  9. Test crash simulation
  10. False-positive dismissal
  11. Auto trip detection toggle
  12. ProGuard / R8
  13. Troubleshooting / FAQ

1. Platform requirements

Requirement Minimum Notes
Android API level API 24 (Android 7.0 Nougat) Covers 99%+ of active devices.
targetSdk API 35 Required for current Google Play compliance.
Google Play Services v21.1.0+ Required on GMS devices.
HMS Core 6.x+ Required on HMS-only (Huawei) devices.
EMUI / HarmonyOS EMUI 5.0+ / HarmonyOS 2.0+ EMUI 5.0 is based on Android 7.0, satisfying minSdk 24.
Kotlin (consumers) Not required The public API is fully Java-compatible.

Breaking change from 3.4.12: The minSdk raise from 17 to 24 excludes devices running Android 4.2–6.0. Verify your user base before upgrading.


2. Add the SDK to your project

Step 1 — Download and copy the AAR

Download roadsave-sdk-<version>.aar from the GitHub Releases page and place it in your app module's libs/ folder. See Downloads for checksum verification and the full runtime dependency list.

app/
  libs/
    roadsave-sdk-<version>.aar

Step 2 — Declare the dependency

// app/build.gradle
repositories {
    google()
    mavenCentral()
    // Required for HMS dependencies
    maven { url 'https://developer.huawei.com/repo/' }
}

dependencies {
    implementation files('libs/roadsave-sdk-<version>.aar')

    // GMS location (required on Google-services devices)
    implementation 'com.google.android.gms:play-services-location:21.1.0'

    // HMS location + maps (required on Huawei devices)
    implementation 'com.huawei.hms:location:6.4.0.300'
    implementation 'com.huawei.hms:maps:5.3.0.300'
    implementation 'com.huawei.agconnect:agconnect-core:1.9.5.300'
}

Step 3 — Optional: IDE inline docs

If you have the javadoc JAR (roadsave-sdk-<version>-javadoc.jar), drop it alongside the AAR in libs/. Android Studio automatically attaches it for hover documentation and autocomplete — no additional configuration is needed.

ProGuard note

Consumer ProGuard rules are bundled inside the AAR. No additional rules are required in your own proguard-rules.pro.


3. Manifest entries

Permissions

Add the following to your AndroidManifest.xml. Bold entries require a runtime request on Android 6.0+ (API 23+):

<!-- Location — runtime permission on API 23+ -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Background location — runtime permission on API 29+ (needed for monitoring while screen is off) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Activity recognition — runtime permission on API 29+ -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- GMS legacy activity recognition (required on older Play Services) -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- HMS activity recognition -->
<uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION" />

<!-- Foreground service (required to keep monitoring alive) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

<!-- Post notifications — runtime permission on API 33+ (for foreground service notification) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- Network & system -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- GPS hardware feature (non-required; SDK checks at runtime) -->
<uses-feature android:name="android.hardware.location.gps" android:required="false" />

Runtime permission request flow

Request permissions before calling configurationSetup(...). At minimum, ACCESS_FINE_LOCATION and ACTIVITY_RECOGNITION (API 29+) must be granted:

// Kotlin
private val locationPermissions = arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION
)

private val activityPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    arrayOf(Manifest.permission.ACTIVITY_RECOGNITION)
} else {
    emptyArray()
}

// API 33+: also request POST_NOTIFICATIONS before starting the SDK
// Java
String[] locationPermissions = {
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION
};

4. Platform detection (GMS vs. HMS)

Call RoadSave.isHmsAvailable(context) to determine which platform the current device supports, then configure GlobalEnvSetting before any other SDK call.

// Kotlin — call this in Application.onCreate() or before configurationSetup()
import org.xms.g.utils.GlobalEnvSetting
import com.roadsave.lib.RoadSave

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        if (RoadSave.isHmsAvailable(this)) {
            GlobalEnvSetting.useHms()    // Huawei device — route through HMS
        } else {
            GlobalEnvSetting.useGms()    // Google device — route through GMS
        }
    }
}
// Java
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (RoadSave.isHmsAvailable(this)) {
            GlobalEnvSetting.useHms();
        } else {
            GlobalEnvSetting.useGms();
        }
    }
}

Important: GlobalEnvSetting must be configured before constructing RoadSave. The SDK constructor calls isHmsAvailable internally and sets the route automatically, but calling it explicitly in Application.onCreate() guarantees correct routing if the SDK is accessed from multiple entry points.


5. Initialization

Step 1 — Construct RoadSave

// Kotlin
val sdk = RoadSave(context)

Step 2 — Build a configuration

// Kotlin — recommended: use the full constructor
val config = RoadSaveConfiguration(
    context        = context,
    appID          = "YOUR_APPLICATION_ID",   // obtained from RoadSave
    userID         = "UNIQUE_USER_ID",        // unique per end-user
    autoDetectTrips = true,
    detectionMode  = true
)
// Java
RoadSaveConfiguration config = new RoadSaveConfiguration(
    context,
    "YOUR_APPLICATION_ID",
    "UNIQUE_USER_ID",
    true,   // autoDetectTrips
    true    // detectionMode
);
  • applicationID — your unique Application ID, obtained from the RoadSave developer portal. Contact roadsave.com if you do not have one.
  • clientUserID — a unique string identifying the end user in your system. Each user must have a distinct ID.
  • autoTripDetection — when true, the SDK starts and stops trip monitoring automatically based on detected vehicle movement.
  • crashDetectionMode — when true, the SDK evaluates potential crash events during monitored trips.
sdk.setRoadSaveListener(myListener) // see Section 7

Step 4 — Start monitoring

sdk.configurationSetup(config)

configurationSetup starts the ActivityMonitoring foreground service, registers the SDK with the RoadSave server, and begins trip auto-detection (if enabled). The result is delivered asynchronously via RoadSaveListener.onConfigurationSuccess() or RoadSaveListener.onConfigurationError().

Minimal working example

class MyActivity : AppCompatActivity() {
    private lateinit var sdk: RoadSave

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        sdk = RoadSave(this)
        sdk.setRoadSaveListener(object : RoadSaveListener {
            override fun onConfigurationSuccess() {
                Log.i("SDK", "Registered successfully")
            }
            override fun onConfigurationError(errorCode: Int, message: String) {
                Log.e("SDK", "Registration failed: $errorCode$message")
            }
            // … other overrides
        })

        val config = RoadSaveConfiguration(this, "YOUR_APP_ID", "USER_123", true, true)
        sdk.configurationSetup(config)
    }

    override fun onDestroy() {
        super.onDestroy()
        sdk.removeRoadSaveListener()
    }
}

6. SDK lifecycle

Method Description
configurationSetup(config) Start monitoring. Must be called after permissions are granted.
dismantle() Stop all SDK services and cancel any active trip. Call when the SDK is no longer needed.
isServiceRunning(): Boolean Returns true if the ActivityMonitoring foreground service is running.
isSDKEnabled(): Boolean Returns true if the server has authorized SDK use for this application ID.
isDeviceSupported(): Boolean Returns true if the device has the required GPS and accelerometer hardware.
shouldTripStart(context): Boolean Convenience check: location enabled + device supported + activity permission granted.
getCurrentTripInfo(): CurrentTripInfo? Returns live trip data during an active trip, or null if no trip is in progress.
getTripStartInfo(): TripStartInfo? Returns the start location and timestamp of the most recently started trip.
getConfiguration(): RoadSaveConfiguration Returns the currently persisted SDK configuration (reads from SharedPreferences).
getSDKBuildVersion(): String Returns the SDK version string (e.g. "4.0.0").
getCrashAlgorithmVersion(): String Returns the crash algorithm version last fetched from the server.
isHms(): Boolean Returns true if the device is currently running in HMS mode.
isGms(): Boolean Returns true if the device is currently running in GMS mode.

7. Handling callbacks

Register a listener before calling configurationSetup. All callbacks are delivered on the main thread.

sdk.setRoadSaveListener(object : RoadSaveListener {

    override fun onConfigurationSuccess() {
        // SDK registered. Monitoring is now active.
    }

    override fun onConfigurationError(errorCode: Int, message: String) {
        // Registration failed. See ErrorCode for codes 100–107.
        Log.e("SDK", "Config error $errorCode: $message")
    }

    override fun onTripStarted(startInfo: TripStartInfo) {
        // A vehicle trip has started.
        // startInfo.startLocation — RoadSaveLocation of trip origin
        // startInfo.startTimeStamp — epoch milliseconds
    }

    override fun onTripEnded(tripInfo: TripInfo) {
        // Trip has ended.
        // tripInfo.distance      — total distance in metres
        // tripInfo.averageSpeed  — average speed in m/s
        // tripInfo.startTimeStamp / endTimeStamp — epoch milliseconds
        // tripInfo.wayPoints     — List<RoadSaveLocation>
    }

    override fun onTripMonitoringUpdate(currentTripInfo: CurrentTripInfo) {
        // Called ~every second during an active trip.
        // currentTripInfo.currentLocation — live RoadSaveLocation
        // currentTripInfo.startTripInfo   — TripStartInfo
        // currentTripInfo.wayPoints       — route so far
    }

    override fun onCrashDetected(crashInfo: CrashInfo) {
        // A crash has been detected and evaluated by the server.
        // crashInfo.confidence        — CrashConfidence (HIGH / LOW / UNKNOWN)
        // crashInfo.crashSpeed        — speed at breach (m/s)
        // crashInfo.crashMaxGForce    — peak G-force reading
        // crashInfo.crashTimeStamp    — epoch milliseconds
        // crashInfo.crashLocation     — RoadSaveLocation
        // crashInfo.CrashEvaluationGUID — unique crash ID (for false-positive reporting)
    }

    override fun onLocationChanged(location: RoadSaveLocation) {
        // Device location updated.
        // location.latitude / longitude / speed / accuracy / bearing / altitude
    }

    override fun onLocationEnabledChanged(enabled: Boolean) {
        // Location services were enabled (true) or disabled (false) by the user.
    }

    override fun onSdkError(errorCode: Int) {
        // Runtime error. See ErrorCode for codes 300–505.
        when (errorCode) {
            ErrorCode.ForegroundServicePermissionError ->
                promptForLocationPermission()
            ErrorCode.SdkStopped ->
                restartSdk()
            else ->
                Log.w("SDK", "SDK error: $errorCode")
        }
    }
})

Remove the listener when it is no longer needed:

sdk.removeRoadSaveListener()

Using broadcast receivers (legacy, still supported)

For backward compatibility, the SDK also fires LocalBroadcast intents. Register a BroadcastReceiver for the relevant action strings from RoadSaveBroadcastConstants:

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            RoadSaveBroadcastConstants.CRASH_DETECTED -> {
                val info = intent.getSerializableExtra(
                    RoadSaveBroadcastConstants.CRASH_DETECTED_DATA
                ) as? CrashInfo
                // handle crash
            }
            RoadSaveBroadcastConstants.TRIP_MONITORING_ENDS -> {
                val info = intent.getSerializableExtra(
                    RoadSaveBroadcastConstants.TRIP_MONITORING_ENDS_DATA
                ) as? TripInfo
                // handle trip end
            }
        }
    }
}

Recommendation: prefer RoadSaveListener for new integrations. The broadcast mechanism requires LocalBroadcastManager and manual receiver registration/deregistration.

CrashConfidence values

Value Meaning
HIGH Server evaluated the event as a high-confidence crash. Recommend immediate emergency response check.
LOW Server evaluated the event as a low-confidence crash. May be a harsh braking or road bump.
UNKNOWN Evaluation result could not be determined (e.g., no server response).

8. Foreground service notifications

The SDK runs ActivityMonitoring and TripMonitoring as foreground services, which require a persistent notification. Customize the notification content before or after calling configurationSetup:

// Trip monitoring notification
sdk.setTripNotification(
    icon  = R.drawable.ic_trip_notification,  // pass 0 to keep current icon
    title = "RoadSave — Trip in progress",
    description = "Monitoring your drive"
)

// Activity monitoring notification
sdk.setActivityNotification(
    icon  = R.drawable.ic_activity_notification,
    title = "RoadSave — Active",
    description = "Waiting for vehicle movement"
)

Pass 0 for icon or "" for title/description to leave the current value unchanged.

The SDK creates its notification channel with ID RoadSave.getDefaultChannelID(). On API 26+ you can retrieve this ID to customize the channel's importance or sound before starting the SDK:

val channelId = RoadSave.getDefaultChannelID()
// Create or modify the NotificationChannel before calling configurationSetup()

9. Test crash simulation

Trigger a test crash event to verify your integration without a physical accident. The test crash follows the same server upload and callback path as a real crash.

sdk.raiseTestCrash()

The method requires ACCESS_FINE_LOCATION to be granted at runtime. If the permission is missing, RoadSaveListener.onSdkError is called with ErrorCode.ForegroundServicePermissionError and the test crash is aborted.

The test crash result arrives via RoadSaveListener.onCrashDetected() approximately 10–15 seconds after calling raiseTestCrash().


10. False-positive dismissal

When a user dismisses an in-app crash countdown (indicating the event was not a real crash), report the dismissal to the server using setCrashFalsePositive:

val dismissed = sdk.setCrashFalsePositive(
    crashInfo   = crashInfo,    // CrashInfo from onCrashDetected
    reasonCode  = CrashFalsePositiveReason.CrashFalsePositivePhoneDropped,
    description = "User confirmed phone was dropped"
)

setCrashFalsePositive returns true if the server request was dispatched successfully.

CrashFalsePositiveReason constants

Constant Code Meaning
CrashFalsePositiveCrashNoHelpNeeded 100 Crash occurred but no assistance is required.
CrashFalsePositiveNotDriving 101 User was not driving.
CrashFalsePositivePhoneDropped 102 Phone was dropped.
CrashFalsePositivePhoneUsed 103 Phone was used (tapped, etc.).
CrashFalsePositiveHarshBrake 104 Harsh braking event, not a crash.
CrashFalsePositiveOther 105 Other reason.

When the user cancels a crash countdown without dismissing it as a false positive, call countdownCancelled instead:

sdk.countdownCancelled(crashInfo)

11. Auto trip detection toggle

Auto trip detection starts and stops trip monitoring automatically when the SDK detects that the user is in a vehicle. It can be toggled at runtime:

// Disable auto-detection (manual trip control only)
sdk.setAutoTripDetection(false)

// Re-enable
sdk.setAutoTripDetection(true)

// Read current value
val isAutoDetecting: Boolean = sdk.autoTripDetection

This setting is persisted across app restarts. Changes take effect from the next monitoring cycle.


12. ProGuard / R8

No action is required. The SDK AAR ships a consumer-rules.pro file that AGP automatically merges into your build when R8 minification is enabled. This file keeps all public SDK classes and Serializable models that are passed in Intent extras.

If you inspect the merged rules and see SDK classes missing from the keep list, contact RoadSave support — do not manually add rules that duplicate the consumer file.


13. Troubleshooting / FAQ

SDK never calls onConfigurationSuccess

  1. Verify ACCESS_FINE_LOCATION (and ACTIVITY_RECOGNITION on API 29+) are granted at runtime before calling configurationSetup.
  2. Check that applicationID and clientUserID are non-empty and valid. Use ErrorCode.ConfigErrorAuthorisationDenied (102) to detect invalid credentials.
  3. Ensure the device has network access. The SDK must reach https://api.roadsave.co.za/api/ for registration.
  4. Check logcat for RoadSave tag entries.

No notification channel on API 26+

The SDK creates its channel on first call to configurationSetup. If POST_NOTIFICATIONS (API 33+) is not granted, the notification is silently suppressed but the service still runs. Grant the permission before calling configurationSetup to ensure the notification appears.

FGS ForegroundServiceStartNotAllowedException on API 34+

Android 14+ restricts foreground service starts from certain background states. Ensure your app calls configurationSetup from a foreground context (e.g., from an Activity's onResume, or from a user-initiated action). If the exception occurs, the SDK dispatches ErrorCode.ForegroundServicePermissionError (505).

HMS services not initializing on Huawei devices

Ensure GlobalEnvSetting.useHms() is called in Application.onCreate() before any SDK method. Without this, the SDK defaults to GMS routing, which fails on HMS-only devices.

onSdkError(500) — SDK stopped unexpectedly

Error code 500 (SdkStopped) means the primary monitoring service was killed, often by Android's battery optimization. Ask the user to exempt your app from battery optimization in device settings, or call configurationSetup again to restart monitoring.

raiseTestCrash completes but onCrashDetected is never called

Check that the device had a GPS fix at the time of the test crash. The SDK logs "The GPS on this device could not obtain a lock" if location is null. Move to an outdoor location with clear sky view and retry.

Error code reference

Range Category Delivered via
100–107 Configuration / registration onConfigurationError(errorCode, message)
300–301 Crash evaluation / sensor onSdkError(errorCode)
500–505 Service lifecycle / runtime onSdkError(errorCode)

See ErrorCode for the full list of constants and recommended actions for each code.


This document is part of the RoadSave SDK 4.0.0 modernization (Phase 7). For the full API reference, open docs/index.html from the release bundle or visit docs.roadsave.co.za.