[go: nahoru, domu]

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

Integrate FLP as a Location source #220

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions health-services/ExerciseSampleCompose/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ dependencies {
implementation libs.guava
implementation libs.androidx.concurrent

//Wear OS Compose Navigation
// Wear OS Compose Navigation
implementation libs.compose.wear.navigation
implementation libs.androidx.compose.navigation
implementation libs.horologist.compose.layout
implementation libs.horologist.compose.material
implementation libs.horologist.health.composables
implementation libs.horologist.health.service

//Wear Health Services
// Wear Health Services
implementation libs.androidx.health.services

// Lifecycle components
Expand All @@ -111,6 +111,9 @@ dependencies {
// Ongoing Activity
implementation libs.wear.ongoing.activity

// Fused Location Provider
implementation libs.play.services.location

// Hilt
implementation libs.hilt.navigation.compose
implementation libs.dagger.hilt.android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<uses-feature android:name="android.hardware.type.watch" />

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

<queries>
<package android:name="com.google.android.wearable.healthservices" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,33 @@
*/
package com.example.exercisesamplecompose.data

import android.Manifest
import android.annotation.SuppressLint
import android.location.Location
import android.os.Looper
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.health.services.client.ExerciseClient
import androidx.health.services.client.ExerciseUpdateCallback
import androidx.health.services.client.HealthServicesClient
import androidx.health.services.client.data.Availability
import androidx.health.services.client.data.ComparisonType
import androidx.health.services.client.data.DataPointContainer
import androidx.health.services.client.data.DataType
import androidx.health.services.client.data.DataTypeCondition
import androidx.health.services.client.data.ExerciseConfig
import androidx.health.services.client.data.ExerciseEndReason
import androidx.health.services.client.data.ExerciseGoal
import androidx.health.services.client.data.ExerciseLapSummary
import androidx.health.services.client.data.ExerciseState
import androidx.health.services.client.data.ExerciseStateInfo
import androidx.health.services.client.data.ExerciseType
import androidx.health.services.client.data.ExerciseTypeCapabilities
import androidx.health.services.client.data.ExerciseUpdate
import androidx.health.services.client.data.LocationAvailability
import androidx.health.services.client.data.LocationData
import androidx.health.services.client.data.MilestoneMarkerSummary
import androidx.health.services.client.data.SampleDataPoint
import androidx.health.services.client.data.WarmUpConfig
import androidx.health.services.client.endExercise
import androidx.health.services.client.getCapabilities
Expand All @@ -39,9 +51,20 @@ import androidx.health.services.client.prepareExercise
import androidx.health.services.client.resumeExercise
import androidx.health.services.client.startExercise
import com.example.exercisesamplecompose.service.ExerciseLogger
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationListener
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.Priority
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import java.time.Duration
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -51,8 +74,9 @@ import javax.inject.Singleton
@SuppressLint("RestrictedApi")
@Singleton
class ExerciseClientManager @Inject constructor(
val healthServicesClient: HealthServicesClient,
val logger: ExerciseLogger
private val healthServicesClient: HealthServicesClient,
private val flpClient: FusedLocationProviderClient,
private val logger: ExerciseLogger
) {
val exerciseClient: ExerciseClient = healthServicesClient.exerciseClient

Expand Down Expand Up @@ -168,6 +192,9 @@ class ExerciseClientManager @Inject constructor(
* cancelled, this flow will unregister the listener.
* [callbackFlow] is used to bridge between a callback-based API and Kotlin flows.
*/
@OptIn(ExperimentalCoroutinesApi::class)
@SuppressLint("MissingPermission")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worrying to have suppressions. Are there ways to structure without needing a suppression?

@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
val exerciseUpdateFlow = callbackFlow {
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
Expand All @@ -194,7 +221,7 @@ class ExerciseClientManager @Inject constructor(
}
}

exerciseClient.setUpdateCallback(callback)
exerciseClient.setUpdateCallback(callback, flpClient)
awaitClose {
// Ignore async result
exerciseClient.clearUpdateCallbackAsync(callback)
Expand All @@ -215,5 +242,149 @@ sealed class ExerciseMessage {
ExerciseMessage()
}

@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
@ExperimentalCoroutinesApi
fun FusedLocationProviderClient.locationUpdates(): Flow<Location> = callbackFlow {
val locationRequest =
LocationRequest.Builder(10000).setPriority(Priority.PRIORITY_HIGH_ACCURACY).build()

// val locationCallback = object : LocationCallback() {
// override fun onLocationResult(locationResult: LocationResult) {
// for (location in locationResult.locations) {
// trySend(location)
// }
// }
// }
// requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
// awaitClose { removeLocationUpdates(locationCallback) }

val locationListener = LocationListener { location -> trySend(location) }

requestLocationUpdates(locationRequest, locationListener, Looper.getMainLooper()) // is this the right looper?
awaitClose { removeLocationUpdates(locationListener) }

Log.d("qqqqqq", "requested location updates")
Copy link
Contributor
@yschimke yschimke Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't display, awaitClose{} is blocking. move above?

}

@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
fun FusedLocationProviderClient.locationUpdates(callback: (l: Location) -> Unit) {
val locationRequest =
LocationRequest.Builder(10000).setPriority(Priority.PRIORITY_HIGH_ACCURACY).build()

val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations) {
Log.d("qqqqq", "got location")
callback(location)
}
}
}

requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}

@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
suspend fun ExerciseClient.setUpdateCallback(
callback: ExerciseUpdateCallback,
ftpClient: FusedLocationProviderClient
) {
Log.d("qqqqqq", "exerciseupdatecallback")

val locationRequest =
LocationRequest.Builder(10000).setPriority(Priority.PRIORITY_HIGH_ACCURACY).build()

val locationListener = LocationListener { location -> callback.onExerciseUpdateReceived(
exerciseUpdateFromLocation(location))
}

ftpClient.requestLocationUpdates(locationRequest, locationListener, Looper.getMainLooper())

ftpClient.locationUpdates { location -> callback.onExerciseUpdateReceived(
exerciseUpdateFromLocation(location))
}

class Proxy(val obj: ExerciseUpdateCallback) : ExerciseUpdateCallback by obj {

override fun onExerciseUpdateReceived(update: ExerciseUpdate) {

val hasLocation = update.latestMetrics.getData(DataType.LOCATION).isNotEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth some comments why you are working around WHS. Or is this something health-services-client should be doing?


if (hasLocation) {
Log.d("qqqqqq", "WHS is now providing location, switching to WHS")
ftpClient.removeLocationUpdates(locationListener)
}

return obj.onExerciseUpdateReceived(update)
}
}

val proxy = Proxy(callback)

return setUpdateCallback(proxy)

// val locationFlow = ftpClient.locationUpdates()
// locationFlow.collect { location ->
// Log.d("qqqqqq", "sending update from FLP")
// callback.onExerciseUpdateReceived(ExerciseUpdateFromLocation(location))
// }
// return setUpdateCallback(callback)
}

@SuppressLint("RestrictedApi")
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun DataPointContainer.fromLocation(l: Location) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels awkward, ideally would only be in test code.

If you want to leave it, it should have a TODO linking to a tracking bug requesting these APIs be public.

DataPointContainer(
mapOf(
Pair(
DataType.LOCATION,
listOf(
SampleDataPoint(
DataType.LOCATION,
LocationData(l.latitude, l.longitude),
Duration.ZERO
)
)
)
)
)
}

//@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi")
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun exerciseUpdateFromLocation(l: Location): ExerciseUpdate {
val latestMetrics: DataPointContainer = DataPointContainer(
mapOf(
Pair(
DataType.LOCATION,
listOf(
SampleDataPoint(
DataType.LOCATION,
LocationData(l.latitude, l.longitude),
Duration.ZERO
)
)
)
)
)
val latestAchievedGoals: Set<ExerciseGoal<Number>> = emptySet()
val latestMilestoneMarkerSummaries: Set<MilestoneMarkerSummary> = emptySet()
val exerciseStateInfo = ExerciseStateInfo(ExerciseState.ACTIVE, ExerciseEndReason.UNKNOWN)
val exerciseConfig: ExerciseConfig? = null
val activeDurationCheckpoint: ExerciseUpdate.ActiveDurationCheckpoint? = null
val updateDurationFromBoot: Duration? = null
val startTime: Instant? = null
val activeDurationLegacy: Duration = Duration.ZERO
return ExerciseUpdate(
latestMetrics,
latestAchievedGoals,
latestMilestoneMarkerSummaries,
exerciseStateInfo,
exerciseConfig,
activeDurationCheckpoint,
updateDurationFromBoot,
startTime,
activeDurationLegacy
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
package com.example.exercisesamplecompose.di

import android.content.Context
import android.util.Log
import androidx.health.services.client.HealthServices
import androidx.health.services.client.HealthServicesClient
import com.example.exercisesamplecompose.service.AndroidLogExerciseLogger
import com.example.exercisesamplecompose.service.ExerciseLogger
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -51,4 +54,11 @@ class MainModule {
@Singleton
@Provides
fun provideLogger(): ExerciseLogger = AndroidLogExerciseLogger()

@Singleton
@Provides
fun provideFusedLocationProviderClient(@ApplicationContext context: Context): FusedLocationProviderClient {
Log.d("qqqqqq", "getting flpclient")
return LocationServices.getFusedLocationProviderClient(context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test-ext-junit = "androidx.test.ext:junit:1.1.5"
wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "androidx-wear-compose" }
wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "androidx-wear-compose" }
wear-ongoing-activity = "androidx.wear:wear-ongoing:1.0.0"

play-services-location = "com.google.android.gms:play-services-location:21.0.1"

[plugins]
com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
Expand Down
Loading