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