HealthConnectClientImpl.kt
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.health.connect.client.impl
import androidx.activity.result.contract.ActivityResultContract
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.PermissionController
import androidx.health.connect.client.aggregate.AggregationResult
import androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration
import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
import androidx.health.connect.client.impl.converters.aggregate.retrieveAggregateDataRow
import androidx.health.connect.client.impl.converters.aggregate.toAggregateDataRowGroupByDuration
import androidx.health.connect.client.impl.converters.aggregate.toAggregateDataRowGroupByPeriod
import androidx.health.connect.client.impl.converters.datatype.toDataTypeIdPairProtoList
import androidx.health.connect.client.impl.converters.datatype.toDataTypeName
import androidx.health.connect.client.impl.converters.permission.toJetpackPermission
import androidx.health.connect.client.impl.converters.permission.toProtoPermission
import androidx.health.connect.client.impl.converters.records.toProto
import androidx.health.connect.client.impl.converters.records.toRecord
import androidx.health.connect.client.impl.converters.request.toDeleteDataRangeRequestProto
import androidx.health.connect.client.impl.converters.request.toProto
import androidx.health.connect.client.impl.converters.request.toReadDataRangeRequestProto
import androidx.health.connect.client.impl.converters.request.toReadDataRequestProto
import androidx.health.connect.client.impl.converters.response.toChangesResponse
import androidx.health.connect.client.impl.converters.response.toReadRecordsResponse
import androidx.health.connect.client.permission.Permission
import androidx.health.connect.client.permission.createHealthDataRequestPermissions
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.request.AggregateGroupByDurationRequest
import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.request.ChangesTokenRequest
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.response.ChangesResponse
import androidx.health.connect.client.response.InsertRecordsResponse
import androidx.health.connect.client.response.ReadRecordResponse
import androidx.health.connect.client.response.ReadRecordsResponse
import androidx.health.connect.client.time.TimeRangeFilter
import androidx.health.platform.client.HealthDataAsyncClient
import androidx.health.platform.client.proto.DataProto
import androidx.health.platform.client.proto.RequestProto
import kotlin.reflect.KClass
import kotlinx.coroutines.guava.await
/**
* Kotlin extension implementation that exposes kotlin coroutines rather than guava
* ListenableFutures.
*
* @suppress
*/
class HealthConnectClientImpl
internal constructor(
private val providerPackageName: String,
private val delegate: HealthDataAsyncClient,
) : HealthConnectClient, PermissionController {
override fun createRequestPermissionActivityContract():
ActivityResultContract<Set<Permission>, Set<Permission>> =
createHealthDataRequestPermissions(providerPackageName = providerPackageName)
override suspend fun getGrantedPermissions(permissions: Set<Permission>): Set<Permission> {
return delegate
.getGrantedPermissions(permissions.map { it.toProtoPermission() }.toSet())
.await()
.map { it.toJetpackPermission() }
.toSet()
}
override suspend fun revokeAllPermissions() {
return delegate.revokeAllPermissions().await()
}
override val permissionController: PermissionController
get() = this
override suspend fun insertRecords(records: List<Record>): InsertRecordsResponse {
val uidList = delegate.insertData(records.map { it.toProto() }).await()
return InsertRecordsResponse(recordUidsList = uidList)
}
override suspend fun updateRecords(records: List<Record>) {
delegate.updateData(records.map { it.toProto() }).await()
}
override suspend fun deleteRecords(
recordType: KClass<out Record>,
uidsList: List<String>,
clientIdsList: List<String>,
) {
delegate
.deleteData(
toDataTypeIdPairProtoList(recordType, uidsList),
toDataTypeIdPairProtoList(recordType, clientIdsList)
)
.await()
}
override suspend fun deleteRecords(
recordType: KClass<out Record>,
timeRangeFilter: TimeRangeFilter,
) {
delegate.deleteDataRange(toDeleteDataRangeRequestProto(recordType, timeRangeFilter)).await()
}
@Suppress("UNCHECKED_CAST") // Safe to cast as the type should match
override suspend fun <T : Record> readRecord(
recordType: KClass<T>,
uid: String,
): ReadRecordResponse<T> {
val proto = delegate.readData(toReadDataRequestProto(recordType, uid)).await()
return ReadRecordResponse(toRecord(proto) as T)
}
override suspend fun getChangesToken(request: ChangesTokenRequest): String {
val proto =
delegate
.getChangesToken(
RequestProto.GetChangesTokenRequest.newBuilder()
.addAllDataType(
request.recordTypes.map {
DataProto.DataType.newBuilder().setName(it.toDataTypeName()).build()
}
)
.addAllDataOriginFilters(
request.dataOriginFilters.map {
DataProto.DataOrigin.newBuilder()
.setApplicationId(it.packageName)
.build()
}
)
.build()
)
.await()
return proto.changesToken
}
override suspend fun getChanges(changesToken: String): ChangesResponse {
val proto =
delegate
.getChanges(
RequestProto.GetChangesRequest.newBuilder()
.setChangesToken(changesToken)
.build()
)
.await()
return toChangesResponse(proto)
}
override suspend fun <T : Record> readRecords(
request: ReadRecordsRequest<T>,
): ReadRecordsResponse<T> {
val proto = delegate.readDataRange(toReadDataRangeRequestProto(request)).await()
return toReadRecordsResponse(proto)
}
override suspend fun aggregate(request: AggregateRequest): AggregationResult {
val responseProto = delegate.aggregate(request.toProto()).await()
return responseProto.rowsList.first().retrieveAggregateDataRow()
}
override suspend fun aggregateGroupByDuration(
request: AggregateGroupByDurationRequest,
): List<AggregationResultGroupedByDuration> {
val responseProto = delegate.aggregate(request.toProto()).await()
return responseProto.rowsList.map { it.toAggregateDataRowGroupByDuration() }.toList()
}
override suspend fun aggregateGroupByPeriod(
request: AggregateGroupByPeriodRequest
): List<AggregationResultGroupedByPeriod> {
val responseProto = delegate.aggregate(request.toProto()).await()
return responseProto.rowsList.map { it.toAggregateDataRowGroupByPeriod() }.toList()
}
}