diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml
index 156797b6..6897078a 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/example/src/main/AndroidManifest.xml
@@ -14,6 +14,7 @@
+
?, user: RadarUser?) {
// pass through flush replay onComplete for track callback
- callback?.onComplete(status)
+ if (status != RadarStatus.SUCCESS && RadarSettings.getSdkConfiguration(context).useOfflineRTOUpdates) {
+ RadarOfflineManager().contextualizeLocation(context, trackLocation, object : RadarOfflineManager.RadarOfflineCallback {
+ override fun onComplete(config: RadarConfig?) {
+ if (config != null) {
+ callback?.onComplete(status, null, null, null, null, config)
+ } else {
+ callback?.onComplete(status)
+ }
+ }
+ })
+ } else {
+ callback?.onComplete(status)
+ }
}
}
)
@@ -425,9 +439,20 @@ internal class RadarApiClient(
}
Radar.sendError(status)
-
- callback?.onComplete(status)
-
+ if (RadarSettings.getSdkConfiguration(context).useOfflineRTOUpdates) {
+ Radar.logger.d("network issue encountered, falling back to RadarOfflineManager")
+ RadarOfflineManager().contextualizeLocation(context, location, object : RadarOfflineManager.RadarOfflineCallback {
+ override fun onComplete(config: RadarConfig?) {
+ if (config != null) {
+ callback?.onComplete(status, null, null, null, null, config)
+ } else {
+ callback?.onComplete(status)
+ }
+ }
+ })
+ } else {
+ callback?.onComplete(status)
+ }
return
}
diff --git a/sdk/src/main/java/io/radar/sdk/RadarLocationManager.kt b/sdk/src/main/java/io/radar/sdk/RadarLocationManager.kt
index c516105d..95dd01e1 100644
--- a/sdk/src/main/java/io/radar/sdk/RadarLocationManager.kt
+++ b/sdk/src/main/java/io/radar/sdk/RadarLocationManager.kt
@@ -353,7 +353,8 @@ internal class RadarLocationManager(
}
}
- private fun replaceSyncedGeofences(radarGeofences: Array?) {
+ internal fun replaceSyncedGeofences(radarGeofences: Array?) {
+ RadarState.setNearbyGeofences(context, radarGeofences)
this.removeSyncedGeofences() { success ->
this.addSyncedGeofences(radarGeofences)
}
@@ -620,7 +621,9 @@ internal class RadarLocationManager(
config: RadarConfig?,
token: RadarVerifiedLocationToken?
) {
- locationManager.replaceSyncedGeofences(nearbyGeofences)
+ if (status == RadarStatus.SUCCESS) {
+ locationManager.replaceSyncedGeofences(nearbyGeofences)
+ }
if (options.foregroundServiceEnabled && foregroundService.updatesOnly) {
locationManager.stopForegroundService()
diff --git a/sdk/src/main/java/io/radar/sdk/RadarOfflineManager.kt b/sdk/src/main/java/io/radar/sdk/RadarOfflineManager.kt
new file mode 100644
index 00000000..4c817d06
--- /dev/null
+++ b/sdk/src/main/java/io/radar/sdk/RadarOfflineManager.kt
@@ -0,0 +1,91 @@
+package io.radar.sdk
+
+import android.content.Context
+import android.location.Location
+import io.radar.sdk.model.RadarRemoteTrackingOptions
+import io.radar.sdk.model.RadarCircleGeometry
+import io.radar.sdk.model.RadarConfig
+import io.radar.sdk.model.RadarCoordinate
+import io.radar.sdk.model.RadarPolygonGeometry
+import org.json.JSONObject
+
+class RadarOfflineManager {
+ interface RadarOfflineCallback {
+ fun onComplete(config: RadarConfig?)
+ }
+ internal fun contextualizeLocation(context: Context, location: Location, callback: RadarOfflineCallback) {
+ var newGeofenceIds = mutableSetOf()
+ var newGeofenceTags = mutableSetOf()
+ val nearbyGeofences = RadarState.getNearbyGeofences(context)
+ if (nearbyGeofences == null) {
+ Radar.logger.d("skipping as no synced nearby geofence")
+ callback.onComplete(null)
+ return
+ }
+ for (geofence in nearbyGeofences) {
+ var center: RadarCoordinate? = null
+ var radius = 100.0
+ if (geofence.geometry is RadarCircleGeometry) {
+ center = geofence.geometry.center
+ radius = geofence.geometry.radius
+ } else if (geofence.geometry is RadarPolygonGeometry) {
+ center = geofence.geometry.center
+ radius = geofence.geometry.radius
+ } else {
+ Radar.logger.e("Unsupported geofence geometry type")
+ continue
+ }
+ if (isPointInsideCircle(center, radius, location)) {
+ newGeofenceIds.add(geofence._id)
+ if (geofence.tag != null) {
+ newGeofenceTags.add(geofence.tag)
+ }
+ }
+ }
+ RadarState.setGeofenceIds(context,newGeofenceIds)
+ val sdkConfiguration = RadarSettings.getSdkConfiguration(context)
+ val rampUpGeofenceTags = RadarRemoteTrackingOptions.getGeofenceTagsWithKey(sdkConfiguration.remoteTrackingOptions, "inGeofence")
+ var isRampedUp = false
+ if (!rampUpGeofenceTags.isNullOrEmpty()) {
+ for (tag in rampUpGeofenceTags) {
+ if (newGeofenceTags.contains(tag)) {
+ isRampedUp = true
+ break
+ }
+ }
+ }
+ var newTrackingOptions: RadarTrackingOptions? = null
+ if (isRampedUp) {
+ // ramp up
+ Radar.logger.d("Ramp up geofences with trackingOptions: $sdkConfiguration.inGeofenceTrackingOptions")
+ newTrackingOptions = RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(sdkConfiguration.remoteTrackingOptions, "inGeofence")
+ } else {
+ val tripOptions = RadarSettings.getTripOptions(context)
+ if (tripOptions != null && RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(sdkConfiguration.remoteTrackingOptions, "onTrip") != null){
+ Radar.logger.d("Ramp down geofences with trackingOptions: $sdkConfiguration.6onTripTrackingOptions")
+ newTrackingOptions = RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(sdkConfiguration.remoteTrackingOptions, "onTrip")
+ } else {
+ Radar.logger.d("Ramp down geofences with trackingOptions: $sdkConfiguration.defaultTrackingOptions")
+ newTrackingOptions = RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(sdkConfiguration.remoteTrackingOptions, "default")
+ }
+ }
+ if (newTrackingOptions != null) {
+ val metaDict = JSONObject()
+ metaDict.put("trackingOptions", newTrackingOptions.toJson())
+ val configDict = JSONObject()
+ configDict.put("meta", metaDict)
+ callback.onComplete(RadarConfig.fromJson(configDict))
+ return
+ }
+ callback.onComplete(null)
+ return
+ }
+
+ private fun isPointInsideCircle(center: RadarCoordinate, radius: Double, point: Location): Boolean {
+ val centerLocation = Location("centerLocation")
+ centerLocation.latitude = center.latitude
+ centerLocation.longitude = center.longitude
+ val distanceBetween = point.distanceTo(centerLocation)
+ return distanceBetween <= radius
+ }
+}
diff --git a/sdk/src/main/java/io/radar/sdk/RadarState.kt b/sdk/src/main/java/io/radar/sdk/RadarState.kt
index 5710582c..d6703fe5 100644
--- a/sdk/src/main/java/io/radar/sdk/RadarState.kt
+++ b/sdk/src/main/java/io/radar/sdk/RadarState.kt
@@ -7,6 +7,8 @@ import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.edit
import io.radar.sdk.model.RadarBeacon
+import io.radar.sdk.model.RadarGeofence
+import org.json.JSONArray
import org.json.JSONObject
internal object RadarState {
@@ -39,6 +41,7 @@ internal object RadarState {
private const val KEY_LAST_BEACON_UUIDS = "last_beacon_uuids"
private const val KEY_LAST_BEACON_UIDS = "last_beacon_uids"
private const val KEY_LAST_MOTION_ACTIVITY = "last_motion_activity"
+ private const val KEY_NEARBY_GEOFENCES = "nearby_geofences"
private fun getSharedPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences("RadarSDK", Context.MODE_PRIVATE)
@@ -277,4 +280,15 @@ internal object RadarState {
}
}
+ internal fun getNearbyGeofences(context: Context): Array? {
+ val jsonString = getSharedPreferences(context).getString(KEY_NEARBY_GEOFENCES, "")
+ val jsonArray = jsonString?.let { JSONArray(it) }
+ return RadarGeofence.fromJson(jsonArray)
+ }
+
+ internal fun setNearbyGeofences(context: Context, nearbyGeofences: Array?) {
+ val jsonString = RadarGeofence.toJson(nearbyGeofences).toString()
+ getSharedPreferences(context).edit { putString(KEY_NEARBY_GEOFENCES, jsonString) }
+ }
+
}
diff --git a/sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt b/sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt
index 751e45cb..94ab0179 100644
--- a/sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt
+++ b/sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt
@@ -125,7 +125,7 @@ internal class RadarVerificationManager(
config: RadarConfig?,
token: RadarVerifiedLocationToken?
) {
- if (status == Radar.RadarStatus.SUCCESS) {
+ if (config != null) {
Radar.locationManager.updateTrackingFromMeta(
config?.meta
)
diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarConfig.kt b/sdk/src/main/java/io/radar/sdk/model/RadarConfig.kt
index 7630e721..43a6d176 100644
--- a/sdk/src/main/java/io/radar/sdk/model/RadarConfig.kt
+++ b/sdk/src/main/java/io/radar/sdk/model/RadarConfig.kt
@@ -2,7 +2,7 @@ package io.radar.sdk.model
import org.json.JSONObject
-internal data class RadarConfig(
+data class RadarConfig(
val meta: RadarMeta,
val googlePlayProjectNumber: Long?,
val nonce: String?
diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarGeofence.kt b/sdk/src/main/java/io/radar/sdk/model/RadarGeofence.kt
index dacab0c9..5d447812 100644
--- a/sdk/src/main/java/io/radar/sdk/model/RadarGeofence.kt
+++ b/sdk/src/main/java/io/radar/sdk/model/RadarGeofence.kt
@@ -87,13 +87,13 @@ class RadarGeofence(
} ?: RadarCoordinate(0.0, 0.0)
val radius = obj.optDouble(FIELD_GEOMETRY_RADIUS)
val geometry = when (obj.optString(FIELD_TYPE)) {
- TYPE_CIRCLE -> {
+ TYPE_CIRCLE, TYPE_GEOMETRY_CIRCLE -> {
RadarCircleGeometry(
center,
radius
)
}
- TYPE_POLYGON, TYPE_ISOCHRONE -> {
+ TYPE_POLYGON, TYPE_ISOCHRONE, TYPE_GEOMETRY_POLYGON -> {
val geometryObj = obj.optJSONObject(FIELD_GEOMETRY)
val coordinatesArr = geometryObj?.optJSONArray(FIELD_COORDINATES)
if (coordinatesArr != null) {
diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarMeta.kt b/sdk/src/main/java/io/radar/sdk/model/RadarMeta.kt
index bc3de1fb..7de26f9c 100644
--- a/sdk/src/main/java/io/radar/sdk/model/RadarMeta.kt
+++ b/sdk/src/main/java/io/radar/sdk/model/RadarMeta.kt
@@ -3,7 +3,7 @@ package io.radar.sdk.model
import io.radar.sdk.RadarTrackingOptions
import org.json.JSONObject
-internal data class RadarMeta(
+data class RadarMeta(
val remoteTrackingOptions: RadarTrackingOptions?,
val sdkConfiguration: RadarSdkConfiguration?,
) {
diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarRemoteTrackingOptions.kt b/sdk/src/main/java/io/radar/sdk/model/RadarRemoteTrackingOptions.kt
new file mode 100644
index 00000000..dd33cb61
--- /dev/null
+++ b/sdk/src/main/java/io/radar/sdk/model/RadarRemoteTrackingOptions.kt
@@ -0,0 +1,94 @@
+package io.radar.sdk.model
+
+import io.radar.sdk.RadarTrackingOptions
+import org.json.JSONArray
+import org.json.JSONObject
+
+class RadarRemoteTrackingOptions(
+ val type: String,
+ val trackingOptions: RadarTrackingOptions,
+ val geofenceTags: Array?
+) {
+ internal companion object {
+ private const val TYPE_ID = "type"
+ private const val TRACKING_OPTIONS = "trackingOptions"
+ private const val GEOFENCE_TAGS = "geofenceTags"
+
+ @JvmStatic
+ fun fromJson(obj: JSONObject?): RadarRemoteTrackingOptions? {
+ if (obj == null) {
+ return null
+ }
+ val type = obj.optString(TYPE_ID)
+ val trackingOptions = RadarTrackingOptions.fromJson(obj.optJSONObject(TRACKING_OPTIONS))
+ val geofenceTags = obj.optJSONArray(GEOFENCE_TAGS)?.let { tags ->
+ (0 until tags.length()).map { index ->
+ tags.getString(index)
+ }.toTypedArray()
+ }
+ return RadarRemoteTrackingOptions(type, trackingOptions, geofenceTags)
+ }
+
+ @JvmStatic
+ fun fromJson(arr: JSONArray?): Array? {
+ if (arr == null) {
+ return null
+ }
+ return Array(arr.length()) { index ->
+ fromJson(arr.optJSONObject(index))
+ }.filterNotNull().toTypedArray()
+ }
+
+ @JvmStatic
+ fun toJson(remoteTrackingOptions: Array?): JSONArray? {
+ if (remoteTrackingOptions == null) {
+ return null
+ }
+ val arr = JSONArray()
+ remoteTrackingOptions.forEach { remoteTrackingOption ->
+ arr.put(remoteTrackingOption.toJson())
+ }
+ return arr
+ }
+
+ @JvmStatic
+ fun getRemoteTrackingOptionsWithKey(remoteTrackingOptions: Array?, key: String): RadarTrackingOptions? {
+ if (remoteTrackingOptions == null) {
+ return null
+ }
+ for (remoteTrackingOption in remoteTrackingOptions) {
+ if (remoteTrackingOption.type == key) {
+ return remoteTrackingOption.trackingOptions
+ }
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun getGeofenceTagsWithKey(remoteTrackingOptions: Array?, key: String): Array? {
+ if (remoteTrackingOptions == null) {
+ return null
+ }
+ var geofenceTags: Array? = null
+ for (remoteTrackingOption in remoteTrackingOptions) {
+ if (remoteTrackingOption.type == key) {
+ geofenceTags = remoteTrackingOption.geofenceTags
+ }
+ }
+ return geofenceTags
+ }
+ }
+
+ fun toJson(): JSONObject {
+ val obj = JSONObject()
+ obj.putOpt(TYPE_ID, type)
+ obj.putOpt(TRACKING_OPTIONS, trackingOptions.toJson())
+ val geofenceTagsArr = JSONArray()
+ if (geofenceTags != null) {
+ geofenceTags.forEach { geofenceTag -> geofenceTagsArr.put(geofenceTag) }
+ obj.putOpt(GEOFENCE_TAGS, geofenceTagsArr)
+ }
+ return obj
+ }
+
+}
\ No newline at end of file
diff --git a/sdk/src/main/java/io/radar/sdk/model/RadarSdkConfiguration.kt b/sdk/src/main/java/io/radar/sdk/model/RadarSdkConfiguration.kt
index f6403ac0..f467708f 100644
--- a/sdk/src/main/java/io/radar/sdk/model/RadarSdkConfiguration.kt
+++ b/sdk/src/main/java/io/radar/sdk/model/RadarSdkConfiguration.kt
@@ -9,7 +9,7 @@ import org.json.JSONObject
/**
* Represents server-side configuration settings.
*/
-internal data class RadarSdkConfiguration(
+data class RadarSdkConfiguration(
val maxConcurrentJobs: Int,
val schedulerRequiresNetwork: Boolean,
val usePersistence: Boolean,
@@ -21,7 +21,9 @@ internal data class RadarSdkConfiguration(
val trackOnceOnAppOpen: Boolean,
val useLocationMetadata: Boolean,
val useOpenedAppConversion: Boolean = false,
-) {
+ val useOfflineRTOUpdates: Boolean,
+ val remoteTrackingOptions: Array?,
+ ) {
companion object {
private const val MAX_CONCURRENT_JOBS = "maxConcurrentJobs"
private const val DEFAULT_MAX_CONCURRENT_JOBS = 1
@@ -35,7 +37,8 @@ internal data class RadarSdkConfiguration(
private const val TRACK_ONCE_ON_APP_OPEN = "trackOnceOnAppOpen"
private const val USE_LOCATION_METADATA = "useLocationMetadata"
private const val USE_OPENED_APP_CONVERSION = "useOpenedAppConversion"
-
+ private const val USE_OFFLINE_RTO_UPDATES = "useOfflineRTOUpdates"
+ private const val ALTERNATIVE_TRACKING_OPTIONS = "remoteTrackingOptions"
fun fromJson(json: JSONObject?): RadarSdkConfiguration {
// set json as empty object if json is null, which uses fallback values
@@ -53,6 +56,8 @@ internal data class RadarSdkConfiguration(
config.optBoolean(TRACK_ONCE_ON_APP_OPEN, false),
config.optBoolean(USE_LOCATION_METADATA, false),
config.optBoolean(USE_OPENED_APP_CONVERSION, true),
+ config.optBoolean(USE_OFFLINE_RTO_UPDATES, false),
+ config.optJSONArray(ALTERNATIVE_TRACKING_OPTIONS)?.let { RadarRemoteTrackingOptions.fromJson(it) },
)
}
@@ -82,6 +87,8 @@ internal data class RadarSdkConfiguration(
putOpt(TRACK_ONCE_ON_APP_OPEN, trackOnceOnAppOpen)
putOpt(USE_LOCATION_METADATA, useLocationMetadata)
putOpt(USE_OPENED_APP_CONVERSION, useOpenedAppConversion)
+ putOpt(USE_OFFLINE_RTO_UPDATES, useOfflineRTOUpdates)
+ putOpt(ALTERNATIVE_TRACKING_OPTIONS, RadarRemoteTrackingOptions.toJson(remoteTrackingOptions))
}
}
}
diff --git a/sdk/src/test/java/io/radar/sdk/RadarTest.kt b/sdk/src/test/java/io/radar/sdk/RadarTest.kt
index e4245056..1f928091 100644
--- a/sdk/src/test/java/io/radar/sdk/RadarTest.kt
+++ b/sdk/src/test/java/io/radar/sdk/RadarTest.kt
@@ -5,6 +5,7 @@ import android.location.Location
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.radar.sdk.Radar.locationManager
import io.radar.sdk.model.*
import org.json.JSONObject
import org.junit.Assert.*
@@ -331,7 +332,7 @@ class RadarTest {
icon = 1337,
updatesOnly = true,
))
- // Radar.setNotificationOptions has side effects on foregroundServiceOptions.
+ // Radar.setNotificationOptions has side effects on foregroundServiceOptions.
Radar.setNotificationOptions(RadarNotificationOptions(
"foo",
"red",
@@ -591,15 +592,15 @@ class RadarTest {
updatesOnly = true,
iconColor = "#FF0000"
))
-
- val options = RadarTrackingOptions.EFFICIENT
- options.desiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.LOW
val now = Date()
- options.startTrackingAfter = now
- options.stopTrackingAfter = Date(now.time + 1000)
- options.sync = RadarTrackingOptions.RadarTrackingOptionsSync.NONE
- options.syncGeofences = true
- options.syncGeofencesLimit = 100
+ val options = RadarTrackingOptions.EFFICIENT.copy(
+ desiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.LOW,
+ startTrackingAfter = now,
+ stopTrackingAfter = Date(now.time + 1000),
+ sync = RadarTrackingOptions.RadarTrackingOptionsSync.NONE,
+ syncGeofences = true,
+ syncGeofencesLimit = 100
+ )
Radar.startTracking(options)
assertEquals(options, Radar.getTrackingOptions())
assertTrue(Radar.isTracking())
@@ -701,12 +702,18 @@ class RadarTest {
@Test
fun test_Radar_startTrip() {
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ permissionsHelperMock.mockFineLocationPermissionGranted = true
val tripOptions = getTestTripOptions()
+ val latch = CountDownLatch(1)
Radar.startTrip(tripOptions) { status, trip, events ->
- assertEquals(tripOptions, Radar.getTripOptions())
- assertFalse(Radar.isTracking())
+ latch.countDown()
}
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(tripOptions, Radar.getTripOptions())
+ assertTrue(Radar.isTracking())
}
@Test
@@ -798,7 +805,6 @@ class RadarTest {
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
-
assertEquals(Radar.RadarStatus.ERROR_PERMISSIONS, callbackStatus)
}
@@ -1549,7 +1555,7 @@ class RadarTest {
@Test
fun test_Radar_setSdkConfiguration() {
- val sdkConfiguration = RadarSdkConfiguration(1, false, false, false, false, false, Radar.RadarLogLevel.WARNING, true, true, true)
+ val sdkConfiguration = RadarSdkConfiguration(1, false, false, false, false, false, Radar.RadarLogLevel.WARNING, true, true, true,true, true, null)
RadarSettings.setUserDebug(context, false)
RadarSettings.setSdkConfiguration(context, sdkConfiguration)
@@ -1572,7 +1578,7 @@ class RadarTest {
latch.countDown()
}
})
-
+
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
@@ -1586,5 +1592,184 @@ class RadarTest {
assertEquals(true, savedSdkConfiguration?.startTrackingOnInitialize)
assertEquals(true, savedSdkConfiguration?.trackOnceOnAppOpen)
assertEquals(true,savedSdkConfiguration?.useLocationMetadata)
+ assertEquals(true, savedSdkConfiguration.useOfflineRTOUpdates)
+ assertEquals(RadarTrackingOptions.EFFICIENT, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(savedSdkConfiguration.remoteTrackingOptions,"default"))
+ assertEquals(RadarTrackingOptions.RESPONSIVE, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(savedSdkConfiguration.remoteTrackingOptions,"inGeofence"))
+ assertEquals(RadarTrackingOptions.CONTINUOUS, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(savedSdkConfiguration.remoteTrackingOptions,"onTrip"))
+ assertEquals(arrayOf("venue")[0],
+ (RadarRemoteTrackingOptions.getGeofenceTagsWithKey(savedSdkConfiguration.remoteTrackingOptions,"inGeofence"))?.get(0) ?: ""
+ )
+ }
+
+ @Test
+ fun test_Radar_trackOnce_offlineRampUp() {
+ RadarSettings.setTripOptions(context, null)
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.mockResponse = RadarTestUtils.jsonObjectFromResource("/get_config_response.json")
+
+ Radar.apiClient.getConfig("sdkConfigUpdate", false, object : RadarApiClient.RadarGetConfigApiCallback {
+ override fun onComplete(status: Radar.RadarStatus, config: RadarConfig?) {
+
+ if (config != null) {
+ RadarSettings.setSdkConfiguration(context, config.meta.sdkConfiguration)
+ locationManager.updateTrackingFromMeta(config.meta)
+ }
+ assertEquals(RadarTrackingOptions.EFFICIENT, Radar.getTrackingOptions())
+ assertEquals(status, Radar.RadarStatus.SUCCESS)
+ permissionsHelperMock.mockFineLocationPermissionGranted = true
+ val mockLocation = Location("RadarSDK")
+ mockLocation.latitude = 40.783825
+ mockLocation.longitude = -73.975365
+ mockLocation.accuracy = 65f
+ mockLocation.time = System.currentTimeMillis()
+ locationClientMock.mockLocation = mockLocation
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.mockResponse = RadarTestUtils.jsonObjectFromResource("/track.json")
+ val latch = CountDownLatch(1)
+ var firstTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ Radar.trackOnce { status, _, _, _ ->
+ firstTrackOnceStatus = status
+ latch.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(firstTrackOnceStatus, Radar.RadarStatus.SUCCESS)
+ var secondTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.mockStatus = Radar.RadarStatus.ERROR_NETWORK
+ val latch2 = CountDownLatch(1)
+ Radar.trackOnce(mockLocation) { status, _, _, _ ->
+ secondTrackOnceStatus = status
+ latch2.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch2.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(secondTrackOnceStatus, Radar.RadarStatus.ERROR_NETWORK)
+ assertEquals(RadarTrackingOptions.RESPONSIVE,Radar.getTrackingOptions())
+ }
+ })
+ }
+ @Test
+ fun test_Radar_trackOnce_offlineRampDown_default() {
+ RadarSettings.setTripOptions(context, null)
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.mockResponse =
+ RadarTestUtils.jsonObjectFromResource("/get_config_response.json")
+
+ Radar.apiClient.getConfig(
+ "sdkConfigUpdate",
+ false,
+ object : RadarApiClient.RadarGetConfigApiCallback {
+ override fun onComplete(status: Radar.RadarStatus, config: RadarConfig?) {
+
+ if (config != null) {
+ RadarSettings.setSdkConfiguration(context, config.meta.sdkConfiguration)
+ locationManager.updateTrackingFromMeta(config.meta)
+ }
+ assertEquals(RadarTrackingOptions.EFFICIENT, Radar.getTrackingOptions())
+ assertEquals(status, Radar.RadarStatus.SUCCESS)
+ permissionsHelperMock.mockFineLocationPermissionGranted = true
+ val mockLocation = Location("RadarSDK")
+ mockLocation.latitude = 40.783825
+ mockLocation.longitude = -73.975365
+ mockLocation.accuracy = 65f
+ mockLocation.time = System.currentTimeMillis()
+ locationClientMock.mockLocation = mockLocation
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.addMockResponse("v1/track", RadarTestUtils.jsonObjectFromResource("/rampup.json")!!)
+ val latch = CountDownLatch(1)
+ var firstTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ Radar.trackOnce { status, _, _, _ ->
+ firstTrackOnceStatus = status
+ latch.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(firstTrackOnceStatus, Radar.RadarStatus.SUCCESS)
+ // need to fix this
+ assertEquals(
+ RadarTrackingOptions.RESPONSIVE,
+ Radar.getTrackingOptions()
+ )
+ apiHelperMock.mockStatus = Radar.RadarStatus.ERROR_NETWORK
+ mockLocation.latitude = 50.783825
+ mockLocation.longitude = -63.975365
+ val latch2 = CountDownLatch(1)
+ var secondTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ Radar.trackOnce(mockLocation) { status, _, _, _ ->
+ secondTrackOnceStatus = status
+ latch2.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch2.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(secondTrackOnceStatus, Radar.RadarStatus.ERROR_NETWORK)
+ assertEquals(
+ RadarTrackingOptions.EFFICIENT,
+ Radar.getTrackingOptions()
+ )
+ apiHelperMock.addMockResponse("v1/track", RadarTestUtils.jsonObjectFromResource("/track.json")!!)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun test_Radar_trackOnce_offlineRampDown_trips() {
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ val tripOptions = getTestTripOptions()
+ val latch = CountDownLatch(1)
+ Radar.startTrip(tripOptions) { _, _, _ ->
+ latch.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.mockResponse = RadarTestUtils.jsonObjectFromResource("/get_config_response.json")
+
+ Radar.apiClient.getConfig("sdkConfigUpdate", false, object : RadarApiClient.RadarGetConfigApiCallback {
+ override fun onComplete(status: Radar.RadarStatus, config: RadarConfig?) {
+
+ if (config != null) {
+ RadarSettings.setSdkConfiguration(context, config.meta.sdkConfiguration)
+ locationManager.updateTrackingFromMeta(config.meta)
+ }
+ assertEquals(RadarTrackingOptions.EFFICIENT, Radar.getTrackingOptions())
+ assertEquals(status, Radar.RadarStatus.SUCCESS)
+ permissionsHelperMock.mockFineLocationPermissionGranted = true
+ val mockLocation = Location("RadarSDK")
+ mockLocation.latitude = 40.783825
+ mockLocation.longitude = -73.975365
+ mockLocation.accuracy = 65f
+ mockLocation.time = System.currentTimeMillis()
+ locationClientMock.mockLocation = mockLocation
+ apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
+ apiHelperMock.addMockResponse("v1/track", RadarTestUtils.jsonObjectFromResource("/rampup.json")!!)
+ val latch2 = CountDownLatch(1)
+ var firstTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ Radar.trackOnce { status, _, _, _ ->
+ firstTrackOnceStatus = status
+ latch2.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch2.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(firstTrackOnceStatus, Radar.RadarStatus.SUCCESS)
+ assertEquals(RadarTrackingOptions.RESPONSIVE,Radar.getTrackingOptions())
+ mockLocation.latitude = 50.783825
+ mockLocation.longitude = -63.975365
+ apiHelperMock.mockStatus = Radar.RadarStatus.ERROR_NETWORK
+ var secondTrackOnceStatus = Radar.RadarStatus.SUCCESS
+ val latch3 = CountDownLatch(1)
+ Radar.trackOnce(mockLocation) { status, _, _, _ ->
+ secondTrackOnceStatus = status
+ latch3.countDown()
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
+ latch3.await(LATCH_TIMEOUT, TimeUnit.SECONDS)
+ assertEquals(secondTrackOnceStatus, Radar.RadarStatus.ERROR_NETWORK)
+ assertEquals(RadarTrackingOptions.CONTINUOUS,Radar.getTrackingOptions())
+ // clean up
+ RadarSettings.setTripOptions(context, null)
+ apiHelperMock.addMockResponse("v1/track", RadarTestUtils.jsonObjectFromResource("/track.json")!!)
+ }
+ })
}
}
diff --git a/sdk/src/test/java/io/radar/sdk/model/RadarMetaTest.kt b/sdk/src/test/java/io/radar/sdk/model/RadarMetaTest.kt
index 85bc7a56..cf4c6766 100644
--- a/sdk/src/test/java/io/radar/sdk/model/RadarMetaTest.kt
+++ b/sdk/src/test/java/io/radar/sdk/model/RadarMetaTest.kt
@@ -25,24 +25,24 @@ class RadarMetaTest {
val obj = config.meta
assertNotNull(obj.remoteTrackingOptions)
assertEquals(trackingOptions.desiredStoppedUpdateInterval, obj.remoteTrackingOptions!!.desiredStoppedUpdateInterval)
- assertEquals(trackingOptions.fastestStoppedUpdateInterval, obj.remoteTrackingOptions.fastestStoppedUpdateInterval)
- assertEquals(trackingOptions.desiredMovingUpdateInterval, obj.remoteTrackingOptions.desiredMovingUpdateInterval)
- assertEquals(trackingOptions.fastestMovingUpdateInterval, obj.remoteTrackingOptions.fastestMovingUpdateInterval)
- assertEquals(trackingOptions.desiredSyncInterval, obj.remoteTrackingOptions.desiredSyncInterval)
- assertEquals(trackingOptions.desiredAccuracy, obj.remoteTrackingOptions.desiredAccuracy)
- assertEquals(trackingOptions.stopDuration, obj.remoteTrackingOptions.stopDuration)
- assertEquals(trackingOptions.stopDistance, obj.remoteTrackingOptions.stopDistance)
- assertEquals(trackingOptions.startTrackingAfter, obj.remoteTrackingOptions.startTrackingAfter)
- assertEquals(trackingOptions.stopTrackingAfter, obj.remoteTrackingOptions.stopTrackingAfter)
- assertEquals(trackingOptions.replay, obj.remoteTrackingOptions.replay)
- assertEquals(trackingOptions.sync, obj.remoteTrackingOptions.sync)
- assertEquals(trackingOptions.useStoppedGeofence, obj.remoteTrackingOptions.useStoppedGeofence)
- assertEquals(trackingOptions.stoppedGeofenceRadius, obj.remoteTrackingOptions.stoppedGeofenceRadius)
- assertEquals(trackingOptions.useMovingGeofence, obj.remoteTrackingOptions.useMovingGeofence)
- assertEquals(trackingOptions.movingGeofenceRadius, obj.remoteTrackingOptions.movingGeofenceRadius)
- assertEquals(trackingOptions.syncGeofences, obj.remoteTrackingOptions.syncGeofences)
- assertEquals(trackingOptions.syncGeofencesLimit, obj.remoteTrackingOptions.syncGeofencesLimit)
- assertEquals(trackingOptions.foregroundServiceEnabled, obj.remoteTrackingOptions.foregroundServiceEnabled)
- assertEquals(trackingOptions.beacons, obj.remoteTrackingOptions.beacons)
+ assertEquals(trackingOptions.fastestStoppedUpdateInterval, obj.remoteTrackingOptions!!.fastestStoppedUpdateInterval)
+ assertEquals(trackingOptions.desiredMovingUpdateInterval, obj.remoteTrackingOptions!!.desiredMovingUpdateInterval)
+ assertEquals(trackingOptions.fastestMovingUpdateInterval, obj.remoteTrackingOptions!!.fastestMovingUpdateInterval)
+ assertEquals(trackingOptions.desiredSyncInterval, obj.remoteTrackingOptions!!.desiredSyncInterval)
+ assertEquals(trackingOptions.desiredAccuracy, obj.remoteTrackingOptions!!.desiredAccuracy)
+ assertEquals(trackingOptions.stopDuration, obj.remoteTrackingOptions!!.stopDuration)
+ assertEquals(trackingOptions.stopDistance, obj.remoteTrackingOptions!!.stopDistance)
+ assertEquals(trackingOptions.startTrackingAfter, obj.remoteTrackingOptions!!.startTrackingAfter)
+ assertEquals(trackingOptions.stopTrackingAfter, obj.remoteTrackingOptions!!.stopTrackingAfter)
+ assertEquals(trackingOptions.replay, obj.remoteTrackingOptions!!.replay)
+ assertEquals(trackingOptions.sync, obj.remoteTrackingOptions!!.sync)
+ assertEquals(trackingOptions.useStoppedGeofence, obj.remoteTrackingOptions!!.useStoppedGeofence)
+ assertEquals(trackingOptions.stoppedGeofenceRadius, obj.remoteTrackingOptions!!.stoppedGeofenceRadius)
+ assertEquals(trackingOptions.useMovingGeofence, obj.remoteTrackingOptions!!.useMovingGeofence)
+ assertEquals(trackingOptions.movingGeofenceRadius, obj.remoteTrackingOptions!!.movingGeofenceRadius)
+ assertEquals(trackingOptions.syncGeofences, obj.remoteTrackingOptions!!.syncGeofences)
+ assertEquals(trackingOptions.syncGeofencesLimit, obj.remoteTrackingOptions!!.syncGeofencesLimit)
+ assertEquals(trackingOptions.foregroundServiceEnabled, obj.remoteTrackingOptions!!.foregroundServiceEnabled)
+ assertEquals(trackingOptions.beacons, obj.remoteTrackingOptions!!.beacons)
}
}
\ No newline at end of file
diff --git a/sdk/src/test/java/io/radar/sdk/model/RadarSdkConfigurationTest.kt b/sdk/src/test/java/io/radar/sdk/model/RadarSdkConfigurationTest.kt
index 8d6b076f..9b45f101 100644
--- a/sdk/src/test/java/io/radar/sdk/model/RadarSdkConfigurationTest.kt
+++ b/sdk/src/test/java/io/radar/sdk/model/RadarSdkConfigurationTest.kt
@@ -1,9 +1,12 @@
package io.radar.sdk.model
import io.radar.sdk.Radar
+import io.radar.sdk.RadarTrackingOptions
+import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -29,6 +32,12 @@ class RadarSdkConfigurationTest {
private var trackOnceOnAppOpen = false
private var useLocationMetadata = false
private var useOpenedAppConversion = false
+ private var useOfflineRTOUpdates = false
+ private var remoteTrackingOptions =
+ arrayOf(RadarRemoteTrackingOptions("default",RadarTrackingOptions.EFFICIENT,null),
+ RadarRemoteTrackingOptions("onTrip", RadarTrackingOptions.CONTINUOUS,null),
+ RadarRemoteTrackingOptions("inGeofence", RadarTrackingOptions.RESPONSIVE, arrayOf("venue"))
+ )
@Before
fun setUp() {
@@ -46,14 +55,87 @@ class RadarSdkConfigurationTest {
"startTrackingOnInitialize":$startTrackingOnInitialize,
"trackOnceOnAppOpen":$trackOnceOnAppOpen,
"useLocationMetadata":$useLocationMetadata,
- "useOpenedAppConversion":$useOpenedAppConversion
+ "useOpenedAppConversion":$useOpenedAppConversion,
+ "useOfflineRTOUpdates":$useOfflineRTOUpdates,
+ "remoteTrackingOptions": [
+ {
+ "type": "default",
+ "trackingOptions":{
+ "desiredStoppedUpdateInterval": 3600,
+ "fastestStoppedUpdateInterval": 1200,
+ "desiredMovingUpdateInterval": 1200,
+ "fastestMovingUpdateInterval": 360,
+ "desiredSyncInterval": 140,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": false,
+ "stoppedGeofenceRadius": 0,
+ "useMovingGeofence": false,
+ "movingGeofenceRadius": 0,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
+ }
+ },
+ {
+ "type": "onTrip",
+ "trackingOptions":{
+ "desiredStoppedUpdateInterval": 30,
+ "fastestStoppedUpdateInterval": 30,
+ "desiredMovingUpdateInterval": 30,
+ "fastestMovingUpdateInterval": 30,
+ "desiredSyncInterval": 20,
+ "desiredAccuracy": "high",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "replay": "none",
+ "sync": "all",
+ "useStoppedGeofence": false,
+ "stoppedGeofenceRadius": 0,
+ "useMovingGeofence": false,
+ "movingGeofenceRadius": 0,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 0,
+ "foregroundServiceEnabled": true,
+ "beacons": false
+ }
+ },
+ {
+ "type":"inGeofence",
+ "trackingOptions":{
+ "desiredStoppedUpdateInterval": 0,
+ "fastestStoppedUpdateInterval": 0,
+ "desiredMovingUpdateInterval": 150,
+ "fastestMovingUpdateInterval": 30,
+ "desiredSyncInterval": 20,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": true,
+ "stoppedGeofenceRadius": 100,
+ "useMovingGeofence": true,
+ "movingGeofenceRadius": 100,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
+ },
+ "geofenceTags":["venue"]
+ }
+ ]
}""".trimIndent()
}
@Test
fun testToJson() {
assertEquals(
- JSONObject(jsonString).toString(),
+ JSONObject(jsonString).toMap(),
RadarSdkConfiguration(
maxConcurrentJobs,
requiresNetwork,
@@ -65,11 +147,29 @@ class RadarSdkConfigurationTest {
startTrackingOnInitialize,
trackOnceOnAppOpen,
useLocationMetadata,
- useOpenedAppConversion
- ).toJson().toString()
+ useOpenedAppConversion,
+ useOfflineRTOUpdates,
+ remoteTrackingOptions
+ ).toJson().toMap()
)
}
+ fun JSONObject.toMap(): Map = keys().asSequence().associateWith { key ->
+ when (val value = this[key]) {
+ is JSONArray -> value.toList()
+ is JSONObject -> value.toMap()
+ else -> value
+ }
+ }
+
+ fun JSONArray.toList(): List = (0 until length()).map { index ->
+ when (val value = get(index)) {
+ is JSONArray -> value.toList()
+ is JSONObject -> value.toMap()
+ else -> value
+ }
+ }
+
@Test
fun testFromJson() {
val settings = RadarSdkConfiguration.fromJson(JSONObject(jsonString))
@@ -84,6 +184,14 @@ class RadarSdkConfigurationTest {
assertEquals(trackOnceOnAppOpen, settings.trackOnceOnAppOpen)
assertEquals(useLocationMetadata, settings.useLocationMetadata)
assertEquals(useOpenedAppConversion, settings.useOpenedAppConversion)
+ assertEquals(useOfflineRTOUpdates, settings.useOfflineRTOUpdates)
+ assertEquals(RadarTrackingOptions.EFFICIENT, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(settings.remoteTrackingOptions,"default"))
+ assertEquals(RadarTrackingOptions.RESPONSIVE, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(settings.remoteTrackingOptions,"inGeofence"))
+ assertEquals(RadarTrackingOptions.CONTINUOUS, RadarRemoteTrackingOptions.getRemoteTrackingOptionsWithKey(settings.remoteTrackingOptions,"onTrip"))
+ assertEquals(arrayOf("venue")[0],
+ RadarRemoteTrackingOptions.getGeofenceTagsWithKey(settings.remoteTrackingOptions,"inGeofence")
+ ?.get(0) ?: ""
+ )
}
@Test
@@ -100,6 +208,9 @@ class RadarSdkConfigurationTest {
assertFalse(settings.trackOnceOnAppOpen)
assertFalse(settings.useLocationMetadata)
assertTrue(settings.useOpenedAppConversion)
+ assertFalse(settings.useOfflineRTOUpdates)
+ assertNull(settings.remoteTrackingOptions)
+
}
private fun String.removeWhitespace(): String = replace("\\s".toRegex(), "")
diff --git a/sdk/src/test/resources/get_config_response.json b/sdk/src/test/resources/get_config_response.json
index 38eafa83..aadd5b6e 100644
--- a/sdk/src/test/resources/get_config_response.json
+++ b/sdk/src/test/resources/get_config_response.json
@@ -1,18 +1,119 @@
{
- "meta": {
- "code": 200,
- "featureSettings": {
- "useLocationMetadata": false,
- "useRadarKVStore": true,
- "useRadarModifiedBeacon": true,
- "radarLowPowerManagerDesiredAccuracy": 3000,
- "radarLowPowerManagerDistanceFilter": 3000
+ "meta": {
+ "code": 200,
+ "featureSettings": {
+ "useLocationMetadata": false,
+ "useRadarKVStore": true,
+ "useRadarModifiedBeacon": true,
+ "radarLowPowerManagerDesiredAccuracy": 3000,
+ "radarLowPowerManagerDistanceFilter": 3000
+ },
+ "sdkConfiguration": {
+ "logLevel": "info",
+ "startTrackingOnInitialize": true,
+ "trackOnceOnAppOpen": true,
+ "useLocationMetadata": true,
+ "useOfflineRTOUpdates": true,
+ "remoteTrackingOptions": [
+ {
+ "type": "inGeofence",
+ "trackingOptions": {
+ "desiredStoppedUpdateInterval": 0,
+ "fastestStoppedUpdateInterval": 0,
+ "desiredMovingUpdateInterval": 150,
+ "fastestMovingUpdateInterval": 30,
+ "desiredSyncInterval": 20,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "startTrackingAfter": null,
+ "stopTrackingAfter": null,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": true,
+ "stoppedGeofenceRadius": 100,
+ "useMovingGeofence": true,
+ "movingGeofenceRadius": 100,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
+ },
+ "geofenceTags": ["venue"]
},
- "sdkConfiguration": {
- "logLevel": "info",
- "startTrackingOnInitialize": true,
- "trackOnceOnAppOpen": true,
- "useLocationMetadata": true
+ {
+ "type": "onTrip",
+ "trackingOptions": {
+ "desiredStoppedUpdateInterval": 30,
+ "fastestStoppedUpdateInterval": 30,
+ "desiredMovingUpdateInterval": 30,
+ "fastestMovingUpdateInterval": 30,
+ "desiredSyncInterval": 20,
+ "desiredAccuracy": "high",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "startTrackingAfter": null,
+ "stopTrackingAfter": null,
+ "replay": "none",
+ "sync": "all",
+ "useStoppedGeofence": false,
+ "stoppedGeofenceRadius": 0,
+ "useMovingGeofence": false,
+ "movingGeofenceRadius": 0,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 0,
+ "foregroundServiceEnabled": true,
+ "beacons": false
+ }
+ },
+ {
+ "type": "default",
+ "trackingOptions": {
+ "desiredStoppedUpdateInterval": 3600,
+ "fastestStoppedUpdateInterval": 1200,
+ "desiredMovingUpdateInterval": 1200,
+ "fastestMovingUpdateInterval": 360,
+ "desiredSyncInterval": 140,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "startTrackingAfter": null,
+ "stopTrackingAfter": null,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": false,
+ "stoppedGeofenceRadius": 0,
+ "useMovingGeofence": false,
+ "movingGeofenceRadius": 0,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
+ }
}
+ ]
+ },
+ "trackingOptions": {
+ "desiredStoppedUpdateInterval": 3600,
+ "fastestStoppedUpdateInterval": 1200,
+ "desiredMovingUpdateInterval": 1200,
+ "fastestMovingUpdateInterval": 360,
+ "desiredSyncInterval": 140,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "startTrackingAfter": null,
+ "stopTrackingAfter": null,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": false,
+ "stoppedGeofenceRadius": 0,
+ "useMovingGeofence": false,
+ "movingGeofenceRadius": 0,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
}
+ }
}
diff --git a/sdk/src/test/resources/rampup.json b/sdk/src/test/resources/rampup.json
new file mode 100644
index 00000000..26f07498
--- /dev/null
+++ b/sdk/src/test/resources/rampup.json
@@ -0,0 +1,1091 @@
+{
+ "meta": {
+ "code": 200,
+ "trackingOptions": {
+ "desiredStoppedUpdateInterval": 0,
+ "fastestStoppedUpdateInterval": 0,
+ "desiredMovingUpdateInterval": 150,
+ "fastestMovingUpdateInterval": 30,
+ "desiredSyncInterval": 20,
+ "desiredAccuracy": "medium",
+ "stopDuration": 140,
+ "stopDistance": 70,
+ "startTrackingAfter": null,
+ "stopTrackingAfter": null,
+ "replay": "stops",
+ "sync": "all",
+ "useStoppedGeofence": true,
+ "stoppedGeofenceRadius": 100,
+ "useMovingGeofence": true,
+ "movingGeofenceRadius": 100,
+ "syncGeofences": true,
+ "syncGeofencesLimit": 10,
+ "foregroundServiceEnabled": false,
+ "beacons": false
+ }
+ },
+ "user": {
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.9753651, 40.7838251]
+ },
+ "live": false,
+ "geofences": [
+ {
+ "geometryCenter": {
+ "coordinates": [-73.975365, 40.783825],
+ "type": "Point"
+ },
+ "_id": "5ca7dd72208530002b30683c",
+ "description": "S3 Test Monk's Café",
+ "type": "circle",
+ "metadata": {
+ "category": "restaurant"
+ },
+ "geometryRadius": 50,
+ "tag": "venue",
+ "externalId": "2"
+ }
+ ],
+ "segments": [
+ {
+ "description": "Starbucks Visitors",
+ "externalId": "starbucks-visitors"
+ }
+ ],
+ "topChains": [
+ {
+ "domain": "starbucks.com",
+ "name": "Starbucks",
+ "slug": "starbucks",
+ "externalId": "811",
+ "metadata": {
+ "category": "Coffee & Tea",
+ "loyalty": false
+ }
+ },
+ {
+ "domain": "walgreens.com",
+ "name": "Walgreens",
+ "slug": "walgreens",
+ "externalId": "5",
+ "metadata": {
+ "category": "Pharmacy",
+ "loyalty": false
+ }
+ }
+ ],
+ "metadata": {
+ "customId": "123",
+ "customFlag": false
+ },
+ "description": "User 1",
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "ip": "68.129.209.28",
+ "locationAccuracy": 5,
+ "stopped": true,
+ "foreground": false,
+ "deviceId": "A",
+ "userId": "1",
+ "actualUpdatedAt": "2019-11-15T17:31:18.454Z",
+ "updatedAt": "2019-11-15T17:31:18.454Z",
+ "createdAt": "2018-10-06T15:30:03.713Z",
+ "place": {
+ "location": {
+ "coordinates": [-73.97541, 40.78377],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "real-estate",
+ "residence-other",
+ "apartment-condo-building"
+ ],
+ "_id": "59bb1dc10d8998dc02510033",
+ "name": "129 West 81st Street"
+ },
+ "deviceType": "iOS",
+ "nearbyPlaceChains": [
+ {
+ "domain": "starbucks.com",
+ "name": "Starbucks",
+ "slug": "starbucks",
+ "externalId": "811",
+ "metadata": {
+ "category": "Coffee & Tea",
+ "loyalty": false,
+ "pwi": false,
+ "tlog": false
+ }
+ },
+ {
+ "domain": "walgreens.com",
+ "name": "Walgreens",
+ "slug": "walgreens",
+ "externalId": "5",
+ "metadata": {
+ "category": "Pharmacy",
+ "loyalty": false,
+ "pwi": false,
+ "tlog": false
+ }
+ }
+ ],
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "source": "BACKGROUND_LOCATION",
+ "fraud": {
+ "passed": true,
+ "bypassed": true,
+ "proxy": true,
+ "mocked": true,
+ "compromised": true,
+ "jumped": true
+ },
+ "trip": {
+ "_id": "5f3e50491c2b7d005c81f5d9",
+ "live": true,
+ "externalId": "299",
+ "metadata": {
+ "Customer Name": "Jacob Pena",
+ "Car Model": "Green Honda Civic"
+ },
+ "mode": "car",
+ "destinationGeofenceTag": "store",
+ "destinationGeofenceExternalId": "123",
+ "destinationLocation": {
+ "coordinates": [-105.061198, 39.779366],
+ "type": "Point"
+ },
+ "eta": {
+ "duration": 5.5,
+ "distance": 1331
+ },
+ "status": "started"
+ }
+ },
+ "events": [
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.exited_region_state",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "region": {
+ "_id": "5cf695086da6a800683f4e69",
+ "type": "state",
+ "name": "Maryland",
+ "code": "MD"
+ },
+ "_id": "5dcee0e67e71e80027a7eec3",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.entered_region_state",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "region": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "_id": "5dcee0e67e71e80027a7eec4",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.exited_region_dma",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "region": {
+ "_id": "5cf694fb6da6a800683f4d92",
+ "type": "dma",
+ "name": "Baltimore",
+ "code": "512"
+ },
+ "_id": "5dcee0e67e71e80027a7eec5",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.entered_region_dma",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "region": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "_id": "5dcee0e67e71e80027a7eec6",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.exited_geofence",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "duration": 84491.305,
+ "geofence": {
+ "geometryCenter": {
+ "coordinates": [-76.782486, 39.165325],
+ "type": "Point"
+ },
+ "_id": "5d818607dc9243002682d6da",
+ "description": "TA Jessup",
+ "type": "circle",
+ "geometryRadius": 100,
+ "tag": "ta-petro",
+ "externalId": "123",
+ "metadata": {
+ "foo": "bar"
+ }
+ },
+ "_id": "5dcee0e67e71e80027a7eec7",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.entered_geofence",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "geofence": {
+ "geometryCenter": {
+ "coordinates": [-73.975365, 40.783825],
+ "type": "Point"
+ },
+ "_id": "5ca7dd72208530002b30683c",
+ "description": "S3 Test Monk's Café",
+ "type": "circle",
+ "metadata": {
+ "category": "restaurant"
+ },
+ "geometryRadius": 50,
+ "tag": "venue",
+ "externalId": "2"
+ },
+ "_id": "5dcee0e67e71e80027a7eec8",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.entered_place",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 1,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "place": {
+ "location": {
+ "coordinates": [-73.97541, 40.78377],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "real-estate",
+ "residence-other",
+ "apartment-condo-building"
+ ],
+ "_id": "59bb1dc10d8998dc02510033",
+ "name": "129 West 81st Street"
+ },
+ "alternatePlaces": [
+ {
+ "location": {
+ "coordinates": [-73.97541, 40.78377],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "outdoor-places",
+ "landmark",
+ "public-services-government"
+ ],
+ "_id": "59e289a9a064b45aefe78b82",
+ "name": "Things in Jerry Seinfeld's Apartment"
+ },
+ {
+ "location": {
+ "coordinates": [-73.97536, 40.78387],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "local-services",
+ "business-services",
+ "commercial-industrial",
+ "commercial-industrial-equipment"
+ ],
+ "_id": "59e289a9a064b45aefe78b85",
+ "name": "Amazon",
+ "chain": {
+ "domain": "amazon.com",
+ "name": "Amazon",
+ "slug": "amazon"
+ }
+ },
+ {
+ "location": {
+ "coordinates": [-73.97501, 40.78358],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "real-estate",
+ "real-estate-service",
+ "shopping-retail",
+ "mobile-phone-shop"
+ ],
+ "_id": "59bc68ec8be4c5ce94087563",
+ "name": "Endicott"
+ },
+ {
+ "location": {
+ "coordinates": [-73.97512, 40.78341],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": [
+ "travel-transportation",
+ "travel-company",
+ "travel-agency"
+ ],
+ "_id": "59da3785a064b45aefe1e2ef",
+ "name": "Cook Travel"
+ }
+ ],
+ "_id": "5dcee0e67e71e80027a7eec9",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.exited_place",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 2,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "duration": 84491.305,
+ "place": {
+ "location": {
+ "coordinates": [-76.782486, 39.165436],
+ "type": "Point"
+ },
+ "categories": ["food-beverage", "restaurant"],
+ "_id": "5adc6759a4e7ae6c68b63307",
+ "name": "TravelCenters of America",
+ "chain": {
+ "domain": "ta-petro.com",
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america",
+ "externalId": "TA",
+ "metadata": {
+ "category": "gas"
+ }
+ }
+ },
+ "_id": "5dcee0e67e71e80027a7eeca",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.nearby_place_chain",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 2,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "place": {
+ "location": {
+ "coordinates": [-73.97869, 40.783066],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": ["medical-health", "pharmacy"],
+ "_id": "5dc9b3422004860034bfc412",
+ "name": "Walgreens",
+ "chain": {
+ "domain": "walgreens.com",
+ "name": "Walgreens",
+ "slug": "walgreens",
+ "externalId": "5",
+ "metadata": {
+ "category": "Pharmacy",
+ "loyalty": false,
+ "pwi": false,
+ "tlog": false
+ }
+ }
+ },
+ "_id": "5dcee0e67e71e80027a7eecb",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ },
+ {
+ "createdAt": "2019-11-15T17:31:18.454Z",
+ "live": false,
+ "type": "user.nearby_place_chain",
+ "location": {
+ "type": "Point",
+ "coordinates": [-73.975365, 40.783825]
+ },
+ "locationAccuracy": 5,
+ "confidence": 3,
+ "actualCreatedAt": "2019-11-15T17:31:18.454Z",
+ "user": {
+ "segments": [],
+ "topChains": [
+ {
+ "name": "TravelCenters of America",
+ "slug": "travelcenters-of-america"
+ }
+ ],
+ "_id": "5bb8d4fbfd58d5002103ff5c",
+ "deviceId": "A",
+ "userId": "1",
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ }
+ },
+ "place": {
+ "location": {
+ "coordinates": [-73.97453, 40.78356],
+ "type": "Point"
+ },
+ "osmGeometry": {
+ "coordinates": []
+ },
+ "categories": ["food-beverage", "cafe", "coffee-shop"],
+ "_id": "59bc68ec8be4c5ce9408755f",
+ "name": "Starbucks",
+ "chain": {
+ "domain": "starbucks.com",
+ "name": "Starbucks",
+ "slug": "starbucks",
+ "externalId": "811",
+ "metadata": {
+ "category": "Coffee & Tea",
+ "loyalty": false,
+ "pwi": false,
+ "tlog": false
+ }
+ }
+ },
+ "_id": "5dcee0e67e71e80027a7eecc",
+ "country": {
+ "_id": "5cf694f66da6a800683f4d71",
+ "type": "country",
+ "name": "United States",
+ "code": "US"
+ },
+ "state": {
+ "_id": "5cf695096da6a800683f4e7f",
+ "type": "state",
+ "name": "New York",
+ "code": "NY"
+ },
+ "postalCode": {
+ "_id": "5cf695266da6a800683f5817",
+ "type": "postalCode",
+ "name": "10024",
+ "code": "10024"
+ },
+ "dma": {
+ "_id": "5cf695016da6a800683f4e06",
+ "type": "dma",
+ "name": "New York",
+ "code": "501"
+ }
+ }
+ ],
+ "nearbyGeofences": [
+ {
+ "geometryCenter": {
+ "coordinates": [-73.975365, 40.783825],
+ "type": "Point"
+ },
+ "_id": "5ca7dd72208530002b30683c",
+ "description": "S3 Test Monk's Café",
+ "type": "circle",
+ "metadata": {
+ "category": "restaurant"
+ },
+ "geometryRadius": 50,
+ "tag": "venue",
+ "externalId": "2"
+ }
+ ]
+}
diff --git a/sdk/src/test/resources/track.json b/sdk/src/test/resources/track.json
index b3aae16d..adab968b 100644
--- a/sdk/src/test/resources/track.json
+++ b/sdk/src/test/resources/track.json
@@ -1130,5 +1130,25 @@
"code": "501"
}
}
+ ],
+ "nearbyGeofences": [
+ {
+ "geometryCenter": {
+ "coordinates": [
+ -73.975365,
+ 40.783825
+ ],
+ "type": "Point"
+ },
+ "_id": "5ca7dd72208530002b30683c",
+ "description": "S3 Test Monk's Café",
+ "type": "circle",
+ "metadata": {
+ "category": "restaurant"
+ },
+ "geometryRadius": 50,
+ "tag": "venue",
+ "externalId": "2"
+ }
]
}