Skip to content

Android SmartDrive framework

AigarsM edited this page Oct 8, 2026 · 27 revisions

SmartDrive SDK Documentation - v1.6.9 (12 October, 2026)

Tip

New integrations: start with the Android Simplified SDK Integration. Since SmartDrive 1.6.5 the SDK ships a facade, TripWrapper, that replaces the manual configuration, service control and broadcast handling this page describes with one host class, one typed configuration and five calls. This page remains the reference for the direct SCPSmartDriveManager API and for the permission and battery-optimisation requirements, which apply either way. Moving an existing app over: Android TripWrapper Migration Guide.

This document describes how ScopeTechnology framework "SmartDrive" works and how to integrate into your application. While the target audience is coders/developers, it brings better understanding for those who are not too tech-savvy as well.

SmartDrive framework main functionality is to record trips. It supports trip recording:

  • with Beacons (BLE device from ScopeTechnology).
  • with MHub837s (BLE device from ScopeTechnology).
  • without any BLE device (called "Smart" solution).

Related: Requirements & permissions · Reading the current trip · Release notes · Demo app

1. Installation Guide

Following instructions will allow you to integrate our SDK into your application. Additionally, here is the demo sample app that you can follow while going through this document.

1.1. Overview

In introduction we mentioned that SmartDrive has two configuration types:

  • for BLE devices (including ScopeTechnology - beacons and MHubs837s)
  • for no BLE devices (smart solution)

Important

It is important to apply correct configuration and permissions for your specific business needs.

1.2. Requirements

SDKs:

  • Minimum SDK: 24 (Android 7.0) for SmartDrive alone; 27 (Android 8.1) with the Wellness SDK
  • Target SDK: 35 (Android 15)

Permissions. SmartDrive needs location, activity recognition and notification access to record trips and to keep the user informed. Some permissions are already declared in the SDK manifest, some must be declared by your app, and all of them have to be requested at runtime. In addition, dependable trip start detection needs the app to be excluded from battery optimisation, which is a device setting rather than a permission. See the table below and the details in the following subsections.

Permissions at a glance

Capability Declared in Needed for If missing or denied
Location — ACCESS_FINE_LOCATION + ACCESS_BACKGROUND_LOCATION ("Allow all the time") SDK manifest + runtime prompt (1.2.1) All trip recording, BLE device scanning No trips recorded
Activity Recognition — ACTIVITY_RECOGNITION SDK manifest + runtime prompt (1.2.2) Trip detection in smart mode (no BLE devices) Trips detected late or not at all, ghost trips
Bluetooth — BLUETOOTH_SCAN, BLUETOOTH_CONNECT (Android 12+) BLE framework manifest + runtime prompt Beacon / MHub mode only No beacon trips
Notifications — POST_NOTIFICATIONS (Android 13+) App manifest + runtime prompt (1.2.3) Trip-in-progress notification and SDK warnings (location off, permission revoked, unsent trips, distracted driving) No trip indicator and no SDK warnings — recording problems go unnoticed
Phone state — READ_PHONE_STATE App manifest + runtime prompt Phone-call detection in Distracted Driving (2.2.0) Phone calls during trips not detected
Foreground service — FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, FOREGROUND_SERVICE_DATA_SYNC SDK manifest — no action needed Background trip recording —
Battery optimisation exemption Device setting — not a runtime permission (1.2.4) Timely trip start detection while the phone is idle (Doze) Trips start late or are missed after the phone has been idle
Battery saver off (Power saving mode) Device setting — not a permission (1.2.4) Trip start detection and steady location updates during drives Trips missed or started far too late; the phone keeps dropping into deep sleep, even while driving

Tip

PermissionsUtil.getNotGrantedPermissions() in the demo app builds the complete list of runtime permissions for the current Android version, including the separate background-location request required on Android 11+.

1.2.1. Location permissions

Location permissions are used for getting location data that allows us to draw trip route and analyze trip in details, it also is needed for BLE device scanning.

Important

Mandatory permissions (for all SmartDrive configurations).

Manifest.permission.ACCESS_FINE_LOCATION
Manifest.permission.ACCESS_BACKGROUND_LOCATION

Tip

Remember that for Build.VERSION_CODES.Q and higher both permissions are required, whereas below Build.VERSION_CODES.Q only ACCESS_FINE_LOCATION is needed.

Tip

use SDFPermissionUtil.getAllMandatoryLocationPermissions() to retrieve needed location permissions based on your device OS level.

1.2.2. Activity Recognition permission

Activity Recognition allows our algorithm to understand weather the user is in a car (so we can detect trip in timely manner) or is taking a walk (to avoid ghost trips).

Important

Mandatory permission only for "smart" solution (no BLE devices).

Manifest.permission.ACTIVITY_RECOGNITION

1.2.3. Notification permission

SmartDrive records trips from a foreground service, and Android requires every foreground service to show an ongoing notification. SmartDrive shows a "Trip in progress" notification while recording, and it also uses notifications to warn the user when something prevents trips from being recorded or sent:

  • location services turned off,
  • location permission revoked,
  • unsent trips older than 2 days / 1 week (see Section 5.1),
  • distracted driving detected during a trip.

Starting with Android 13 (API 33) notifications require the runtime permission POST_NOTIFICATIONS. It is not bundled with the SDK, so declare it in your app manifest:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

and request it at runtime together with the other permissions:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    permissionsRequired.add(arrayOf(Manifest.permission.POST_NOTIFICATIONS))
}

Important

If the permission is denied, the recording service still runs, but the user sees neither the trip-in-progress notification nor any of the SDK warnings listed above — recording problems go unnoticed until trips are missing. Treat the notification permission as required for dependable trip recording.

If the user has denied the permission permanently, requestPermissions() no longer shows a dialog. Open the app's notification settings instead so the user can re-enable notifications:

val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
    .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
context.startActivity(intent)

Note

All SDK notifications are posted to the notification channel default ("Application notifications", low importance), so the user can also silence them per channel in system settings. The title of the trip notification is the appName configured in Section 2.1.1, and tapping it opens the Activity passed to setTripActivity(). To show the trip notification only once the trip has covered a minimum distance, call showNotificationForValidTripOnly(true).

1.2.4. Battery optimisation

Android puts idle apps into Doze and App Standby, and most manufacturers add their own battery managers on top (Samsung Sleeping apps, Xiaomi / Huawei Autostart and similar). While the app is optimised this way, the system delays the activity-recognition and location wake-ups the SDK relies on to notice that a drive has started. Unlike the permissions above this is a device setting, not a runtime permission — there is no dialog the SDK can show for you.

  • Trip recording itself is not affected. Once a trip is running, the recorder is a foreground service and keeps working with optimisation on.
  • Trip start detection degrades. With optimisation on, trips may start late or be missed entirely after the phone has been parked and idle for a while — this is the most common cause of missing trips on Android.

Ask the user to exclude your app from battery optimisation. Check the current state with PowerManager:

fun Context.isIgnoringBatteryOptimizations(): Boolean {
    val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
    return powerManager.isIgnoringBatteryOptimizations(packageName)
}

If the app is not excluded yet, open the system dialog that asks the user to exclude it. The dialog requires the following permission in your app manifest:

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@SuppressLint("BatteryLife")
fun requestIgnoreBatteryOptimizations(context: Context) {
    val intent = if (context.isIgnoringBatteryOptimizations()) {
        // already excluded - open the system list so the user can review it
        Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
    } else {
        Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
            .setData(Uri.parse("package:${context.packageName}"))
    }
    context.startActivity(intent)
}

Battery saver (Power saving mode) is a separate, stronger problem. The exemption above only takes your app out of App Standby / Doze restrictions; it does not switch off the system-wide Battery saver that the user can turn on in Quick Settings or that the phone enables automatically at a low battery level. With Battery saver on, Android and the manufacturer's power manager throttle location, activity recognition and background work for every app, and the phone drops back into deep sleep far more aggressively — including while the user is driving. In the field this shows up as trips that are missed entirely or start many kilometres after the real departure, on a phone where the app is correctly excluded from battery optimisation.

  • Ask users to keep Battery saver off while they rely on trip recording. Most manufacturers offer no per-app exemption from it — the setting is all or nothing — so this is a user instruction, not something the SDK or your app can fix.
  • Detect it and tell the user. The SDK shows a notification when Battery saver is switched on. In your app, check PowerManager.isPowerSaveMode when the app comes to the foreground and offer the settings screen:
fun Context.isBatterySaverOn(): Boolean =
    (getSystemService(Context.POWER_SERVICE) as PowerManager).isPowerSaveMode

fun openBatterySaverSettings(context: Context) {
    context.startActivity(Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS))
}

Important

Treat the battery optimisation exemption as required for dependable trip start detection. Re-check it whenever the app comes to the foreground and show a message when the app is optimised again — users and system updates can re-enable optimisation at any time.

Note

Google Play reviews apps that declare REQUEST_IGNORE_BATTERY_OPTIMIZATIONS. Trip recording is an accepted use case, but explain in your store listing why the app needs it. On some devices the system dialog is not enough: Samsung users should additionally add the app to Never sleeping apps, and Xiaomi / Huawei users should enable Autostart for the app. The SDK logs Battery optimization feature is disabled. Which is good. when the exemption is in place — a quick check when investigating missed trips.

All runtime permission requests follow the standard Android flow — see Android Developer Guide - Runtime permissions.

1.3. Integration

The library (SDK) is published on a private maven repository. Before you follow integration steps, make sure Scope Technologies has invited user (your GitHub dev account) to the repository with correct access. After that, developer will need to create GitHub personal access token (classic) with read permission to GitHub packages.

Step 1. Configure gradle.properties file

github_username=<GITHUB_USERNAME>
github_key=<PERSONAL_ACCESS_TOKEN>

Step 2. Add repository to your project in gradle.build (top-level) file

repositories { 
    maven { 
        url "https://maven.pkg.github.com/scopetech/Frameworks-Mobile" 
        credentials { 
            username = github_username 
            password = github_key 
        } 
    } 
}

Step 3. Add dependecies to your project in gradle.build (module-level) file

implementation 'com.scopetechnology:smartdrive:1.6.9' 

// the following line is needed to send trips to server 
implementation 'com.scopetechnology:portal-api-base:1.2.9' 

// the following line is needed to access some helper classes. 
implementation 'com.scopetechnology:shared-classes:1.1.7'

If it is planned to use beacons for trip recording. Following dependency should be added:

implementation 'com.scopetechnology:ble:1.5.6'

Or take all of them at matching versions with one dependency, com.scopetechnology:sdk:1.1.9; see the Android Consolidated SDK documentation.

Since SmartDrive 1.6.6 (BLE 1.5.6, shared classes 1.1.7, consolidated SDK 1.1.6) the artifacts are built against API 36 and their AAR metadata requires the consuming app to use compileSdk 36 or later; a lower compileSdk fails the build at checkAarMetadata. targetSdk and minSdk are the app's own choice.

Important

Always confirm that you are using latest version.

2. SmartDrive Initialization

Note

Every setting in this section has a typed field on SmartDriveConfiguration when you integrate through the facade; the API mapping table lists them side by side. The code below is for direct SCPSmartDriveManager integrations.

2.1. Configuration

2.1.1. Initial Configuration

All core configuration happens using SCPSmartDriveManager helper class.

with (SCPSmartDriveManager.getInstance(context)) {
    appName = context.getString(R.string.app_name)
    setAppNameAbbreviation(BuildConfig.APP_NAME)
    setTripActivity(MainActivity::class.java) //This Activity will be opened when user clicks on notification
    recordTripServiceHandledFromAppClass(true)
}

2.1.2. Record Only - Beacon Trips / Smart Trips

We need to tell SmartDrive framework weather we want to use BLE devices for trip recording or use smart solution instead.

Important

If SmartDrive is intended to work with BLE devices

SCPSmartDriveManager.getInstance(context).tripRecordingOnlyWhenBeaconInRange = true
PrefUtil.setString(SharedConstants.PREFERENCES_CONFIG_TYPE, ConfigType.BLE.name)

and additionally we would want to start bluetooth service (this ties together with BLE framework):

BLEManager.retrieveInstance(context).startForegroundBluetoothService()

Important

If SmartDrive is intended to work with no BLE devices using smart solution

SCPSmartDriveManager.getInstance(context).tripRecordingOnlyWhenBeaconInRange = false
PrefUtil.setString(SharedConstants.PREFERENCES_CONFIG_TYPE, ConfigType.SMART.name)

2.1.3. Set recording vehicle

SmartDrive associates every recorded trip with a vehicle through its unit serial number. It must be set before the recording service starts (see Section 3.1).

Important

Without a selected vehicle, trip events have no vehicle ID and trips cannot be sent to the backend.

Step 1. Select a vehicle using PortalBaseApi framework (after login)
val vehicles = BasePortalApi.getInsuredVehicles()
BasePortalApi.setSelectedVehicle(vehicles.first())

Note

PortalBaseApi automatically selects the first vehicle in the InsuredVehicles list during login and caches the selection for subsequent app restarts. An explicit setSelectedVehicle call is only needed when the user switches to a different vehicle.

Step 2. Apply the selection to SmartDrive during configuration (every app start)

During SmartDrive configuration — before runRecordTripService() is called — ensure the selected vehicle's unit serial number is set:

BasePortalApi.getLiveSelectedVehicle().value?.let { vehicle ->
    SCPSmartDriveManager.getInstance(context).lastSelectedUnitSerialNumber = vehicle.unitSerialNumber
}
Alternative: without PortalBaseApi framework

If PortalBaseApi framework is not configured and the app uses real-time event sending, assign the MProfiler unit id directly:

SCPSmartDriveManager.getInstance(context).lastSelectedUnitSerialNumber = MProfilerUnitId

2.1.4. Auto Sending

If enabled true trip auto sending happens automatically, no additional code needed.

SCPSmartDriveManager.getInstance(context).isTripAutoSendingEnabled = true

2.1.5. Auto Resend Failed Trips

If enabled true framework will try to send failed trips automatically when next successful trip is sent.

SCPSmartDriveManager.getInstance(context).resendFailedTrips(true)

Warning

This feature only retries failed trips when the next trip is successfully sent. If no subsequent trip succeeds (e.g., user stops driving, persistent network issues), failed trips will remain in the local database indefinitely. For guaranteed delivery, implement your own retry logic — see Section 4.8. Retry Failed Trips.

2.1.6. End Trip Timer

Our solution uses trip end timer to validate if trip has truly finished (compared to heavy traffic with no movement for longer period of time). Trip end timer does not change recorded trip actual time range.

Warning

While you can change trip end timer based on your needs, it is strongly suggested to configure it between 3 - 5 minutes range to avoid ghost trips.

val tripEndTimerMinutes = 5.0f //Example for 5 minutes
SmartDriveUtils.setFloatPreference(
    context,
    MHubConstants.AUTOSTOP_THRESHOLD_PREF,
    tripEndTimerMinutes
)

2.1.7. Mode of Transport (MOT)

Mode of Transport is clever solution to avoid trip recording on pre-defined transport types. For instance, if you want to avoid public transport buses or trains, you can configure so and framework will discard them. By default - disabled.

val motSkipTrips = listOf(
    "Air", // Airplane
    "Water", // Boats, jet skis
    "Public", // Bus, trams etc.
    "Railway", // Train
    "OffRoad" // OffRoad
).toSet()

with (SCPSmartDriveManager.getInstance(context)) {
    motEnabled = true
    skipTripMOTs = motSkipTrips
}

2.1.8. Real Time Trip sending

Real time trip sending configuration by default is disabled. It is used to display trip that are still in progress in real time (unfinished trips).

private fun setupRealtimeTripSending(context: Context) {

    val mProfilerConfiguration = MProfilerConfiguration(
        "https://test.scopemp.net/Scope.MProfiler.ThirdPartyGateway.Api/api/v1/", // serviceURL
        "ServiceProviderName", // serviceProvider
        "m7JLq17YtFx7j7HS" // apiKey
    )

    SCPSmartDriveManager.getInstance(context).setupProfilerConfiguration(mProfilerConfiguration)

}

Note

MProfiler config values in the example are just a placeholders but in reality they will look very similar. To get MProfiler Configuration real values, kindly ask Scope support.

2.1.9. Accident Detection

When enabled, framework will be able to detect if driver has encountered serious accident. SDK will display a fragment view with a timer to the user. If user is not able to take action while timer runs out, we will call configured emergency phone number.

Note

This feature is available only for Beacon devices (BLE configuration).

val isAccidentDetectionEnabled = true
SmartDriveUtils.setBooleanPreference(
     context,
     MHubConstants.ACCIDENT_DETECTION_PREF,
     ConfigHelper.isAccidentDetectionEnabled()
)

2.2.0. Distracted Driving

When enabled will monitor driver actions during the trip. Following distracting driving types are available:

  • PHONE USE - phone is being used while driving. Browsing different apps, tilting it, shaking it and other similar actions will be caught.
  • PHONE CALLS - user is having a phone call while driving (WITHOUT headset that is considered safe).
val isDistractedDrivingEnabled = true
SmartDriveUtils.setBooleanPreference(
    context,
    MHubConstants.DISTRACTION_DETECTION_PREF,
    isDistractedDrivingEnabled
)

// set distraction types which should be tracked
SmartDriveUtils.setBooleanPreference(
     context,
     MHubConstants.DISTRACTION_DETECTION_TYPE_PHONE_USE_PREF,
     true
)

SmartDriveUtils.setBooleanPreference(
      context,
      MHubConstants.DISTRACTION_DETECTION_TYPE_PHONE_CALL_PREF,
      true
)

Tip

If you want to detect events based on minimum speed, make sure you are using library version v1.2.1+

SmartDriveUtils.setFloatPreference(
    context,
    MHubConstants.DISTRACTION_DETECTION_MINIMUM_SPEED_PREF,
    5.0f // If user speed is below 5km/h -> ignore events
)

3. Trip Recording

3.1. Start SmartDrive

To start SmartDrive simply call runRecordTripService when onboarding process has been finished for your app and view is ready. It will always check if service is running before creating a new one.

fun runSmartDrive(context: Context) {
    val smartDriveManager = SCPSmartDriveManager.getInstance(context)
    if (smartDriveManager.isTripAutoStartEnabled || smartDriveManager.activeTripInProgress()) {
        smartDriveManager.runRecordTripService()
    }
}

3.2. Stop SmartDrive

To stop RecordTripService (core logic of trip recording) call stopRecordTripService().

fun stopSmartDrive(context: Context) {
    SCPSmartDriveManager.getInstance(context).stopRecordTripService()
}

Tip

If you want to clean all resources for SmartDrive, use logout() function.

SCPSmartDriveManager.getInstance(context).logout()

Note

Calling logout function will automatically stop RecordTripService.

Caution

Calling logout function will remove all local trip data.

4. Trip Management

Trip management is handled using TripManagmentHelper class.

4.1. Start/Stop Helper

To start helper, use startHelper(). If you want to stop it, call stopHelper().

with (TripManagementHelper.getInstance(context)) {
    startHelper()
}

Tip

registerTripEventReceiver offers callback methods that are triggered on trip start or end - in case you want to add custom logic.

4.2. Trip List

If you want to retrieve local trip data, use:

SCPSmartDriveManager.getInstance(context).allTrips // all finished trips
SCPSmartDriveManager.getInstance(context).allUnsentTrips // all finished unsent trips 

4.3. Reading the current trip (status, location, distance, duration, speed)

While a trip is being recorded you can show a live "trip in progress" indicator in your app (status, current location, distance, duration, speed). Everything is exposed by the SDK as LiveData — no additional location client or timer is needed on the app side. There are two kinds of information: the recording status, which changes at trip boundaries, and the live components, which are updated while a trip is active.

Value API Type / unit Updated
Trip in progress liveActiveTripInProgress (top-level val in com.scope.common.utils) LiveData<Boolean> On trip start / end
Trip in progress (synchronous) SCPSmartDriveManager.activeTripInProgress() Boolean —
Current speed SCPSmartDriveManager.liveGPSSpeed LiveData<Float> — km/h Every GPS fix
Current location SCPSmartDriveManager.liveLocation LiveData<Location> Every GPS fix
Trip distance TripManagementHelper.tripDistance LiveData<Float> — km Every GPS fix while moving
Trip duration TripManagementHelper.tripDuration LiveData<Period> (Joda-Time) Every second
Trip start time SCPSmartDriveManager.tripStartTime DateTime? — null when no trip —
Trip ended SCPSmartDriveManager.liveTripEnded SingleLiveEvent<Unit> On trip end

Important

tripDistance and tripDuration are only updated after TripManagementHelper.startHelper() has been called (see Section 4.1).

Status — react to changes. Observe liveActiveTripInProgress, or call activeTripInProgress() when you need the value synchronously (for example when a screen is created):

import com.scope.common.utils.liveActiveTripInProgress

liveActiveTripInProgress.observe(this) { inProgress ->
    tripStatusView.text = if (inProgress) "Status: RECORDING" else "Status: IDLE"
}

// synchronous check
val recording = SCPSmartDriveManager.getInstance(context).activeTripInProgress()

Components — observe while active.

val smartDriveManager = SCPSmartDriveManager.getInstance(context)
val tripHelper = TripManagementHelper.getInstance(context)

// Speed in km/h
smartDriveManager.liveGPSSpeed.observe(this) { speed ->
    tripSpeedView.text = "${speed.toInt()}"
}

// Latest GPS fix received by the recorder
smartDriveManager.liveLocation.observe(this) { location ->
    location?.let { /* e.g. move a map marker to it.latitude / it.longitude */ }
}

// Distance in kilometres
tripHelper.tripDistance.observe(this) { distance ->
    tripDistanceView.text = "${(distance * 10).toInt() / 10.0}"
}

// Duration as Joda-Time Period, refreshed every second
tripHelper.tripDuration.observe(this) { period ->
    tripDurationView.text = String.format("%02d : %02d : %02d", period.hours, period.minutes, period.seconds)
}

Units and behaviour:

  • liveGPSSpeed is km/h, taken from the GPS fix. It is 0 when the fix carries no speed or when position updates stall, and it is reset to 0 when the recording service stops. If you display speed while no trip is in progress, consider treating small values (below 5 km/h) as 0 to hide GPS drift.
  • liveLocation is the latest validated GPS fix; it is reset to null when the recording service stops.
  • tripDistance is kilometres and is reset to 0 when the trip ends.
  • tripDuration is a Joda-Time Period counted from the trip start time. The helper refreshes it once per second, so no app-side timer is needed; it is reset when the trip ends.
  • liveActiveTripInProgress is true only for active trips. The SDK's internal passive trips (for example the cool-down after a manual trip stop) never set it.
  • These values live in process memory. If your app is relaunched while a trip is in progress, startHelper() re-arms the duration timer for the ongoing trip automatically.

Trip boundaries. To react to trip start and end use registerTripEventReceiver (Section 4.7) or observe liveTripEnded.

Trip counters. TripManagementHelper also exposes counters that are refreshed when a trip ends or finishes sending:

with (TripManagementHelper.getInstance(context)) {
    tripCount.observe(viewLifecycleOwner) {
        //Receive trip count (int)
    }
    unsentTripCount.observe(viewLifecycleOwner) {
        //Receive unsent trip count (int)
    }
    tripSendingFinished.observe(viewLifecycleOwner) {
        //Called when trip sending finished
    }
}

Tip

Reference implementation: SmartDriveHelper.kt (LiveData accessors) and the status screen in MainActivity.kt in the demo app.

4.4. Delete Trip

If your logic requires to delete specific trip, you can do so using:

SCPSmartDriveManager.getInstance(this).deleteTrip(trip)

4.5. Send Trips

If you handle trip sending logic manually, use:

SCPSmartDriveManager.getInstance(this).sendTrip(trip) // Send specific trip
SCPSmartDriveManager.getInstance(this).sendAllTrips() // Send all trips 

Caution

If you use sendAllTrips() remember that it takes into account Mode of Transport (MOT) configuration.

4.6. Update Processing Trips

This method checks if sent trips have finished processing on the server and are available via the trip API. It also recovers trips stuck in intermediate states or not properly finalized. It is recommended to call this when the app is opened.

TripManagementHelper.getInstance(this).checkProcessingTrips()

Note

This method does not retry trips that are marked as failed to send. For that, see Section 4.8. Retry Failed Trips.

4.7. Observe Trip Start/Stop

To receive updates on trip start or end, use following callback:

with (TripManagementHelper.getInstance(context)) {
   registerTripEventReceiver(object : TripManagementHelper.TripEventReceiver() {
        override fun onTripStarted() {
            // Trip started
        }

        override fun onTripEnded() {
            // Trip ended
        }
    })
    startHelper()
}

4.8. Retry Failed Trips

Trips can fail to send due to server timeouts, network issues, or the app process being killed by Android (e.g., thermal throttling, battery optimization) during transmission. In these cases, trips are marked as FAILED_TO_SEND in the local database.

Note

Through the facade this is TripWrapper.retryFailedTrips() (or ScopeSDK.retryFailedTrips()), which honours the mode-of-transport settings and, as a manual send, ignores the auto-resend setting and quarantine (since 1.6.9); the onTripSent callback plus tripCounts replace the TripManagementHelper observers below. The code in this section is for direct integrations.

The built-in resendFailedTrips feature (see Section 2.1.5) only retries failed trips after the next successful trip send. If no subsequent trip succeeds, failed trips remain unsent indefinitely.

To ensure reliable delivery, it is recommended to implement retry logic when the app comes to foreground:

// In your main Activity's onResume()
override fun onResume() {
    super.onResume()

    val smartDriveManager = SCPSmartDriveManager.getInstance(this)
    val unsentTrips = smartDriveManager.allUnsentTrips

    if (unsentTrips.isNotEmpty()) {
        smartDriveManager.sendAllTrips()
    }
}

Tip

sendAllTrips() is safe to call even when there are no unsent trips. It is also recommended to call checkProcessingTrips() alongside retry logic after app restarts to recover any trips stuck in intermediate states.

If your app uses Mode of Transport (MOT) feature, use sendAllUnsentTrips() instead and pass your MOT configuration. This method will postpone sending trips that have incomplete MOT reports — where API calls to validate trip locations may have failed and it is not yet confirmed that the trip was driven by a vehicle rather than another mode of transport.

val motEnabled = true
val motSkipTrips = setOf("Public", "Railway") // MOT types to exclude
val manualSendingDeadline: DateTime? = null // null to send all, or set a cutoff date

smartDriveManager.sendAllUnsentTrips(motEnabled, motSkipTrips, manualSendingDeadline)

To monitor unsent trip count and react when sending completes:

with (TripManagementHelper.getInstance(context)) {
    unsentTripCount.observe(viewLifecycleOwner) {
        // React to unsent trip count changes
    }
    tripSendingFinished.observe(viewLifecycleOwner) {
        // Called when trip sending finishes
    }
}

5. Utilities

5.1. Unsent Trip Notifications

SmartDrive framework caters for cases where trips have not been sent to Server side. This usually happens if user has no network. Two notifications are being sent:

  • After 2 days
  • After 1 week

If you want to disable notifications for unsent trips, use:

SCPSmartDriveManager.getInstance(context).checkForOldTrips(false)

5.2. Mock IN_VEHICLE state (for testing)

If you want to mock IN_VEHICLE state use:

SCPSmartDriveManager.getInstance(context).sendFakeInVehicleActivityUpdate(context)

Note

Used for smart (no BLE devices) solution only!

Related documentation

Clone this wiki locally