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

feat: adds a new method for requesting exercise permissions #167

Merged
merged 15 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import dev.matinzd.healthconnect.permissions.HealthConnectPermissionDelegate
import dev.matinzd.healthconnect.permissions.PermissionUtils
import dev.matinzd.healthconnect.records.ReactExerciseSessionRecord
import dev.matinzd.healthconnect.records.ReactHealthRecord
import dev.matinzd.healthconnect.utils.ClientNotInitialized
import dev.matinzd.healthconnect.utils.ExerciseRouteAccessDenied
import dev.matinzd.healthconnect.utils.convertChangesTokenRequestOptionsFromJS
import dev.matinzd.healthconnect.utils.getTimeRangeFilter
import dev.matinzd.healthconnect.utils.reactRecordTypeToClassMap
Expand Down Expand Up @@ -62,16 +64,33 @@ class HealthConnectManager(private val applicationContext: ReactApplicationConte
}

fun requestPermission(
reactPermissions: ReadableArray, providerPackageName: String, promise: Promise
reactPermissions: ReadableArray,
promise: Promise
) {
throwUnlessClientIsAvailable(promise) {
coroutineScope.launch {
val granted = HealthConnectPermissionDelegate.launch(PermissionUtils.parsePermissions(reactPermissions))
val granted = HealthConnectPermissionDelegate.launchPermissionsDialog(PermissionUtils.parsePermissions(reactPermissions))
promise.resolve(PermissionUtils.mapPermissionResult(granted))
}
}
}

fun requestExerciseRoute(
recordId: String, promise: Promise
) {
throwUnlessClientIsAvailable(promise) {
coroutineScope.launch {
val exerciseRoute = HealthConnectPermissionDelegate.launchExerciseRouteAccessRequestDialog(recordId)
if (exerciseRoute != null) {
promise.resolve(ReactExerciseSessionRecord.parseExerciseRoute(exerciseRoute))
}
else{
promise.rejectWithException(ExerciseRouteAccessDenied())
}
}
matinzd marked this conversation as resolved.
Show resolved Hide resolved
}
}

fun revokeAllPermissions(promise: Promise) {
throwUnlessClientIsAvailable(promise) {
coroutineScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,20 @@ class HealthConnectModule internal constructor(context: ReactApplicationContext)
@ReactMethod
override fun requestPermission(
permissions: ReadableArray,
providerPackageName: String,
matinzd marked this conversation as resolved.
Show resolved Hide resolved
promise: Promise
) {
return manager.requestPermission(permissions, providerPackageName, promise)
return manager.requestPermission(permissions, promise)
}

@ReactMethod
override fun requestExerciseRoute(
recordId: String,
promise: Promise
) {
return manager.requestExerciseRoute(recordId, promise)
}


@ReactMethod
override fun getGrantedPermissions(promise: Promise) {
return manager.getGrantedPermissions(promise)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,51 @@ package dev.matinzd.healthconnect.permissions
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.health.connect.client.PermissionController
import androidx.health.connect.client.contracts.ExerciseRouteRequestContract
import androidx.health.connect.client.records.ExerciseRoute
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch

object HealthConnectPermissionDelegate {
private lateinit var requestPermission: ActivityResultLauncher<Set<String>>
private val channel = Channel<Set<String>>()
private val coroutineScope = CoroutineScope(Dispatchers.IO)
private val permissionsChannel = Channel<Set<String>>()
private val exerciseRouteChannel = Channel<ExerciseRoute?>()

private lateinit var requestPermission: ActivityResultLauncher<Set<String>>
private lateinit var requestRoutePermission: ActivityResultLauncher<String>

fun setPermissionDelegate(
activity: ComponentActivity,
providerPackageName: String = "com.google.android.apps.healthdata"
) {
val contract = PermissionController.createRequestPermissionResultContract(providerPackageName)
val exerciseRouteRequestContract = ExerciseRouteRequestContract()

requestPermission = activity.registerForActivityResult(contract) {
coroutineScope.launch {
channel.send(it)
permissionsChannel.send(it)
coroutineContext.cancel()
}
}

requestRoutePermission = activity.registerForActivityResult(exerciseRouteRequestContract) {
coroutineScope.launch {
exerciseRouteChannel.send(it)
coroutineContext.cancel()
}
}
}

suspend fun launch(permissions: Set<String>): Set<String> {
suspend fun launchPermissionsDialog(permissions: Set<String>): Set<String> {
requestPermission.launch(permissions)
return channel.receive()
return permissionsChannel.receive()
}

suspend fun launchExerciseRouteAccessRequestDialog(recordId: String): ExerciseRoute? {
requestRoutePermission.launch(recordId)
return exerciseRouteChannel.receive()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.matinzd.healthconnect.permissions

import android.util.Log
import androidx.health.connect.client.PermissionController
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.ExerciseSessionRecord
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableNativeArray
import dev.matinzd.healthconnect.utils.InvalidRecordType
Expand All @@ -13,10 +15,18 @@ class PermissionUtils {
return reactPermissions.toArrayList().mapNotNull {
it as HashMap<*, *>
val recordType = it["recordType"]
val accessType = it["accessType"]

Log.d("PermissionUtils","Access type $accessType and recordType $recordType")

if (accessType == "write" && recordType == "ExerciseRoute") {
return@mapNotNull HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE
}

val recordClass = reactRecordTypeToClassMap[recordType]
?: throw InvalidRecordType()

when (it["accessType"]) {
when (accessType) {
"write" -> HealthPermission.getWritePermission(recordClass)
"read" -> HealthPermission.getReadPermission(recordClass)
else -> null
Expand Down
matinzd marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
import androidx.health.connect.client.request.AggregateRequest
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableNativeArray
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import dev.matinzd.healthconnect.utils.*
Expand Down Expand Up @@ -101,36 +102,26 @@ class ReactExerciseSessionRecord : ReactHealthRecordImpl<ExerciseSessionRecord>
})


val exerciseRouteMap = WritableNativeMap()
when (record.exerciseRouteResult) {
is ExerciseRouteResult.Data -> {
val exerciseRouteMap = WritableNativeMap()
exerciseRouteMap.putArray("route", WritableNativeArray().apply {
(record.exerciseRouteResult as ExerciseRouteResult.Data).exerciseRoute.route.map {
val map = WritableNativeMap()
map.putString("time", it.time.toString())
map.putDouble("latitude", it.latitude)
map.putDouble("longitude", it.longitude)
map.putMap("horizontalAccuracy", lengthToJsMap(it.horizontalAccuracy))
map.putMap("verticalAccuracy", lengthToJsMap(it.verticalAccuracy))
map.putMap("altitude", lengthToJsMap(it.altitude))
this.pushMap(map)
}
})
putMap("exerciseRoute", exerciseRouteMap)
val exerciseRoute: ExerciseRoute =
(record.exerciseRouteResult as ExerciseRouteResult.Data).exerciseRoute
val route = parseExerciseRoute(exerciseRoute)
exerciseRouteMap.putString("type", "DATA")
exerciseRouteMap.putArray("route", route)
}

is ExerciseRouteResult.NoData -> {
putMap("exerciseRoute", WritableNativeMap())
}
exerciseRouteMap.putString("type", "NO_DATA")
exerciseRouteMap.putArray("route", WritableNativeArray())

is ExerciseRouteResult.ConsentRequired -> {
throw Exception("Consent required")
}

else -> {
putMap("exerciseRoute", WritableNativeMap())
is ExerciseRouteResult.ConsentRequired -> {
exerciseRouteMap.putString("type", "CONSENT_REQUIRED")
exerciseRouteMap.putArray("route", WritableNativeArray())
}
}
putMap("exerciseRoute", exerciseRouteMap)

putMap("metadata", convertMetadataToJSMap(record.metadata))
}
Expand Down Expand Up @@ -178,4 +169,21 @@ class ReactExerciseSessionRecord : ReactHealthRecordImpl<ExerciseSessionRecord>
}
}
}

companion object {
fun parseExerciseRoute(exerciseRoute: ExerciseRoute): ReadableNativeArray {
return WritableNativeArray().apply {
exerciseRoute.route.map {
val map = WritableNativeMap()
map.putString("time", it.time.toString())
map.putDouble("latitude", it.latitude)
map.putDouble("longitude", it.longitude)
map.putMap("horizontalAccuracy", lengthToJsMap(it.horizontalAccuracy))
map.putMap("verticalAccuracy", lengthToJsMap(it.verticalAccuracy))
map.putMap("altitude", lengthToJsMap(it.altitude))
this.pushMap(map)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.RemoteException
import com.facebook.react.bridge.Promise
import okio.IOException

class ExerciseRouteAccessDenied : Exception("Request to access exercise route denied")
class ClientNotInitialized : Exception("Health Connect client is not initialized")
class InvalidRecordType : Exception("Record type is not valid")
class InvalidTemperature : Exception("Temperature is not valid")
Expand Down
5 changes: 4 additions & 1 deletion android/src/oldarch/HealthConnectSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ abstract class HealthConnectSpec internal constructor(context: ReactApplicationC
abstract fun openHealthConnectDataManagement(providerPackageName: String?);

@ReactMethod
abstract fun requestPermission(permissions: ReadableArray, providerPackageName: String, promise: Promise);
abstract fun requestPermission(permissions: ReadableArray, promise: Promise);

@ReactMethod
abstract fun requestExerciseRoute(recordId: String, promise: Promise);

@ReactMethod
abstract fun getGrantedPermissions(promise: Promise);
Expand Down
2 changes: 2 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.health.WRITE_STEPS" />
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTES"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE"/>

<application
android:name=".MainApplication"
Expand Down
Loading
Loading