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