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¶
- Platform requirements
- Add the SDK to your project
- Manifest entries
- Platform detection (GMS vs. HMS)
- Initialization
- SDK lifecycle
- Handling callbacks
- Foreground service notifications
- Test crash simulation
- False-positive dismissal
- Auto trip detection toggle
- ProGuard / R8
- 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.
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:
GlobalEnvSettingmust be configured before constructingRoadSave. The SDK constructor callsisHmsAvailableinternally and sets the route automatically, but calling it explicitly inApplication.onCreate()guarantees correct routing if the SDK is accessed from multiple entry points.
5. Initialization¶
Step 1 — Construct RoadSave¶
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— whentrue, the SDK starts and stops trip monitoring automatically based on detected vehicle movement.crashDetectionMode— whentrue, the SDK evaluates potential crash events during monitored trips.
Step 3 — Register a listener (recommended before configurationSetup)¶
Step 4 — Start monitoring¶
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¶
Using RoadSaveListener (recommended)¶
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:
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
RoadSaveListenerfor new integrations. The broadcast mechanism requiresLocalBroadcastManagerand 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.
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:
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¶
- Verify
ACCESS_FINE_LOCATION(andACTIVITY_RECOGNITIONon API 29+) are granted at runtime before callingconfigurationSetup. - Check that
applicationIDandclientUserIDare non-empty and valid. UseErrorCode.ConfigErrorAuthorisationDenied(102) to detect invalid credentials. - Ensure the device has network access. The SDK must reach
https://api.roadsave.co.za/api/for registration. - Check logcat for
RoadSavetag 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.