• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.controller.dataentries.formatters
17 
18 import android.content.Context
19 import android.health.connect.datatypes.SkinTemperatureRecord
20 import android.health.connect.datatypes.SkinTemperatureRecord.Delta
21 import android.health.connect.datatypes.SkinTemperatureRecord.MEASUREMENT_LOCATION_UNKNOWN
22 import android.health.connect.datatypes.units.Temperature
23 import android.health.connect.datatypes.units.TemperatureDelta
24 import com.android.healthconnect.controller.R
25 import com.android.healthconnect.controller.data.entries.FormattedEntry
26 import com.android.healthconnect.controller.dataentries.formatters.shared.EntryFormatter
27 import com.android.healthconnect.controller.dataentries.formatters.shared.RecordDetailsFormatter
28 import com.android.healthconnect.controller.dataentries.formatters.shared.UnitFormatter
29 import com.android.healthconnect.controller.dataentries.units.UnitPreferences
30 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
31 import dagger.hilt.android.qualifiers.ApplicationContext
32 import javax.inject.Inject
33 import javax.inject.Singleton
34 
35 /** Formatter for printing SkinTemperatureRecord data. */
36 @Singleton
37 class SkinTemperatureFormatter
38 @Inject
39 constructor(@ApplicationContext private val context: Context) :
40     EntryFormatter<SkinTemperatureRecord>(context),
41     RecordDetailsFormatter<SkinTemperatureRecord>,
42     UnitFormatter<TemperatureDelta> {
43 
44     private val timeFormatter = LocalDateTimeFormatter(context)
45 
formatRecordnull46     override suspend fun formatRecord(
47         record: SkinTemperatureRecord,
48         header: String,
49         headerA11y: String,
50         unitPreferences: UnitPreferences,
51     ): FormattedEntry {
52         return FormattedEntry.SeriesDataEntry(
53             uuid = record.metadata.id,
54             header = header,
55             headerA11y = headerA11y,
56             title = formatValue(record, unitPreferences),
57             titleA11y = formatA11yValue(record, unitPreferences),
58             dataType = record::class,
59         )
60     }
61 
formatRecordDetailsnull62     override suspend fun formatRecordDetails(record: SkinTemperatureRecord): List<FormattedEntry> {
63 
64         val measurementLocationEntry: List<FormattedEntry> =
65             if (record.measurementLocation == MEASUREMENT_LOCATION_UNKNOWN) {
66                 listOf()
67             } else {
68                 listOf(
69                     FormattedEntry.ReverseSessionDetail(
70                         uuid = record.metadata.id,
71                         title =
72                             context.getString(R.string.skin_temperature_measurement_location_title),
73                         titleA11y =
74                             context.getString(R.string.skin_temperature_measurement_location_title),
75                         header = formatLocation(context, record.measurementLocation),
76                         headerA11y = formatLocation(context, record.measurementLocation),
77                     )
78                 )
79             }
80 
81         val baselineEntry: List<FormattedEntry> =
82             if (record.baseline == null) {
83                 listOf()
84             } else {
85                 listOf(
86                     FormattedEntry.ReverseSessionDetail(
87                         uuid = record.metadata.id,
88                         title = context.getString(R.string.skin_temperature_baseline_title),
89                         titleA11y = context.getString(R.string.skin_temperature_baseline_title),
90                         header = formatNullableTemperature(record.baseline, false),
91                         headerA11y = formatNullableTemperature(record.baseline, true),
92                     )
93                 )
94             }
95 
96         val deltasTitle: List<FormattedEntry> =
97             listOf(
98                 FormattedEntry.FormattedSectionTitle(
99                     context.getString(R.string.skin_temperature_delta_details_heading)
100                 )
101             )
102 
103         val deltas =
104             record.deltas
105                 .sortedBy { it.time }
106                 .map { formatDelta(record.metadata.id, it, unitPreferences) }
107         val formattedSectionDetails: List<FormattedEntry.FormattedSessionDetail> = buildList {
108             if (deltas.isNotEmpty()) {
109                 addAll(deltas)
110             }
111         }
112 
113         return measurementLocationEntry
114             .plus(baselineEntry)
115             .plus(deltasTitle)
116             .plus(formattedSectionDetails)
117     }
118 
formatNullableTemperaturenull119     private fun formatNullableTemperature(temperature: Temperature?, isA11y: Boolean): String {
120         if (temperature == null) return ""
121         return if (isA11y) {
122             TemperatureFormatter.formatA11tValue(
123                 context,
124                 temperature,
125                 MEASUREMENT_LOCATION_UNKNOWN,
126                 unitPreferences,
127             )
128         } else {
129             TemperatureFormatter.formatValue(
130                 context,
131                 temperature,
132                 MEASUREMENT_LOCATION_UNKNOWN,
133                 unitPreferences,
134             )
135         }
136     }
137 
formatDeltanull138     private fun formatDelta(
139         id: String,
140         delta: Delta,
141         unitPreferences: UnitPreferences,
142     ): FormattedEntry.FormattedSessionDetail {
143         return FormattedEntry.FormattedSessionDetail(
144             uuid = id,
145             header = timeFormatter.formatTime(delta.time),
146             headerA11y = timeFormatter.formatTime(delta.time),
147             title =
148                 TemperatureDeltaFormatter.formatSingleDeltaValue(
149                     context,
150                     delta.delta,
151                     unitPreferences,
152                 ),
153             titleA11y =
154                 TemperatureDeltaFormatter.formatSingleDeltaA11yValue(
155                     context,
156                     delta.delta,
157                     unitPreferences,
158                 ),
159         )
160     }
161 
formatValuenull162     override suspend fun formatValue(
163         record: SkinTemperatureRecord,
164         unitPreferences: UnitPreferences,
165     ): String {
166         return if (record.deltas.size == 1) {
167             formatUnit(record.deltas.first().delta)
168         } else {
169             format(record, unitPreferences, false)
170         }
171     }
172 
formatA11yValuenull173     override suspend fun formatA11yValue(
174         record: SkinTemperatureRecord,
175         unitPreferences: UnitPreferences,
176     ): String {
177         return if (record.deltas.size == 1) {
178             formatA11yUnit(record.deltas.first().delta)
179         } else {
180             format(record, unitPreferences, true)
181         }
182     }
183 
formatUnitnull184     override fun formatUnit(unit: TemperatureDelta): String {
185         return TemperatureDeltaFormatter.formatAverageDeltaValue(context, unit, unitPreferences)
186     }
187 
formatA11yUnitnull188     override fun formatA11yUnit(unit: TemperatureDelta): String {
189         return TemperatureDeltaFormatter.formatAverageDeltaA11yValue(context, unit, unitPreferences)
190     }
191 
formatnull192     private fun format(
193         record: SkinTemperatureRecord,
194         unitPreferences: UnitPreferences,
195         isA11y: Boolean,
196     ): String {
197         if (record.deltas.isEmpty()) {
198             return context.getString(R.string.no_data)
199         }
200         val averageDelta =
201             TemperatureDelta.fromCelsius(
202                 record.deltas.sumOf { it.delta.inCelsius } / record.deltas.size
203             )
204         return if (isA11y) {
205             TemperatureDeltaFormatter.formatAverageDeltaA11yValue(
206                 context,
207                 averageDelta,
208                 unitPreferences,
209             )
210         } else {
211             TemperatureDeltaFormatter.formatAverageDeltaValue(
212                 context,
213                 averageDelta,
214                 unitPreferences,
215             )
216         }
217     }
218 
formatLocationnull219     private fun formatLocation(context: Context, location: Int): String {
220         return when (location) {
221             SkinTemperatureRecord.MEASUREMENT_LOCATION_FINGER ->
222                 context.getString(R.string.temperature_location_finger)
223             SkinTemperatureRecord.MEASUREMENT_LOCATION_TOE ->
224                 context.getString(R.string.temperature_location_toe)
225             SkinTemperatureRecord.MEASUREMENT_LOCATION_WRIST ->
226                 context.getString(R.string.temperature_location_wrist)
227             else -> {
228                 throw IllegalArgumentException(
229                     "Unrecognised skin temperature measurement location: $location"
230                 )
231             }
232         }
233     }
234 }
235