Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kenny/offline ramp up #410

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions example/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application
android:allowBackup="true"
android:fullBackupOnly="true"
Expand Down
10 changes: 8 additions & 2 deletions sdk/src/main/java/io/radar/sdk/Radar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -868,9 +868,12 @@ object Radar {
config: RadarConfig?,
token: RadarVerifiedLocationToken?
) {
if (status == RadarStatus.SUCCESS ){
if (config != null) {
locationManager.updateTrackingFromMeta(config?.meta)
}
if (status == RadarStatus.SUCCESS) {
locationManager.replaceSyncedGeofences(nearbyGeofences)
}
handler.post {
callback?.onComplete(status, location, events, user)
}
Expand Down Expand Up @@ -965,9 +968,12 @@ object Radar {
config: RadarConfig?,
token: RadarVerifiedLocationToken?
) {
if (status == RadarStatus.SUCCESS ){
if (config != null) {
locationManager.updateTrackingFromMeta(config?.meta)
}
if (status == RadarStatus.SUCCESS) {
locationManager.replaceSyncedGeofences(nearbyGeofences)
}
handler.post {
callback?.onComplete(status, location, events, user)
}
Expand Down
33 changes: 29 additions & 4 deletions sdk/src/main/java/io/radar/sdk/RadarApiClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,26 @@ internal class RadarApiClient(
// before we track, check if replays need to sync
val replaying = options.replay == RadarTrackingOptions.RadarTrackingOptionsReplay.ALL && hasReplays && !verified
if (replaying) {
// creating alias to location to avoid name space conflict
val trackLocation = location
Radar.flushReplays(
replayParams = params,
callback = object : Radar.RadarTrackCallback {
override fun onComplete(status: RadarStatus, location: Location?, events: Array<RadarEvent>?, 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)
}
}
}
)
Expand All @@ -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
}

Expand Down
7 changes: 5 additions & 2 deletions sdk/src/main/java/io/radar/sdk/RadarLocationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ internal class RadarLocationManager(
}
}

private fun replaceSyncedGeofences(radarGeofences: Array<RadarGeofence>?) {
internal fun replaceSyncedGeofences(radarGeofences: Array<RadarGeofence>?) {
RadarState.setNearbyGeofences(context, radarGeofences)
this.removeSyncedGeofences() { success ->
this.addSyncedGeofences(radarGeofences)
}
Expand Down Expand Up @@ -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()
Expand Down
91 changes: 91 additions & 0 deletions sdk/src/main/java/io/radar/sdk/RadarOfflineManager.kt
Original file line number Diff line number Diff line change
@@ -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<String>()
var newGeofenceTags = mutableSetOf<String>()
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
}
}
14 changes: 14 additions & 0 deletions sdk/src/main/java/io/radar/sdk/RadarState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -277,4 +280,15 @@ internal object RadarState {
}
}

internal fun getNearbyGeofences(context: Context): Array<RadarGeofence>? {
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<RadarGeofence>?) {
val jsonString = RadarGeofence.toJson(nearbyGeofences).toString()
getSharedPreferences(context).edit { putString(KEY_NEARBY_GEOFENCES, jsonString) }
}

}
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ internal class RadarVerificationManager(
config: RadarConfig?,
token: RadarVerifiedLocationToken?
) {
if (status == Radar.RadarStatus.SUCCESS) {
if (config != null) {
Radar.locationManager.updateTrackingFromMeta(
config?.meta
)
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/radar/sdk/model/RadarConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/main/java/io/radar/sdk/model/RadarGeofence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/radar/sdk/model/RadarMeta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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?,
) {
Expand Down
94 changes: 94 additions & 0 deletions sdk/src/main/java/io/radar/sdk/model/RadarRemoteTrackingOptions.kt
Original file line number Diff line number Diff line change
@@ -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<String>?
) {
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<RadarRemoteTrackingOptions>? {
if (arr == null) {
return null
}
return Array(arr.length()) { index ->
fromJson(arr.optJSONObject(index))
}.filterNotNull().toTypedArray()
}

@JvmStatic
fun toJson(remoteTrackingOptions: Array<RadarRemoteTrackingOptions>?): JSONArray? {
if (remoteTrackingOptions == null) {
return null
}
val arr = JSONArray()
remoteTrackingOptions.forEach { remoteTrackingOption ->
arr.put(remoteTrackingOption.toJson())
}
return arr
}

@JvmStatic
fun getRemoteTrackingOptionsWithKey(remoteTrackingOptions: Array<RadarRemoteTrackingOptions>?, 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<RadarRemoteTrackingOptions>?, key: String): Array<String>? {
if (remoteTrackingOptions == null) {
return null
}
var geofenceTags: Array<String>? = 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
}

}
Loading