diff --git a/health-services/ExerciseSampleCompose/app/build.gradle b/health-services/ExerciseSampleCompose/app/build.gradle
index 92f638ac..e24a7e76 100644
--- a/health-services/ExerciseSampleCompose/app/build.gradle
+++ b/health-services/ExerciseSampleCompose/app/build.gradle
@@ -88,7 +88,7 @@ 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
@@ -96,7 +96,7 @@ dependencies {
implementation libs.horologist.health.composables
implementation libs.horologist.health.service
- //Wear Health Services
+ // Wear Health Services
implementation libs.androidx.health.services
// Lifecycle components
@@ -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
diff --git a/health-services/ExerciseSampleCompose/app/src/main/AndroidManifest.xml b/health-services/ExerciseSampleCompose/app/src/main/AndroidManifest.xml
index 902f3aad..7d8f7d43 100644
--- a/health-services/ExerciseSampleCompose/app/src/main/AndroidManifest.xml
+++ b/health-services/ExerciseSampleCompose/app/src/main/AndroidManifest.xml
@@ -26,6 +26,7 @@
+
diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/data/ExerciseClientManager.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/data/ExerciseClientManager.kt
index a0d2bef5..e83331e8 100644
--- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/data/ExerciseClientManager.kt
+++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/data/ExerciseClientManager.kt
@@ -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
@@ -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
@@ -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
@@ -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")
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
val exerciseUpdateFlow = callbackFlow {
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
@@ -194,7 +221,7 @@ class ExerciseClientManager @Inject constructor(
}
}
- exerciseClient.setUpdateCallback(callback)
+ exerciseClient.setUpdateCallback(callback, flpClient)
awaitClose {
// Ignore async result
exerciseClient.clearUpdateCallbackAsync(callback)
@@ -215,5 +242,76 @@ sealed class ExerciseMessage {
ExerciseMessage()
}
+@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
+@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+suspend fun ExerciseClient.setUpdateCallback(
+ callback: ExerciseUpdateCallback,
+ ftpClient: FusedLocationProviderClient
+) {
+ val locationRequest =
+ LocationRequest.Builder(10000).setPriority(Priority.PRIORITY_HIGH_ACCURACY).build()
+
+ val locationListener = LocationListener { location -> callback.onExerciseUpdateReceived(
+ ExerciseUpdate.fromLocation(location))
+ Log.d("qqqqqq", "Got location from FLP: $location")
+ }
+
+ ftpClient.requestLocationUpdates(locationRequest, locationListener, Looper.getMainLooper())
+
+ class Proxy(val obj: ExerciseUpdateCallback) : ExerciseUpdateCallback by obj {
+
+ override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
+
+ val hasLocation = update.latestMetrics.getData(DataType.LOCATION).isNotEmpty()
+
+ if (hasLocation) {
+ Log.d("qqqqqq", "Removing FLP")
+ ftpClient.removeLocationUpdates(locationListener)
+ }
+
+ return obj.onExerciseUpdateReceived(update)
+ }
+ }
+ val proxy = Proxy(callback)
+ return setUpdateCallback(proxy)
+}
+
+@SuppressLint("RestrictedApi")
+@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+fun ExerciseUpdate.Companion.fromLocation(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> = emptySet()
+ val latestMilestoneMarkerSummaries: Set = 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
+ )
+}
diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/di/MainModule.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/di/MainModule.kt
index 3fd1defe..b99855fc 100644
--- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/di/MainModule.kt
+++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/di/MainModule.kt
@@ -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
@@ -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)
+ }
}
diff --git a/health-services/ExerciseSampleCompose/gradle/libs.versions.toml b/health-services/ExerciseSampleCompose/gradle/libs.versions.toml
index f17a230a..e5aae11b 100644
--- a/health-services/ExerciseSampleCompose/gradle/libs.versions.toml
+++ b/health-services/ExerciseSampleCompose/gradle/libs.versions.toml
@@ -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" }