Skip to content

Android TripWrapper SDK

AigarsM edited this page Oct 8, 2026 · 4 revisions

Android TripWrapper SDK

The SmartDrive-level facade, com.scope.mhub.TripWrapper, for apps that integrate the SmartDrive SDK on its own. Apps on the consolidated SDK use the same calls and types through ScopeSDK; see Android Simplified SDK Integration. Last updated: 12 Oct 2026 for SmartDrive 1.6.9.

1. Overview

TripWrapper is the integration layer between your app and the SmartDrive recording engine. It owns the order of operations, decides when recording may run, applies your configuration to the engine, and delivers trip events, so your code supplies values and receives results.

1.1 What TripWrapper does

  • Arms and disarms the recording service when a session, a vehicle and the required permissions are all present.
  • Applies a typed, validated configuration to the engine, in the order the engine needs.
  • Remembers the vehicle PortalAPI announces, or the one a host without PortalAPI passes to vehicleSelected with its unit type, or asks the host for one.
  • Remembers the configuration start accepted and restores it at the next process start, so recording resumes after a reboot or a service restart before the app has its settings again.
  • Delivers trip start, update, end and upload results on the main thread.
  • Reports diagnostics, a health verdict, and a health snapshot in the log.
  • Lists the runtime permissions your configuration needs.

1.2 Architecture

Your app  ─ host (one question) + configuration ─►  TripWrapper  ─►  SCPSmartDriveManager / RecordTripService
          ◄─ callbacks, tripState, tripCounts, diagnostics ─┘

TripWrapper is the only class in the root package com.scope.mhub; the types a host handles live in com.scope.mhub.tripwrapper. It is additive: the direct manager API keeps working, and the configureSmartDrive closure lets you reach it for anything without a typed field.

Rule: once you integrate through TripWrapper, do not also start or stop the recording service, write engine preferences, or select the vehicle through SCPSmartDriveManager directly. The facade re-applies its configuration and would overwrite you. Anything without a typed field goes in configureSmartDrive, which runs after every field.

2. Requirements

  • minSdk 24; compileSdk 36 or later since 1.6.6, which the artifact's AAR metadata requires. Kotlin or Java; the host interface has default methods, so a Java host implements one method.
  • Runtime permissions, from TripWrapper.requiredPermissions(configuration):
Permission When
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION always; Android 12 requires both in one request
ACCESS_BACKGROUND_LOCATION Android 10 and later
ACTIVITY_RECOGNITION Android 10 and later, unless recording is beacon gated
POST_NOTIFICATIONS Android 13 and later
READ_PHONE_STATE distractedDrivingEnabled together with distractedDrivingPhoneCallEnabled, or speakerOffAfterCall
BLUETOOTH_SCAN, BLUETOOTH_CONNECT Android 12 and later, when recording is beacon gated
  • A Timber tree. The SDK logs through Timber and is silent without one.

3. Quick start

The SmartDriveDemo individual_sdks branch is this integration in working form: AppTripWrapperHost.kt, AppTripWrapperConfiguration.kt and SmartDriveHelper.kt, with the five SDKs as individual dependencies. Its master branch is the same app through the consolidated SDK.

Step 1 — write the host

class AppHost : TripWrapperHost {
    override fun isAuthorized(): Boolean = /* a user session exists */
}

Step 2 — initialize and start in Application.onCreate

val host = AppHost()
TripWrapper.initialize(applicationContext, host)   // in every process: restores the last accepted configuration

val configuration = TripWrapperConfiguration(
    mode = TripWrapperConfiguration.Mode.SMART,   // or BEACON
    appName = getString(R.string.app_name),
    appNameAbbreviation = "APP",
    notificationActivity = MainActivity::class.java,
    smartDrive = SmartDriveConfiguration(resendFailedTrips = true),
)
TripWrapper.start(applicationContext, configuration, host)   // whenever the settings are known or change

Then TripWrapper.authenticated() after login, TripWrapper.permissionsChanged() once every required permission is granted, and TripWrapper.logout() on logout. Any order. An app without PortalAPI also calls TripWrapper.vehicleSelected(unitSerialNumber, unitType) when the user picks a vehicle.

Step 3 — handle trip events

TripWrapper.onTripStarted = { trip -> }
TripWrapper.onTripUpdated = { distanceMeters, durationSeconds, speedKmh -> }
TripWrapper.onTripEnded   = { trip -> }
TripWrapper.onTripSent    = { trip, error -> }   // error is null when delivered

4. Host reference

TripWrapperHost is the one interface your app implements. The SDK asks it whenever something may have changed.

Question Required Meaning
isAuthorized(): Boolean yes A user session exists. Recording is armed only while this is true.
deviceID(): String? no, default null Unit serial number of the vehicle trips are recorded for. Null means the vehicle PortalAPI announced when it selected one, or the one passed to vehicleSelected; the facade remembers it across restarts. A non-null answer wins over both. Call vehicleChanged() when your answer changes.
onBeaconTripStart() no, default no-op Called when a beacon starts a trip; hosts with BLE can snapshot the beacon in range.

The selected vehicle's unit type is not a host question. A host without PortalAPI passes it with the vehicle to vehicleSelected(unitSerialNumber, unitType), and the facade remembers it next to the serial, across restarts, so the recording decision below survives a restart like the serial does. selectedVehicleUnitType() reads it back for the app's own screens. PortalAPI's announcement carries no type and leaves the decision to mode.

Who overrides deviceID(). Only an app on PortalAPI whose vehicle rule differs from PortalAPI's. PortalAPI announces only SmartDrive vehicles whose portal permissions are all granted. An app without PortalAPI leaves deviceID() alone and passes the vehicle it selects, with its unit type, to vehicleSelected, which stores both the way an announcement is stored; the call is ignored before initialize or start, one reason to call initialize from Application.onCreate. Leave deviceID() alone when the vehicle is announced or selected; override it when your app picks it by a rule of its own while PortalAPI announces another.

Recording decision, from the unit type given to vehicleSelected and smartDrive.trackMHubVehicles:

Unit type trackMHubVehicles Result
MHUB true Distraction-only trips, started from activity recognition
MHUB false No recording service
VIRTUAL_BLE any Normal recording, beacon gated
VIRTUAL_SMART, UNKNOWN any Normal recording
none given any Normal recording; gated when mode is BEACON

An MHub records the trip itself and the phone only adds the distraction events, so its trips are not gated on the unit's beacon and need no Bluetooth contact with it. Beacon gating applies to VIRTUAL_BLE vehicles, and to every vehicle in BEACON mode when no unit type is given. A change of gating at runtime, a switch between a beacon-gated vehicle and a sensor-only one, reaches the running recording service without a restart.

Logging and notifications are not part of the host: the SDK logs through Timber and posts its own notifications behind typed switches.

5. Data model

Type Fields
TripRecord tripId, portalId, startDate, endDate (null while recording), startLocation, endLocation, distanceMeters, durationSeconds, maxSpeedKmh, status, unitSerialNumber, distractionOnly, events (a list of TripRecord.Event)
TripStatus RECORDING, READY_TO_SEND, PENDING, SENDING, DELIVERED, FAILED
TripCounts local, unsent, failed, delivered; unsent and failed count finished trips only
TripState Idle; Recording(trip, distanceMeters, durationSeconds, speedKmh); Ended(trip)
VehicleUnitType MHUB, VIRTUAL_BLE, VIRTUAL_SMART, UNKNOWN
TripWrapperDiagnostics See Android Diagnostics and Health Snapshot

Units are in the names: metres, seconds, km/h.

6. API reference

Every member of TripWrapper is static (@JvmStatic).

6.1 Lifecycle

Call Effect
initialize(context, host) Call from Application.onCreate, in every process. Restores the configuration the last start accepted, unless the user logged out since, and arms recording at once; with none remembered it waits for start. The restored configuration has no configureSmartDrive closure; the next start brings it back.
start(context, configuration, host) Applies the configuration and remembers it for initialize. Call whenever the settings are known or change, before or after login; a configuration equal to the one in effect changes nothing. Refuses a configuration that cannot work with an IllegalArgumentException naming the field. Schedules the SDK's daily trip-retention sweep for smartDrive.tripRetentionDays. Works without initialize, then without the restore.
authenticated() Re-checks the session and arms recording if everything else is present. Safe to repeat.
permissionsChanged() Re-checks the required permissions. Call from the permission-result callback.
vehicleChanged() For hosts that answer deviceID() themselves: re-reads it. Hosts that leave the vehicle to PortalAPI never need it.
vehicleSelected(unitSerialNumber, unitType = null) For hosts without PortalAPI: the app selected this vehicle. Remembered with its unit type, recording re-evaluated. A host that answers deviceID() still wins for the serial. Ignored before initialize or start.
selectedVehicleUnitType() The unit type remembered with the selected vehicle, or null when none was given.
stop() Stops recording, keeps configuration and local trips. authenticated() arms again.
logout() Stops recording and clears the vehicle selection, the remembered configuration and local trips.
isInitialized True while a configuration is in effect: accepted by start, or restored by initialize.

6.2 Trip events

Member Delivery
onTripStarted: (TripRecord) -> Unit Main thread, when a trip starts, including a trip already recording when the facade arms after an app restart.
onTripUpdated: (distanceMeters, durationSeconds, speedKmh) -> Unit Main thread, on every position while recording.
onTripEnded: (TripRecord) -> Unit Main thread, when a trip stops recording.
onTripSent: (TripRecord, error: String?) -> Unit Main thread, when an upload finishes; error is the failure name or null. tripCounts is refreshed first.
listener: TripListener? Java-friendly alternative to the four lambdas.
tripState: StateFlow<TripState> For Compose, or Views through asLiveData().
tripCounts: StateFlow<TripCounts> For a Pending trips screen. Finished trips only; the trip being recorded is in no count. Refreshed at trip end, after every upload result, when the SDK deletes trips on its own (backend confirmation, retention, mode-of-transport reject) or changes a status outside an upload, and set to TripCounts.NONE on logout; before 1.6.7 only the first two applied.

6.3 Trip access and actions

Call Returns
getCurrentTrip() The trip being recorded, or null.
getLocalTrips() Every trip stored on the phone.
getTargetTrips() Trips not yet delivered, failed ones included.
retryFailedTrips() Sends unsent trips now, honouring the mode-of-transport settings. A manual send ignores the automatic-retry settings: it also sends a trip quarantined after repeated backend rejects and, with resendFailedTrips off, the failed trips the SDK leaves for the user (since 1.6.9; until then the call ran the SDK's own eligibility check and sent nothing in those cases). The SDK also retries on its own when resendFailedTrips is on.
confirmProcessingTrips(onDone = null) Runs the processing-confirmation sweep now and calls onDone on the main thread when it is over, tripCounts refreshed first: uploaded trips the Portal already lists leave the delivered count. Call it before showing trips fetched from the Portal, confirmProcessingTrips { fetchTrips() }. The SDK also sweeps ten minutes after each upload, every eight hours, and on every return to the foreground while such trips exist; a sweep within two minutes of the previous one is skipped. Since 1.6.8.

6.4 Diagnostics

Call Returns
getDiagnostics() A TripWrapperDiagnostics picture of the integration.
isHealthy() Its verdict.
getMissingProtocols() Integration gaps that stay silent at runtime: no session after authenticated, no vehicle once there is a session, no Timber tree.
logHealthSnapshot(trigger = MANUAL) Writes the snapshot block to the log, as the SDK does itself at start, trip start and trip end.
requiredPermissions(configuration) The runtime permissions this configuration needs on this device.

6.5 Configuration

TripWrapperConfiguration(mode, appName, appNameAbbreviation, notificationActivity, realtimeSending = false, autoSend = true, realtime = null, smartDrive = SmartDriveConfiguration()). Java uses TripWrapperConfiguration.Builder. Configurations compare by value, so one built again with the same settings is no change for start; a configureSmartDrive closure compares by instance.

SmartDriveConfiguration holds every engine setting, with the SDK's own defaults, so an empty one changes nothing:

Group Fields
Trip end autostopThresholdSeconds (300), minTripDistanceMeters (102), locationAccuracy (320 m)
Sending resendFailedTrips (false), beaconTripAutoSend (null: follow autoSend), pendingTripsManualSendingHoursDelay (0)
Retention tripRetentionDays (30): days a finished trip stays on the phone before the daily sweep deletes it; unsent trips get twice that, never past the Portal's 30-day processing horizon. null keeps trips
Notifications showNotificationForValidTripOnly (false), showDistractedDriverNotification (false), showOldTripReminders (true)
Distraction distractedDrivingEnabled (false), distractedDrivingPhoneUseEnabled (true), distractedDrivingPhoneCallEnabled (true), distractionMinSpeedKmh (0), distractionSensor
Mode of transport motEnabled (false), motSkipTypes, motElevationKey, motStaticMapsKey, motChecks
Accident accidentDetectionEnabled (false), accidentValidation (required when enabled), positionAfterIncidentDurationSeconds (300), positionAfterIncidentIntervalSeconds (20), speakerOffAfterCall (false)
Driving behaviour drivingBehaviour (0.29 g, 0.31 g, 0.25 g, all detectors on, 1000 ms)
MHub trackMHubVehicles (false; requires realtimeSending)
Escape hatch configureSmartDrive: ((SCPSmartDriveManager) -> Unit)?, runs after every field

Refused at start: realtimeSending without realtime, trackMHubVehicles without realtimeSending, pendingTripsManualSendingHoursDelay combined with realtimeSending, motEnabled without motStaticMapsKey, accidentDetectionEnabled without accidentValidation, and any field out of its range.

7. Migrating from direct SCPSmartDriveManager integration

7.1 Key rule

The facade owns the engine. Delete the code that started and stopped the recording service, wrote preferences and selected the vehicle, and express the same intent as configuration.

7.2 Migration steps

  1. Replace your configure routine with a TripWrapperConfiguration; the mapping table below says where each setting went.
  2. Write a host that answers isAuthorized(). Delete the code that seeded the vehicle serial number: an app on PortalAPI gets the vehicle announced, an app without PortalAPI passes it to vehicleSelected with its unit type, and only an app that picks the vehicle by a rule PortalAPI does not know makes that rule deviceID().
  3. Replace start and stop calls with initialize in Application.onCreate, start where your settings are known, authenticated after login and logout, and add permissionsChanged() where your permission flow finishes.
  4. Replace broadcast receivers and helper LiveData with the four callbacks.
  5. Run once and read the health snapshot in the log; it names anything still missing.

The full walkthrough is the Android TripWrapper Migration Guide.

7.3 API mapping

Direct manager use Through TripWrapper
setAppName, setAppNameAbbreviation, setTripActivity, recordTripServiceHandledFromAppClass appName, appNameAbbreviation, notificationActivity; the last is handled
setTripRecordingOnlyWhenBeaconInRange(true) mode = BEACON
lastSelectedUnitSerialNumber = ... PortalAPI's announcement; vehicleSelected(unitSerialNumber, unitType) for an app without PortalAPI; deviceID() on the host for a rule of the app's own
setTripAutoSendingEnabled, setIBeaconTripAutoSending autoSend, smartDrive.beaconTripAutoSend
Scheduling DeleteOldTripsWorker yourself smartDrive.tripRetentionDays; start schedules the sweep
resendFailedTrips(true) smartDrive.resendFailedTrips
AUTOSTOP_THRESHOLD_PREF (minutes) smartDrive.autostopThresholdSeconds
setMinimumTripDistance, REGULAR_LOCATION_ACCURACY_THRESHOLD_PREF minTripDistanceMeters, locationAccuracy
setMOTEnabled, setStaticMapsApiKey, setElevationApiKey, setSkipTripMOTs, setCustom*MOT* motEnabled, motStaticMapsKey, motElevationKey, motSkipTypes, motChecks
setupProfilerConfiguration(...) realtimeSending = true, realtime = MProfilerConfiguration(...)
ACCIDENT_DETECTION_* preferences, setPositionAfterIncident* accidentDetectionEnabled, accidentValidation, positionAfterIncidentDurationSeconds, positionAfterIncidentIntervalSeconds
DISTRACTION_DETECTION_* preferences distractedDrivingEnabled, distractedDrivingPhoneUseEnabled, distractedDrivingPhoneCallEnabled, distractionMinSpeedKmh, distractionSensor
showNotificationForValidTripOnly, showDistractedDriverNotification, checkForOldTrips the three notification fields
enableTripAutoStart() + runRecordTripService() initialize(...) and start(...), then authenticated()
disableTripAutoStart() + stopRecordTripService() stop() or logout()
allTrips, allUnsentTrips getLocalTrips(), getTargetTrips()
sendAllTrips(), sendAllUnsentTrips(...) retryFailedTrips()
Trip start/end broadcast receivers, TripManagementHelper LiveData the four callbacks, tripState, tripCounts
sendFakeInVehicleActivityUpdate(context) unchanged; a test affordance on the manager, not part of the facade
Any setter without a field smartDrive.configureSmartDrive = { manager -> ... }

8. Integration checklist

  • Dependency added and resolving; a Timber tree planted.
  • Host answers isAuthorized(); deviceID() overridden only if your app picks the vehicle by its own rule; vehicleSelected called on vehicle selection if your app has no PortalAPI.
  • initialize in Application.onCreate; configuration built; start runs without an exception.
  • authenticated() after login, permissionsChanged() after the permission flow, logout() on logout.
  • Permissions requested from requiredPermissions(configuration), background location in its own dialog.
  • The four callbacks, or TripListener, wired.
  • No remaining direct calls that start or stop the service, write engine preferences or select the vehicle.
  • isHealthy() true on a logged-in device with permissions; the health snapshot in the log shows no warning marks.

Clone this wiki locally