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.DataOrigin 21 import android.health.connect.datatypes.Device 22 import android.health.connect.datatypes.HeartRateRecord 23 import android.health.connect.datatypes.MenstruationFlowRecord 24 import android.health.connect.datatypes.MenstruationFlowRecord.MenstruationFlowType 25 import android.health.connect.datatypes.MenstruationPeriodRecord 26 import android.health.connect.datatypes.Metadata 27 import android.health.connect.datatypes.SkinTemperatureRecord 28 import android.health.connect.datatypes.StepsRecord 29 import android.health.connect.datatypes.units.Temperature 30 import android.health.connect.datatypes.units.TemperatureDelta 31 import android.os.Build.MANUFACTURER 32 import android.os.Build.MODEL 33 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils.Companion.insertRecords 34 import java.time.Duration.ofDays 35 import java.time.Duration.ofMinutes 36 import java.time.Instant 37 import java.time.temporal.ChronoUnit 38 import java.util.Random 39 import kotlin.random.Random as ktRandom 40 import kotlinx.coroutines.runBlocking 41 42 class SeedData(private val context: Context, private val manager: HealthConnectManager) { 43 44 companion object { 45 const val NUMBER_OF_SERIES_RECORDS_TO_INSERT = 200L 46 val VALID_READING_LOCATIONS = 47 setOf( 48 SkinTemperatureRecord.MEASUREMENT_LOCATION_FINGER, 49 SkinTemperatureRecord.MEASUREMENT_LOCATION_TOE, 50 SkinTemperatureRecord.MEASUREMENT_LOCATION_WRIST, 51 SkinTemperatureRecord.MEASUREMENT_LOCATION_UNKNOWN) 52 } 53 54 fun seedData() { 55 runBlocking { 56 try { 57 seedMenstruationData() 58 seedStepsData() 59 seedHeartRateData(10) 60 seedSkinTemperatureData(10) 61 } catch (ex: Exception) { 62 throw ex 63 } 64 } 65 } 66 67 private suspend fun seedStepsData() { 68 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 69 val records = (1L..50).map { count -> getStepsRecord(count, start.plus(ofMinutes(count))) } 70 71 insertRecords(records, manager) 72 } 73 74 private suspend fun seedMenstruationData() { 75 val today = Instant.now() 76 val periodRecord = 77 MenstruationPeriodRecord.Builder(getMetaData(), today.minus(ofDays(5L)), today).build() 78 val flowRecords = 79 (-5..0).map { days -> 80 MenstruationFlowRecord.Builder( 81 getMetaData(), 82 today.plus(ofDays(days.toLong())), 83 MenstruationFlowType.FLOW_MEDIUM) 84 .build() 85 } 86 insertRecords( 87 buildList { 88 add(periodRecord) 89 addAll(flowRecords) 90 }, 91 manager) 92 } 93 94 suspend fun seedHeartRateData(numberOfRecordsPerBatch: Long) { 95 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 96 val random = Random() 97 val records = 98 (1L..numberOfRecordsPerBatch).map { timeOffset -> 99 val hrSamples = ArrayList<Pair<Long, Instant>>() 100 repeat(10) { i -> 101 hrSamples.add( 102 Pair(getValidHeartRate(random), start.plus(ofMinutes(timeOffset + i)))) 103 } 104 getHeartRateRecord( 105 hrSamples, 106 start.plus(ofMinutes(timeOffset)), 107 start.plus(ofMinutes(timeOffset + 100))) 108 } 109 insertRecords(records, manager) 110 } 111 112 private suspend fun seedSkinTemperatureData(numberOfRecordsPerBatch: Long) { 113 val start = Instant.now().truncatedTo(ChronoUnit.DAYS) 114 val records = 115 (1L..numberOfRecordsPerBatch).map { timeOffset -> 116 val skinTempSamples = ArrayList<SkinTemperatureRecord.Delta>() 117 repeat(10) { i -> 118 skinTempSamples.add( 119 SkinTemperatureRecord.Delta( 120 getValidTemperatureDelta(), start.plus(ofMinutes(timeOffset + i)))) 121 } 122 getSkinTemperatureRecord( 123 skinTempSamples, 124 start.plus(ofMinutes(timeOffset)), 125 start.plus(ofMinutes(timeOffset + 100))) 126 } 127 insertRecords(records, manager) 128 } 129 130 private fun getSkinTemperatureRecord( 131 deltasList: List<SkinTemperatureRecord.Delta>, 132 startTime: Instant, 133 endTime: Instant 134 ): SkinTemperatureRecord { 135 return SkinTemperatureRecord.Builder(getMetaData(), startTime, endTime) 136 .setDeltas(deltasList) 137 .setBaseline(Temperature.fromCelsius(25.0)) 138 .setMeasurementLocation(VALID_READING_LOCATIONS.random()) 139 .build() 140 } 141 142 private fun getValidTemperatureDelta(): TemperatureDelta { 143 return TemperatureDelta.fromCelsius((ktRandom.nextInt(-30, 30)).toDouble() / 10) 144 } 145 146 private fun getHeartRateRecord( 147 heartRateValues: List<Pair<Long, Instant>>, 148 start: Instant, 149 end: Instant, 150 ): HeartRateRecord { 151 return HeartRateRecord.Builder( 152 getMetaData(), 153 start, 154 end, 155 heartRateValues.map { HeartRateRecord.HeartRateSample(it.first, it.second) }) 156 .build() 157 } 158 159 private fun getValidHeartRate(random: Random): Long { 160 return (random.nextInt(20) + 80).toLong() 161 } 162 163 private fun getStepsRecord(count: Long, time: Instant): StepsRecord { 164 return StepsRecord.Builder(getMetaData(), time, time.plusSeconds(30), count).build() 165 } 166 167 private fun getMetaData(): Metadata { 168 val device: Device = 169 Device.Builder().setManufacturer(MANUFACTURER).setModel(MODEL).setType(1).build() 170 val dataOrigin = DataOrigin.Builder().setPackageName(context.packageName).build() 171 return Metadata.Builder().setDevice(device).setDataOrigin(dataOrigin).build() 172 } 173 } 174