[go: nahoru, domu]

blob: f986b535b34c4944ef434ef60354cda292cd77c2 [file] [log] [blame]
/*
* 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.ActiveCaloriesBurnedRecord
import androidx.health.connect.client.records.BasalBodyTemperatureRecord
import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureMeasurementLocation
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.BodyWaterMassRecord
import androidx.health.connect.client.records.BoneMassRecord
import androidx.health.connect.client.records.CervicalMucusRecord
import androidx.health.connect.client.records.CervicalMucusRecord.Companion.APPEARANCE_INT_TO_STRING_MAP
import androidx.health.connect.client.records.CervicalMucusRecord.Companion.SENSATION_INT_TO_STRING_MAP
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ElevationGainedRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.HydrationRecord
import androidx.health.connect.client.records.IntermenstrualBleedingRecord
import androidx.health.connect.client.records.LeanBodyMassRecord
import androidx.health.connect.client.records.MealType
import androidx.health.connect.client.records.MenstruationFlowRecord
import androidx.health.connect.client.records.MenstruationPeriodRecord
import androidx.health.connect.client.records.NutritionRecord
import androidx.health.connect.client.records.OvulationTestRecord
import androidx.health.connect.client.records.OxygenSaturationRecord
import androidx.health.connect.client.records.PowerRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.RespiratoryRateRecord
import androidx.health.connect.client.records.RestingHeartRateRecord
import androidx.health.connect.client.records.SeriesRecord
import androidx.health.connect.client.records.SexualActivityRecord
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.SleepStageRecord
import androidx.health.connect.client.records.SleepStageRecord.Companion.STAGE_TYPE_INT_TO_STRING_MAP
import androidx.health.connect.client.records.SpeedRecord
import androidx.health.connect.client.records.StepsCadenceRecord
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.records.Vo2MaxRecord
import androidx.health.connect.client.records.WeightRecord
import androidx.health.connect.client.records.WheelchairPushesRecord
import androidx.health.platform.client.proto.DataProto
/** Converts public API object into internal proto for ipc. */
fun Record.toProto(): DataProto.DataPoint =
when (this) {
is BasalBodyTemperatureRecord ->
instantaneousProto()
.setDataType(protoDataType("BasalBodyTemperature"))
.apply {
putValues("temperature", doubleVal(temperature.inCelsius))
enumValFromInt(
measurementLocation,
BodyTemperatureMeasurementLocation
.MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
)
?.let { putValues("measurementLocation", it) }
}
.build()
is BasalMetabolicRateRecord ->
instantaneousProto()
.setDataType(protoDataType("BasalMetabolicRate"))
.apply { putValues("bmr", doubleVal(basalMetabolicRate.inKilocaloriesPerDay)) }
.build()
is BloodGlucoseRecord ->
instantaneousProto()
.setDataType(protoDataType("BloodGlucose"))
.apply {
putValues("level", doubleVal(level.inMillimolesPerLiter))
enumValFromInt(
specimenSource,
BloodGlucoseRecord.SPECIMEN_SOURCE_INT_TO_STRING_MAP
)
?.let { putValues("specimenSource", it) }
enumValFromInt(mealType, MealType.MEAL_TYPE_INT_TO_STRING_MAP)?.let {
putValues("mealType", it)
}
enumValFromInt(
relationToMeal,
BloodGlucoseRecord.RELATION_TO_MEAL_INT_TO_STRING_MAP,
)
?.let { putValues("relationToMeal", it) }
}
.build()
is BloodPressureRecord ->
instantaneousProto()
.setDataType(protoDataType("BloodPressure"))
.apply {
putValues("systolic", doubleVal(systolic.inMillimetersOfMercury))
putValues("diastolic", doubleVal(diastolic.inMillimetersOfMercury))
enumValFromInt(
bodyPosition,
BloodPressureRecord.BODY_POSITION_INT_TO_STRING_MAP
)
?.let { putValues("bodyPosition", it) }
enumValFromInt(
measurementLocation,
BloodPressureRecord.MEASUREMENT_LOCATION_INT_TO_STRING_MAP
)
?.let { putValues("measurementLocation", it) }
}
.build()
is BodyFatRecord ->
instantaneousProto()
.setDataType(protoDataType("BodyFat"))
.apply { putValues("percentage", doubleVal(percentage.value)) }
.build()
is BodyTemperatureRecord ->
instantaneousProto()
.setDataType(protoDataType("BodyTemperature"))
.apply {
putValues("temperature", doubleVal(temperature.inCelsius))
enumValFromInt(
measurementLocation,
BodyTemperatureMeasurementLocation
.MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
)
?.let { putValues("measurementLocation", it) }
}
.build()
is BodyWaterMassRecord ->
instantaneousProto()
.setDataType(protoDataType("BodyWaterMass"))
.apply { putValues("mass", doubleVal(mass.inKilograms)) }
.build()
is BoneMassRecord ->
instantaneousProto()
.setDataType(protoDataType("BoneMass"))
.apply { putValues("mass", doubleVal(mass.inKilograms)) }
.build()
is CervicalMucusRecord ->
instantaneousProto()
.setDataType(protoDataType("CervicalMucus"))
.apply {
enumValFromInt(appearance, APPEARANCE_INT_TO_STRING_MAP)?.let {
putValues("texture", it)
}
enumValFromInt(sensation, SENSATION_INT_TO_STRING_MAP)?.let {
putValues("amount", it)
}
}
.build()
is CyclingPedalingCadenceRecord ->
toProto(dataTypeName = "CyclingPedalingCadenceSeries") { sample ->
DataProto.SeriesValue.newBuilder()
.putValues("rpm", doubleVal(sample.revolutionsPerMinute))
.setInstantTimeMillis(sample.time.toEpochMilli())
.build()
}
is HeartRateRecord ->
toProto(dataTypeName = "HeartRateSeries") { sample ->
DataProto.SeriesValue.newBuilder()
.putValues("bpm", longVal(sample.beatsPerMinute))
.setInstantTimeMillis(sample.time.toEpochMilli())
.build()
}
is HeightRecord ->
instantaneousProto()
.setDataType(protoDataType("Height"))
.apply { putValues("height", doubleVal(height.inMeters)) }
.build()
is HeartRateVariabilityRmssdRecord ->
instantaneousProto()
.setDataType(protoDataType("HeartRateVariabilityRmssd"))
.apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
.build()
is IntermenstrualBleedingRecord ->
instantaneousProto().setDataType(protoDataType("IntermenstrualBleeding")).build()
is LeanBodyMassRecord ->
instantaneousProto()
.setDataType(protoDataType("LeanBodyMass"))
.apply { putValues("mass", doubleVal(mass.inKilograms)) }
.build()
is MenstruationFlowRecord ->
instantaneousProto()
.setDataType(protoDataType("Menstruation"))
.apply {
enumValFromInt(flow, MenstruationFlowRecord.FLOW_TYPE_INT_TO_STRING_MAP)?.let {
putValues("flow", it)
}
}
.build()
is MenstruationPeriodRecord ->
intervalProto().setDataType(protoDataType("MenstruationPeriod")).build()
is OvulationTestRecord ->
instantaneousProto()
.setDataType(protoDataType("OvulationTest"))
.apply {
enumValFromInt(result, OvulationTestRecord.RESULT_INT_TO_STRING_MAP)?.let {
putValues("result", it)
}
}
.build()
is OxygenSaturationRecord ->
instantaneousProto()
.setDataType(protoDataType("OxygenSaturation"))
.apply { putValues("percentage", doubleVal(percentage.value)) }
.build()
is PowerRecord ->
toProto(dataTypeName = "PowerSeries") { sample ->
DataProto.SeriesValue.newBuilder()
.putValues("power", doubleVal(sample.power.inWatts))
.setInstantTimeMillis(sample.time.toEpochMilli())
.build()
}
is RespiratoryRateRecord ->
instantaneousProto()
.setDataType(protoDataType("RespiratoryRate"))
.apply { putValues("rate", doubleVal(rate)) }
.build()
is RestingHeartRateRecord ->
instantaneousProto()
.setDataType(protoDataType("RestingHeartRate"))
.apply { putValues("bpm", longVal(beatsPerMinute)) }
.build()
is SexualActivityRecord ->
instantaneousProto()
.setDataType(protoDataType("SexualActivity"))
.apply {
enumValFromInt(
protectionUsed,
SexualActivityRecord.PROTECTION_USED_INT_TO_STRING_MAP
)
?.let { putValues("protectionUsed", it) }
}
.build()
is SpeedRecord ->
toProto(dataTypeName = "SpeedSeries") { sample ->
DataProto.SeriesValue.newBuilder()
.putValues("speed", doubleVal(sample.speed.inMetersPerSecond))
.setInstantTimeMillis(sample.time.toEpochMilli())
.build()
}
is StepsCadenceRecord ->
toProto(dataTypeName = "StepsCadenceSeries") { sample ->
DataProto.SeriesValue.newBuilder()
.putValues("rate", doubleVal(sample.rate))
.setInstantTimeMillis(sample.time.toEpochMilli())
.build()
}
is Vo2MaxRecord ->
instantaneousProto()
.setDataType(protoDataType("Vo2Max"))
.apply {
putValues("vo2", doubleVal(vo2MillilitersPerMinuteKilogram))
enumValFromInt(
measurementMethod,
Vo2MaxRecord.MEASUREMENT_METHOD_INT_TO_STRING_MAP
)
?.let { putValues("measurementMethod", it) }
}
.build()
is WeightRecord ->
instantaneousProto()
.setDataType(protoDataType("Weight"))
.apply { putValues("weight", doubleVal(weight.inKilograms)) }
.build()
is ActiveCaloriesBurnedRecord ->
intervalProto()
.setDataType(protoDataType("ActiveCaloriesBurned"))
.apply { putValues("energy", doubleVal(energy.inKilocalories)) }
.build()
is ExerciseSessionRecord ->
intervalProto()
.setDataType(protoDataType("ActivitySession"))
.putValues("hasRoute", boolVal(hasRoute))
.apply {
val exerciseType =
enumValFromInt(
exerciseType,
ExerciseSessionRecord.EXERCISE_TYPE_INT_TO_STRING_MAP
)
?: enumVal("workout")
putValues("activityType", exerciseType)
title?.let { putValues("title", stringVal(it)) }
notes?.let { putValues("notes", stringVal(it)) }
if (segments.isNotEmpty()) {
putSubTypeDataLists(
"segments",
DataProto.DataPoint.SubTypeDataList.newBuilder()
.addAllValues(segments.map { it.toProto() }).build()
)
}
if (laps.isNotEmpty()) {
putSubTypeDataLists(
"laps",
DataProto.DataPoint.SubTypeDataList.newBuilder()
.addAllValues(laps.map { it.toProto() }).build()
)
}
route?.let { exerciseRoute ->
putSubTypeDataLists(
"route",
DataProto.DataPoint.SubTypeDataList.newBuilder()
.addAllValues(exerciseRoute.route.map { it.toProto() }).build()
)
}
}
.build()
is DistanceRecord ->
intervalProto()
.setDataType(protoDataType("Distance"))
.apply { putValues("distance", doubleVal(distance.inMeters)) }
.build()
is ElevationGainedRecord ->
intervalProto()
.setDataType(protoDataType("ElevationGained"))
.apply { putValues("elevation", doubleVal(elevation.inMeters)) }
.build()
is FloorsClimbedRecord ->
intervalProto()
.setDataType(protoDataType("FloorsClimbed"))
.apply { putValues("floors", doubleVal(floors)) }
.build()
is HydrationRecord ->
intervalProto()
.setDataType(protoDataType("Hydration"))
.apply { putValues("volume", doubleVal(volume.inLiters)) }
.build()
is NutritionRecord ->
intervalProto()
.setDataType(protoDataType("Nutrition"))
.apply {
if (biotin != null) {
putValues("biotin", doubleVal(biotin.inGrams))
}
if (caffeine != null) {
putValues("caffeine", doubleVal(caffeine.inGrams))
}
if (calcium != null) {
putValues("calcium", doubleVal(calcium.inGrams))
}
if (energy != null) {
putValues("calories", doubleVal(energy.inKilocalories))
}
if (energyFromFat != null) {
putValues("caloriesFromFat", doubleVal(energyFromFat.inKilocalories))
}
if (chloride != null) {
putValues("chloride", doubleVal(chloride.inGrams))
}
if (cholesterol != null) {
putValues("cholesterol", doubleVal(cholesterol.inGrams))
}
if (chromium != null) {
putValues("chromium", doubleVal(chromium.inGrams))
}
if (copper != null) {
putValues("copper", doubleVal(copper.inGrams))
}
if (dietaryFiber != null) {
putValues("dietaryFiber", doubleVal(dietaryFiber.inGrams))
}
if (folate != null) {
putValues("folate", doubleVal(folate.inGrams))
}
if (folicAcid != null) {
putValues("folicAcid", doubleVal(folicAcid.inGrams))
}
if (iodine != null) {
putValues("iodine", doubleVal(iodine.inGrams))
}
if (iron != null) {
putValues("iron", doubleVal(iron.inGrams))
}
if (magnesium != null) {
putValues("magnesium", doubleVal(magnesium.inGrams))
}
if (manganese != null) {
putValues("manganese", doubleVal(manganese.inGrams))
}
if (molybdenum != null) {
putValues("molybdenum", doubleVal(molybdenum.inGrams))
}
if (monounsaturatedFat != null) {
putValues("monounsaturatedFat", doubleVal(monounsaturatedFat.inGrams))
}
if (niacin != null) {
putValues("niacin", doubleVal(niacin.inGrams))
}
if (pantothenicAcid != null) {
putValues("pantothenicAcid", doubleVal(pantothenicAcid.inGrams))
}
if (phosphorus != null) {
putValues("phosphorus", doubleVal(phosphorus.inGrams))
}
if (polyunsaturatedFat != null) {
putValues("polyunsaturatedFat", doubleVal(polyunsaturatedFat.inGrams))
}
if (potassium != null) {
putValues("potassium", doubleVal(potassium.inGrams))
}
if (protein != null) {
putValues("protein", doubleVal(protein.inGrams))
}
if (riboflavin != null) {
putValues("riboflavin", doubleVal(riboflavin.inGrams))
}
if (saturatedFat != null) {
putValues("saturatedFat", doubleVal(saturatedFat.inGrams))
}
if (selenium != null) {
putValues("selenium", doubleVal(selenium.inGrams))
}
if (sodium != null) {
putValues("sodium", doubleVal(sodium.inGrams))
}
if (sugar != null) {
putValues("sugar", doubleVal(sugar.inGrams))
}
if (thiamin != null) {
putValues("thiamin", doubleVal(thiamin.inGrams))
}
if (totalCarbohydrate != null) {
putValues("totalCarbohydrate", doubleVal(totalCarbohydrate.inGrams))
}
if (totalFat != null) {
putValues("totalFat", doubleVal(totalFat.inGrams))
}
if (transFat != null) {
putValues("transFat", doubleVal(transFat.inGrams))
}
if (unsaturatedFat != null) {
putValues("unsaturatedFat", doubleVal(unsaturatedFat.inGrams))
}
if (vitaminA != null) {
putValues("vitaminA", doubleVal(vitaminA.inGrams))
}
if (vitaminB12 != null) {
putValues("vitaminB12", doubleVal(vitaminB12.inGrams))
}
if (vitaminB6 != null) {
putValues("vitaminB6", doubleVal(vitaminB6.inGrams))
}
if (vitaminC != null) {
putValues("vitaminC", doubleVal(vitaminC.inGrams))
}
if (vitaminD != null) {
putValues("vitaminD", doubleVal(vitaminD.inGrams))
}
if (vitaminE != null) {
putValues("vitaminE", doubleVal(vitaminE.inGrams))
}
if (vitaminK != null) {
putValues("vitaminK", doubleVal(vitaminK.inGrams))
}
if (zinc != null) {
putValues("zinc", doubleVal(zinc.inGrams))
}
enumValFromInt(mealType, MealType.MEAL_TYPE_INT_TO_STRING_MAP)?.let {
putValues("mealType", it)
}
name?.let { putValues("name", stringVal(it)) }
}
.build()
is SleepSessionRecord ->
intervalProto()
.setDataType(protoDataType("SleepSession"))
.apply {
if (stages.isNotEmpty()) {
putSubTypeDataLists(
"stages",
DataProto.DataPoint.SubTypeDataList.newBuilder()
.addAllValues(stages.map { it.toProto() }).build()
)
}
title?.let { putValues("title", stringVal(it)) }
notes?.let { putValues("notes", stringVal(it)) }
}
.build()
is SleepStageRecord ->
intervalProto()
.setDataType(protoDataType("SleepStage"))
.apply {
enumValFromInt(stage, STAGE_TYPE_INT_TO_STRING_MAP)?.let {
putValues("stage", it)
}
}
.build()
is StepsRecord ->
intervalProto()
.setDataType(protoDataType("Steps"))
.apply { putValues("count", longVal(count)) }
.build()
is TotalCaloriesBurnedRecord ->
intervalProto()
.setDataType(protoDataType("TotalCaloriesBurned"))
.apply { putValues("energy", doubleVal(energy.inKilocalories)) }
.build()
is WheelchairPushesRecord ->
intervalProto()
.setDataType(protoDataType("WheelchairPushes"))
.apply { putValues("count", longVal(count)) }
.build()
else -> throw RuntimeException("Unsupported yet!")
}
private fun <T : Any> SeriesRecord<T>.toProto(
dataTypeName: String,
getSeriesValue: (sample: T) -> DataProto.SeriesValue,
): DataProto.DataPoint =
intervalProto()
.setDataType(protoDataType(dataTypeName = dataTypeName))
.apply {
for (sample in samples) {
addSeriesValues(getSeriesValue(sample))
}
}
.build()