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 requireViewById<TextView>(R.id.field_name).text = fieldName 66 mLinearLayout = requireViewById(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 = requireViewById<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 } 105 StepsCadenceRecordSample::class.java -> { 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 } 116 ExerciseLap::class.java -> { 117 rowLayout.addView(row.endTime) 118 EditableTextView(context, "Length", INPUT_TYPE_DOUBLE) 119 } 120 SkinTemperatureRecord.Delta::class.java -> { 121 EditableTextView(context, "Delta", INPUT_TYPE_SIGNED_DOUBLE) 122 } 123 PlannedExerciseBlock::class.java -> { 124 rowLayout.removeView(row.startTime) 125 ExerciseBlockInputField( 126 context, 127 EditableTextView(context, "Repetitions", INPUT_TYPE_INT), 128 EditableTextView(context, "Description", INPUT_TYPE_TEXT), 129 CheckBox(context), 130 ) 131 } 132 else -> { 133 return 134 } 135 } 136 row.dataPointField = dataPointField 137 rowLayout.addView(dataPointField) 138 mRowsData.add(row) 139 mLinearLayout.addView(rowLayout, 0) 140 } 141 getFieldValuenull142 override fun getFieldValue(): List<Any> { 143 val samples: ArrayList<Any> = ArrayList() 144 145 for (row in mRowsData) { 146 val dataPoint = row.dataPointField 147 val dataPointString = dataPoint.getFieldValue().toString() 148 val instant = row.startTime 149 when (mDataTypeClass) { 150 SpeedRecordSample::class.java -> { 151 samples.add( 152 SpeedRecordSample( 153 Velocity.fromMetersPerSecond(dataPointString.toDouble()), 154 instant.getFieldValue(), 155 ) 156 ) 157 } 158 HeartRateSample::class.java -> { 159 samples.add(HeartRateSample(dataPointString.toLong(), instant.getFieldValue())) 160 } 161 PowerRecordSample::class.java -> { 162 samples.add( 163 PowerRecordSample( 164 Power.fromWatts(dataPointString.toDouble()), 165 instant.getFieldValue(), 166 ) 167 ) 168 } 169 CyclingPedalingCadenceRecordSample::class.java -> { 170 samples.add( 171 CyclingPedalingCadenceRecordSample( 172 dataPointString.toDouble(), 173 instant.getFieldValue(), 174 ) 175 ) 176 } 177 StepsCadenceRecordSample::class.java -> { 178 samples.add( 179 StepsCadenceRecordSample( 180 dataPointString.toDouble(), 181 instant.getFieldValue(), 182 ) 183 ) 184 } 185 SleepSessionRecord.Stage::class.java -> { 186 samples.add( 187 SleepSessionRecord.Stage( 188 instant.getFieldValue(), 189 row.endTime.getFieldValue(), 190 dataPointString.toInt(), 191 ) 192 ) 193 } 194 ExerciseSegment::class.java -> { 195 samples.add( 196 ExerciseSegment.Builder( 197 instant.getFieldValue(), 198 row.endTime.getFieldValue(), 199 dataPointString.toInt(), 200 ) 201 .build() 202 ) 203 } 204 ExerciseLap::class.java -> { 205 samples.add( 206 ExerciseLap.Builder(instant.getFieldValue(), row.endTime.getFieldValue()) 207 .apply { 208 if (dataPointString.isNotEmpty()) { 209 setLength(Length.fromMeters(dataPointString.toDouble())) 210 } 211 } 212 .build() 213 ) 214 } 215 PlannedExerciseBlock::class.java -> { 216 samples.add(dataPoint.getFieldValue()) 217 } 218 SkinTemperatureRecord.Delta::class.java -> { 219 if (dataPointString.isNotEmpty()) { 220 samples.add( 221 SkinTemperatureRecord.Delta( 222 TemperatureDelta.fromCelsius(dataPointString.toDouble()), 223 instant.getFieldValue(), 224 ) 225 ) 226 } 227 } 228 } 229 } 230 return samples 231 } 232 isEmptynull233 override fun isEmpty(): Boolean { 234 return getFieldValue().isEmpty() 235 } 236 } 237