RecordToProtoUtils.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.
*/
@file:RestrictTo(RestrictTo.Scope.LIBRARY)
package androidx.health.connect.client.impl.converters.records
import androidx.annotation.RestrictTo
import androidx.health.connect.client.records.ExerciseLap
import androidx.health.connect.client.records.ExerciseRoute
import androidx.health.connect.client.records.ExerciseSegment
import androidx.health.connect.client.records.InstantaneousRecord
import androidx.health.connect.client.records.IntervalRecord
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.metadata.Device
import androidx.health.connect.client.records.metadata.DeviceTypes
import androidx.health.connect.client.records.metadata.Metadata
import androidx.health.platform.client.proto.DataProto
import java.time.Instant
internal fun protoDataType(dataTypeName: String): DataProto.DataType =
DataProto.DataType.newBuilder().setName(dataTypeName).build()
@SuppressWarnings("GoodTime") // Suppress GoodTime for serialize/de-serialize.
internal fun InstantaneousRecord.instantaneousProto(): DataProto.DataPoint.Builder {
val builder =
DataProto.DataPoint.newBuilder()
.setMetadata(metadata)
.setInstantTimeMillis(time.toEpochMilli())
zoneOffset?.let { builder.setZoneOffsetSeconds(it.totalSeconds) }
return builder
}
@SuppressWarnings("GoodTime") // Suppress GoodTime for serialize/de-serialize.
internal fun IntervalRecord.intervalProto(): DataProto.DataPoint.Builder {
val builder =
DataProto.DataPoint.newBuilder()
.setMetadata(metadata)
.setStartTimeMillis(startTime.toEpochMilli())
.setEndTimeMillis(endTime.toEpochMilli())
startZoneOffset?.let { builder.setStartZoneOffsetSeconds(it.totalSeconds) }
endZoneOffset?.let { builder.setEndZoneOffsetSeconds(it.totalSeconds) }
return builder
}
@SuppressWarnings("GoodTime") // Suppress GoodTime for serialize/de-serialize.
private fun DataProto.DataPoint.Builder.setMetadata(metadata: Metadata) = apply {
if (metadata.id != Metadata.EMPTY_ID) {
uid = metadata.id
}
if (metadata.dataOrigin.packageName.isNotEmpty()) {
dataOrigin =
DataProto.DataOrigin.newBuilder()
.setApplicationId(metadata.dataOrigin.packageName)
.build()
}
if (metadata.lastModifiedTime.isAfter(Instant.EPOCH)) {
updateTimeMillis = metadata.lastModifiedTime.toEpochMilli()
}
metadata.clientRecordId?.let { setClientId(it) }
if (metadata.clientRecordVersion > 0) {
clientVersion = metadata.clientRecordVersion
}
metadata.device?.let { setDevice(it.toProto()) }
if (metadata.recordingMethod > 0) {
recordingMethod = metadata.recordingMethod
}
}
internal fun Device.toProto(): DataProto.Device {
val obj = this
return DataProto.Device.newBuilder()
.apply {
obj.manufacturer?.let { setManufacturer(it) }
obj.model?.let { setModel(it) }
setType(DEVICE_TYPE_INT_TO_STRING_MAP.getOrDefault(obj.type, DeviceTypes.UNKNOWN))
}
.build()
}
internal fun SleepSessionRecord.Stage.toProto(): DataProto.SubTypeDataValue {
return DataProto.SubTypeDataValue.newBuilder()
.setStartTimeMillis(startTime.toEpochMilli())
.setEndTimeMillis(endTime.toEpochMilli())
.apply {
enumValFromInt(stage, SleepSessionRecord.STAGE_TYPE_INT_TO_STRING_MAP)?.let {
putValues("stage", it)
}
}
.build()
}
internal fun ExerciseSegment.toProto(): DataProto.SubTypeDataValue {
return DataProto.SubTypeDataValue.newBuilder()
.setStartTimeMillis(startTime.toEpochMilli())
.setEndTimeMillis(endTime.toEpochMilli())
.putValues("type", longVal(segmentType.toLong()))
.putValues("reps", longVal(repetitions.toLong()))
.build()
}
internal fun ExerciseLap.toProto(): DataProto.SubTypeDataValue {
return DataProto.SubTypeDataValue.newBuilder()
.setStartTimeMillis(startTime.toEpochMilli())
.setEndTimeMillis(endTime.toEpochMilli())
.apply { length?.let { putValues("length", doubleVal(it.inMeters)) } }
.build()
}
internal fun ExerciseRoute.Location.toProto(): DataProto.SubTypeDataValue {
return DataProto.SubTypeDataValue.newBuilder()
.setStartTimeMillis(time.toEpochMilli())
.setEndTimeMillis(time.toEpochMilli())
.putValues("latitude", doubleVal(latitude))
.putValues("longitude", doubleVal(longitude))
.apply {
horizontalAccuracy?.let { putValues("horizontal_accuracy", doubleVal(it.inMeters)) }
verticalAccuracy?.let { putValues("vertical_accuracy", doubleVal(it.inMeters)) }
altitude?.let { putValues("altitude", doubleVal(it.inMeters)) }
}
.build()
}