Skip to content

Commit

Permalink
Merge pull request #23 from XYOracleNetwork/feature/android-location-…
Browse files Browse the repository at this point in the history
…payload-raw

raw location payload
  • Loading branch information
jonesmac authored Nov 20, 2024
2 parents 0c96281 + 4dfe8ef commit 1a2edd8
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class XyoPanelTest {
val panel = XyoPanel(appContext, Account.random(), arrayListOf(Pair(nodeUrl, Account.random())), listOf(witness, XyoSystemInfoWitness(witness2Account), XyoLocationWitness()))
val result = panel.reportAsyncQuery()
if (result.apiResults === null) throw NullPointerException("apiResults should not be null")
assert(result.payloads?.size == 3)
assert(result.payloads?.size == 4)
result.apiResults?.forEach {
assertEquals(it.errors, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.xyo.client.witness.location.info.LocationActivity
import network.xyo.client.witness.location.info.XyoLocationPayload
import network.xyo.client.witness.location.info.XyoLocationPayloadRaw
import network.xyo.client.witness.location.info.XyoLocationWitness
import org.junit.Rule
import org.junit.Test
Expand All @@ -31,13 +32,19 @@ class LocationWitnessTest {

CoroutineScope(Dispatchers.Main).launch {
val witness = XyoLocationWitness()
val payload = witness.observe(context)?.first()
val locationPayload = witness.observe(context)?.first()

assertInstanceOf<XyoLocationPayload>(payload)
assert(payload.schema == "network.xyo.location.android")
assert((payload.currentLocation) !== null)
assert(payload.currentLocation?.coords?.latitude !== null)
assert(payload.currentLocation?.coords?.longitude !== null)
assertInstanceOf<XyoLocationPayload>(locationPayload)
assert(locationPayload.schema == "network.xyo.location.android")
assert(locationPayload.currentLocation !== null)
assert(locationPayload.currentLocation?.coords?.latitude !== null)
assert(locationPayload.currentLocation?.coords?.longitude !== null)

val locationRawPayload = witness.observe(context)?.get(1)
assertInstanceOf<XyoLocationPayloadRaw>(locationRawPayload)
assert(locationRawPayload.schema == "network.xyo.location.android.raw")

assert(locationPayload._sources?.first() == locationRawPayload.hash())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRe
import network.xyo.client.witness.types.WitnessResult
import network.xyo.client.payload.XyoPayload
import network.xyo.client.witness.location.info.LocationActivity
import network.xyo.client.witness.location.info.XyoLocationPayload
import network.xyo.client.witness.location.info.XyoLocationPayloadRaw
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -49,11 +51,11 @@ class WitnessLocationHandlerTest {
var firstBw: XyoBoundWitnessBodyJson? = null
val result1 = WitnessLocationHandler().witness(appContext.applicationContext, arrayListOf(Pair(apiDomainBeta, null)))
when (result1) {
is WitnessResult.Success<Pair<XyoBoundWitnessBodyJson?, XyoPayload?>> -> {
is WitnessResult.Success<Triple<XyoBoundWitnessBodyJson?, XyoPayload?, XyoPayload?>> -> {
firstBw = result1.data.first
assertInstanceOf<XyoBoundWitnessBodyJson>(firstBw)
assert(result1.data.first !== null)
assert(result1.data.second !== null)
assertInstanceOf<XyoLocationPayload>(result1.data.second)
assertInstanceOf<XyoLocationPayloadRaw>(result1.data.third)
}
is WitnessResult.Error -> {
assert(result1.exception.size > 0)
Expand All @@ -63,11 +65,11 @@ class WitnessLocationHandlerTest {
var secondBw: XyoBoundWitnessBodyJson? = null
val result2 = WitnessLocationHandler().witness(appContext.applicationContext, arrayListOf(Pair(apiDomainBeta, null)))
when (result2) {
is WitnessResult.Success<Pair<XyoBoundWitnessBodyJson?, XyoPayload?>> -> {
is WitnessResult.Success<Triple<XyoBoundWitnessBodyJson?, XyoPayload?, XyoPayload?>> -> {
secondBw = result2.data.first
assertInstanceOf<XyoBoundWitnessBodyJson>(secondBw)
assert(result2.data.first !== null)
assert(result2.data.second !== null)
assertInstanceOf<XyoLocationPayload>(result2.data.second)
assertInstanceOf<XyoLocationPayloadRaw>(result2.data.third)

}
is WitnessResult.Error -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import network.xyo.client.boundwitness.XyoBoundWitnessBodyJson
import network.xyo.client.payload.XyoPayload
import network.xyo.client.settings.XyoSdk

open class WitnessLocationHandler : WitnessHandlerInterface<Pair<XyoBoundWitnessBodyJson?, XyoPayload?>> {
open class WitnessLocationHandler : WitnessHandlerInterface<Triple<XyoBoundWitnessBodyJson?, XyoPayload?, XyoPayload?>> {
@RequiresApi(Build.VERSION_CODES.M)
override suspend fun witness(context: Context, nodeUrlsAndAccounts: ArrayList<Pair<String, AccountInstance?>>): WitnessResult<Pair<XyoBoundWitnessBodyJson?, XyoPayload?>> {
override suspend fun witness(context: Context, nodeUrlsAndAccounts: ArrayList<Pair<String, AccountInstance?>>): WitnessResult<Triple<XyoBoundWitnessBodyJson?, XyoPayload?, XyoPayload?>> {
val account = XyoSdk.getInstance(context.applicationContext).getAccount()
val panel = XyoPanel(context, account, nodeUrlsAndAccounts, listOf(
XyoLocationWitness(account)
Expand All @@ -26,17 +26,19 @@ open class WitnessLocationHandler : WitnessHandlerInterface<Pair<XyoBoundWitness

@OptIn(ExperimentalCoroutinesApi::class)
@RequiresApi(Build.VERSION_CODES.M)
private suspend fun getLocation(panel: XyoPanel): WitnessResult<Pair<XyoBoundWitnessBodyJson?, XyoPayload?>> {
private suspend fun getLocation(panel: XyoPanel): WitnessResult<Triple<XyoBoundWitnessBodyJson?, XyoPayload?, XyoPayload?>> {
return withContext(Dispatchers.IO) {
var locationPayload: XyoPayload? = null
var locationPayloadRaw: XyoPayload? = null
var bw: XyoBoundWitnessBodyJson? = null
val errors: MutableList<Error> = mutableListOf()
panel.let {
it.reportAsyncQuery().let { result ->
val actualPayloads = result.payloads
actualPayloads?.forEach { payload ->
if (payload.schema === "network.xyo.location.android") {
locationPayload = payload
when (payload) {
is XyoLocationPayload -> locationPayload = payload
is XyoLocationPayloadRaw -> locationPayloadRaw = payload
}
}

Expand All @@ -50,7 +52,7 @@ open class WitnessLocationHandler : WitnessHandlerInterface<Pair<XyoBoundWitness
}
}
if (errors.size > 0) return@withContext WitnessResult.Error(errors)
return@withContext WitnessResult.Success(Pair(bw, locationPayload))
return@withContext WitnessResult.Success(Triple(bw, locationPayload, locationPayloadRaw))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package network.xyo.client.witness.location.info
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.google.android.gms.location.LocationServices
import com.squareup.moshi.JsonClass
import kotlinx.coroutines.suspendCancellableCoroutine
Expand All @@ -14,8 +16,9 @@ import kotlin.coroutines.resumeWithException
class XyoLocationCurrent {
companion object {

@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("MissingPermission")
suspend fun detect(context: Context): CurrentLocation? {
suspend fun detect(context: Context): Pair<XyoLocationPayload, XyoLocationPayloadRaw>? {
if (LocationPermissions.check((context)) && LocationPermissions.checkGooglePlayServices(context)) {
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
return suspendCancellableCoroutine { continuation ->
Expand All @@ -25,10 +28,14 @@ class XyoLocationCurrent {
continuation.resumeWith(Result.success(null))
return@addOnSuccessListener
}

val locationRaw = buildRawLocationPayload(location)
val _sources = listOf(locationRaw.hash())
val coordinates = setCoordinatesFromLocation(location)
val currentLocation = CurrentLocation(coordinates, System.currentTimeMillis())

// Resume the coroutine with the retrieved location
continuation.resumeWith(Result.success(currentLocation))
continuation.resumeWith(Result.success(Pair(XyoLocationPayload(currentLocation, _sources), locationRaw)))
}
.addOnFailureListener { exception ->
// Resume the coroutine with an exception if the task fails
Expand All @@ -52,8 +59,38 @@ class XyoLocationCurrent {
location.speed
)
val serialized = XyoSerializable.toJson(coordinates)
Log.i("xyoClient", "serialized Coordinates: ${serialized}")
Log.i("xyoClient", "serialized Coordinates: $serialized")
return coordinates
}

@RequiresApi(Build.VERSION_CODES.O)
private fun buildRawLocationPayload(location: Location): XyoLocationPayloadRaw {
return XyoLocationPayloadRaw.detect(
location.provider,
location.latitude,
location.longitude,
location.altitude,
location.accuracy,
location.bearing,
location.bearingAccuracyDegrees,
location.speed,
location.speedAccuracyMetersPerSecond,
location.verticalAccuracyMeters,
location.time,
handleIsMock(location),
location.extras
)
}

private fun handleIsMock(location: Location): Boolean? {
// Conditionally figure out if using mocked location
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
location.isMock
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
location.isFromMockProvider
} else {
null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package network.xyo.client.witness.location.info

import android.annotation.SuppressLint
import android.content.Context
import com.squareup.moshi.JsonClass
import network.xyo.client.payload.Payload
import network.xyo.client.payload.XyoPayload

interface XyoLocationPayloadMetaInterface : Payload {
var _sources: List<String>?
}

@JsonClass(generateAdapter = true)
data class Coordinates(
val accuracy: Float?,
Expand All @@ -22,24 +25,22 @@ data class CurrentLocation(
val timestamp: Long
)



@JsonClass(generateAdapter = true)
class XyoLocationPayload (
val currentLocation: CurrentLocation? = null
): XyoPayload() {
class XyoLocationPayload(
val currentLocation: CurrentLocation? = null,
override var _sources: List<String>?
): XyoPayload(), XyoLocationPayloadMetaInterface {
override var schema: String
get() = "network.xyo.location.android"
set(value) = Unit

override fun hash(): String {
return sha256String(this)
}

companion object {

@SuppressLint("MissingPermission")
suspend fun detect(context: Context): XyoLocationPayload {
return XyoLocationPayload(
XyoLocationCurrent.detect(context)
)
fun detect(currentLocation: CurrentLocation?, _sources: List<String>?): XyoLocationPayload {
return XyoLocationPayload(currentLocation, _sources)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package network.xyo.client.witness.location.info

import android.os.Bundle
import com.squareup.moshi.JsonClass
import network.xyo.client.payload.XyoPayload

// Extension function for safely retrieving typed values from a Bundle
private fun Bundle.getTypedValue(key: String): Any? {
return when {
containsKey(key) -> {
when {
getString(key) != null -> getString(key)
getInt(key, Int.MIN_VALUE) != Int.MIN_VALUE -> getInt(key)
getLong(key, Long.MIN_VALUE) != Long.MIN_VALUE -> getLong(key)
getFloat(key, Float.MIN_VALUE) != Float.MIN_VALUE -> getFloat(key)
getDouble(key, Double.MIN_VALUE) != Double.MIN_VALUE -> getDouble(key)
getBoolean(key) -> getBoolean(key)
else -> null
}
}
else -> null
}
}

@JsonClass(generateAdapter = true)
open class XyoLocationPayloadRaw(
val provider: String?,
val latitude: Double,
val longitude: Double,
val altitude: Double,
val accuracy: Float?,
val bearing: Float?,
val bearingAccuracyDegrees: Float?,
val speed: Float?,
val speedAccuracyMetersPerSecond: Float?,
val verticalAccuracyMeters: Float?,
val time: Long,
val isMock: Boolean?,
val extras: Map<String, Any?>? = null
): XyoPayload() {
override var schema: String
get() = "network.xyo.location.android.raw"
set(value) = Unit

override fun hash(): String {
return sha256String(this)
}


companion object {
fun detect(
provider: String?,
latitude: Double,
longitude: Double,
altitude: Double,
accuracy: Float?,
bearing: Float?,
bearingAccuracyDegrees: Float?,
speed: Float?,
speedAccuracyMetersPerSecond: Float?,
verticalAccuracyMeters: Float?,
time: Long,
isMock: Boolean?,
extras: Bundle? = null
): XyoLocationPayloadRaw {
val extrasMap = extras?.keySet()?.associateWith { key ->
extras.getTypedValue(key)
}

return XyoLocationPayloadRaw(
provider,
latitude,
longitude,
altitude,
accuracy,
bearing,
bearingAccuracyDegrees,
speed,
speedAccuracyMetersPerSecond,
verticalAccuracyMeters,
time,
isMock,
extrasMap
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package network.xyo.client.witness.location.info

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi

class XyoLocationPayloads {
companion object {

@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("MissingPermission")
suspend fun detect(context: Context): Pair<XyoLocationPayload, XyoLocationPayloadRaw>? {
return XyoLocationCurrent.detect(context)
}
}
}
Loading

0 comments on commit 1a2edd8

Please sign in to comment.