1 /**
2  * 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.fieldviews
17 
18 import android.annotation.SuppressLint
19 import android.content.Context
20 import android.health.connect.datatypes.CyclingPedalingCadenceRecord.CyclingPedalingCadenceRecordSample
21 import android.health.connect.datatypes.ExerciseLap
22 import android.health.connect.datatypes.ExerciseSegment
23 import android.health.connect.datatypes.ExerciseSegmentType
24 import android.health.connect.datatypes.HeartRateRecord.HeartRateSample
25 import android.health.connect.datatypes.PlannedExerciseBlock
26 import android.health.connect.datatypes.PowerRecord.PowerRecordSample
27 import android.health.connect.datatypes.SkinTemperatureRecord
28 import android.health.connect.datatypes.SleepSessionRecord
29 import android.health.connect.datatypes.SpeedRecord.SpeedRecordSample
30 import android.health.connect.datatypes.StepsCadenceRecord.StepsCadenceRecordSample
31 import android.health.connect.datatypes.units.Length
32 import android.health.connect.datatypes.units.Power
33 import android.health.connect.datatypes.units.TemperatureDelta
34 import android.health.connect.datatypes.units.Velocity
35 import android.widget.CheckBox
36 import android.widget.LinearLayout
37 import android.widget.TextView
38 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_DOUBLE
39 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_INT
40 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_LONG
41 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_SIGNED_DOUBLE
42 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_TEXT
43 import com.android.healthconnect.testapps.toolbox.R
44 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils.Companion.getStaticFieldNamesAndValues
45 import com.google.android.material.floatingactionbutton.FloatingActionButton
46 import java.lang.reflect.ParameterizedType
47 import java.lang.reflect.Type
48 
49 @SuppressLint("ViewConstructor")
50 class ListInputField(context: Context, fieldName: String, inputFieldType: ParameterizedType) :
51     InputFieldView(context) {
52 
53     data class Row(val context: Context) {
54         val startTime = DateTimePicker(context, "Start Time", true)
55         val endTime = DateTimePicker(context, "End Time")
56         lateinit var dataPointField: InputFieldView
57     }
58 
59     private var mLinearLayout: LinearLayout
60     private var mDataTypeClass: Type
61     private var mRowsData: ArrayList<Row>
62 
63     init {
64         inflate(context, R.layout.fragment_list_input_view, this)
65         findViewById<TextView>(R.id.field_name).text = fieldName
66         mLinearLayout = findViewById(R.id.list_input_linear_layout)
67         mDataTypeClass = inputFieldType.actualTypeArguments[0]
68         mRowsData = ArrayList()
69         setupAddRowButtonListener()
70     }
71 
setupAddRowButtonListenernull72     private fun setupAddRowButtonListener() {
73         val buttonView = findViewById<FloatingActionButton>(R.id.add_row)
74 
75         buttonView.setOnClickListener { addRow() }
76     }
77 
addRownull78     private fun addRow() {
79         val rowLayout = LinearLayout(context)
80         rowLayout.orientation = VERTICAL
81         val row = Row(context)
82         rowLayout.addView(row.startTime)
83         val dataPointField: InputFieldView =
84             when (mDataTypeClass) {
85                 SpeedRecordSample::class.java -> {
86                     EditableTextView(context, "Velocity", INPUT_TYPE_DOUBLE)
87                 }
88                 HeartRateSample::class.java -> {
89                     EditableTextView(context, "Beats per minute", INPUT_TYPE_LONG)
90                 }
91                 PowerRecordSample::class.java -> {
92                     EditableTextView(context, "Power", INPUT_TYPE_DOUBLE)
93                 }
94                 CyclingPedalingCadenceRecordSample::class.java -> {
95                     EditableTextView(context, "Revolutions Per Minute", INPUT_TYPE_DOUBLE)
96                 }
97                 SleepSessionRecord.Stage::class.java -> {
98                     rowLayout.addView(row.endTime)
99                     EnumDropDown(
100                         context,
101                         "Sleep Stage",
102                         getStaticFieldNamesAndValues(SleepSessionRecord.StageType::class))
103                 }
104                 StepsCadenceRecordSample::class.java -> {
105                     rowLayout.addView(row.startTime)
106                     EditableTextView(context, "Steps Cadence", INPUT_TYPE_DOUBLE)
107                 }
108                 ExerciseSegment::class.java -> {
109                     rowLayout.addView(row.endTime)
110                     EnumDropDown(
111                         context,
112                         "Segment Type",
113                         getStaticFieldNamesAndValues(ExerciseSegmentType::class))
114                 }
115                 ExerciseLap::class.java -> {
116                     rowLayout.addView(row.endTime)
117                     EditableTextView(context, "Length", INPUT_TYPE_DOUBLE)
118                 }
119                 SkinTemperatureRecord.Delta::class.java -> {
120                     EditableTextView(context, "Delta", INPUT_TYPE_SIGNED_DOUBLE)
121                 }
122                 PlannedExerciseBlock::class.java -> {
123                     rowLayout.removeView(row.startTime)
124                     ExerciseBlockInputField(
125                         context,
126                         EditableTextView(context, "Repetitions", INPUT_TYPE_INT),
127                         EditableTextView(context, "Description", INPUT_TYPE_TEXT),
128                         CheckBox(context))
129                 }
130                 else -> {
131                     return
132                 }
133             }
134         row.dataPointField = dataPointField
135         rowLayout.addView(dataPointField)
136         mRowsData.add(row)
137         mLinearLayout.addView(rowLayout, 0)
138     }
139 
getFieldValuenull140     override fun getFieldValue(): List<Any> {
141         val samples: ArrayList<Any> = ArrayList()
142 
143         for (row in mRowsData) {
144             val dataPoint = row.dataPointField
145             val dataPointString = dataPoint.getFieldValue().toString()
146             val instant = row.startTime
147             when (mDataTypeClass) {
148                 SpeedRecordSample::class.java -> {
149                     samples.add(
150                         SpeedRecordSample(
151                             Velocity.fromMetersPerSecond(dataPointString.toDouble()),
152                             instant.getFieldValue()))
153                 }
154                 HeartRateSample::class.java -> {
155                     samples.add(HeartRateSample(dataPointString.toLong(), instant.getFieldValue()))
156                 }
157                 PowerRecordSample::class.java -> {
158                     samples.add(
159                         PowerRecordSample(
160                             Power.fromWatts(dataPointString.toDouble()), instant.getFieldValue()))
161                 }
162                 CyclingPedalingCadenceRecordSample::class.java -> {
163                     samples.add(
164                         CyclingPedalingCadenceRecordSample(
165                             dataPointString.toDouble(), instant.getFieldValue()))
166                 }
167                 StepsCadenceRecordSample::class.java -> {
168                     samples.add(
169                         StepsCadenceRecordSample(
170                             dataPointString.toDouble(), instant.getFieldValue()))
171                 }
172                 SleepSessionRecord.Stage::class.java -> {
173                     samples.add(
174                         SleepSessionRecord.Stage(
175                             instant.getFieldValue(),
176                             row.endTime.getFieldValue(),
177                             dataPointString.toInt()))
178                 }
179                 ExerciseSegment::class.java -> {
180                     samples.add(
181                         ExerciseSegment.Builder(
182                                 instant.getFieldValue(),
183                                 row.endTime.getFieldValue(),
184                                 dataPointString.toInt())
185                             .build())
186                 }
187                 ExerciseLap::class.java -> {
188                     samples.add(
189                         ExerciseLap.Builder(instant.getFieldValue(), row.endTime.getFieldValue())
190                             .apply {
191                                 if (dataPointString.isNotEmpty()) {
192                                     setLength(Length.fromMeters(dataPointString.toDouble()))
193                                 }
194                             }
195                             .build())
196                 }
197                 PlannedExerciseBlock::class.java -> {
198                     samples.add(dataPoint.getFieldValue())
199                 }
200                 SkinTemperatureRecord.Delta::class.java -> {
201                     if (dataPointString.isNotEmpty()) {
202                         samples.add(
203                             SkinTemperatureRecord.Delta(
204                                 TemperatureDelta.fromCelsius(dataPointString.toDouble()),
205                                 instant.getFieldValue()))
206                     }
207                 }
208             }
209         }
210         return samples
211     }
212 
isEmptynull213     override fun isEmpty(): Boolean {
214         return getFieldValue().isEmpty()
215     }
216 }
217