1 /** <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * ``` 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * ``` 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.healthconnect.testapps.toolbox.seed 17 18 import android.content.Context 19 import android.health.connect.HealthConnectManager 20 import android.health.connect.datatypes.HeartRateRecord 21 import android.health.connect.datatypes.MenstruationFlowRecord 22 import android.health.connect.datatypes.MenstruationFlowRecord.MenstruationFlowType 23 import android.health.connect.datatypes.MenstruationPeriodRecord 24 import android.health.connect.datatypes.SkinTemperatureRecord 25 import android.health.connect.datatypes.StepsRecord 26 import android.health.connect.datatypes.units.Temperature 27 import android.health.connect.datatypes.units.TemperatureDelta 28 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils.Companion.getMetaData 29 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils.Companion.insertRecords 30 import kotlinx.coroutines.runBlocking 31 import java.time.Duration.ofDays 32 import java.time.Duration.ofMinutes 33 import java.time.Instant 34 import java.time.temporal.ChronoUnit 35 import java.util.Random 36 import kotlin.random.Random as ktRandom 37 38 39 class SeedData(private val context: Context, private val manager: HealthConnectManager) { 40 41 companion object { 42 const val NUMBER_OF_SERIES_RECORDS_TO_INSERT = 200L 43 val VALID_READING_LOCATIONS = 44 setOf( 45 SkinTemperatureRecord.MEASUREMENT_LOCATION_FINGER, 46 SkinTemperatureRecord.MEASUREMENT_LOCATION_TOE, 47 SkinTemperatureRecord.MEASUREMENT_LOCATION_WRIST, 48 SkinTemperatureRecord.MEASUREMENT_LOCATION_UNKNOWN) 49 } 50 51 fun seedData() { 52 runBlocking { 53 try { 54 seedMenstruationData() 55 seedStepsData() 56 seedHeartRateData(10) 57 seedSkinTemperatureData(10) 58 } catch (ex: Exception) { 59 throw ex 60 } 61 } 62 } 63 64 fun seedAllData(){ 65 try { 66 SeedActivityData(context, manager).seedActivityData() 67 SeedBodyMeasurementsData(context, manager).seedBodyMeasurementsData() 68 SeedCycleTrackingData(context, manager).seedCycleTrackingData() 69 SeedNutritionData(context, manager).seedNutritionData() 70 SeedSleepData(context, manager).seedSleepCategoryData() 71 SeedVitalsData(context, manager).seedVitalsData() 72 SeedWellnessData(context, manager).seedWellnessData() 73 }catch (ex: Exception) { 74 throw ex 75 } 76 } 77 78 private suspend fun seedStepsData() { 79 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 80 val records = (1L..50).map { count -> getStepsRecord(count, start.plus(ofMinutes(count))) } 81 82 insertRecords(records, manager) 83 } 84 85 private suspend fun seedMenstruationData() { 86 val today = Instant.now() 87 val periodRecord = 88 MenstruationPeriodRecord.Builder(getMetaData(context), today.minus(ofDays(5L)), today).build() 89 val flowRecords = 90 (-5..0).map { days -> 91 MenstruationFlowRecord.Builder( 92 getMetaData(context), 93 today.plus(ofDays(days.toLong())), 94 MenstruationFlowType.FLOW_MEDIUM) 95 .build() 96 } 97 insertRecords( 98 buildList { 99 add(periodRecord) 100 addAll(flowRecords) 101 }, 102 manager) 103 } 104 105 suspend fun seedHeartRateData(numberOfRecordsPerBatch: Long) { 106 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 107 val random = Random() 108 val records = 109 (1L..numberOfRecordsPerBatch).map { timeOffset -> 110 val hrSamples = ArrayList<Pair<Long, Instant>>() 111 repeat(10) { i -> 112 hrSamples.add( 113 Pair(getValidHeartRate(random), start.plus(ofMinutes(timeOffset + i)))) 114 } 115 getHeartRateRecord( 116 hrSamples, 117 start.plus(ofMinutes(timeOffset)), 118 start.plus(ofMinutes(timeOffset + 100))) 119 } 120 insertRecords(records, manager) 121 } 122 123 private suspend fun seedSkinTemperatureData(numberOfRecordsPerBatch: Long) { 124 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 125 val records = 126 (1L..numberOfRecordsPerBatch).map { timeOffset -> 127 val skinTempSamples = ArrayList<SkinTemperatureRecord.Delta>() 128 repeat(10) { i -> 129 skinTempSamples.add( 130 SkinTemperatureRecord.Delta( 131 getValidTemperatureDelta(), start.plus(ofMinutes(timeOffset + i)))) 132 } 133 getSkinTemperatureRecord( 134 skinTempSamples, 135 start.plus(ofMinutes(timeOffset)), 136 start.plus(ofMinutes(timeOffset + 100))) 137 } 138 insertRecords(records, manager) 139 } 140 141 private fun getSkinTemperatureRecord( 142 deltasList: List<SkinTemperatureRecord.Delta>, 143 startTime: Instant, 144 endTime: Instant 145 ): SkinTemperatureRecord { 146 return SkinTemperatureRecord.Builder(getMetaData(context), startTime, endTime) 147 .setDeltas(deltasList) 148 .setBaseline(Temperature.fromCelsius(25.0)) 149 .setMeasurementLocation(VALID_READING_LOCATIONS.random()) 150 .build() 151 } 152 153 private fun getValidTemperatureDelta(): TemperatureDelta { 154 return TemperatureDelta.fromCelsius((ktRandom.nextInt(-30, 30)).toDouble() / 10) 155 } 156 157 private fun getHeartRateRecord( 158 heartRateValues: List<Pair<Long, Instant>>, 159 start: Instant, 160 end: Instant, 161 ): HeartRateRecord { 162 return HeartRateRecord.Builder( 163 getMetaData(context), 164 start, 165 end, 166 heartRateValues.map { HeartRateRecord.HeartRateSample(it.first, it.second) }) 167 .build() 168 } 169 170 private fun getValidHeartRate(random: Random): Long { 171 return (random.nextInt(20) + 80).toLong() 172 } 173 174 private fun getStepsRecord(count: Long, time: Instant): StepsRecord { 175 return StepsRecord.Builder(getMetaData(context), time, time.plusSeconds(30), count).build() 176 } 177 } 178 179 180