1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  *
17  */
18 
19 package com.android.healthconnect.controller.dataentries.formatters
20 
21 import android.content.Context
22 import android.health.connect.datatypes.ExerciseLap
23 import android.health.connect.datatypes.ExerciseSegment
24 import android.health.connect.datatypes.ExerciseSessionRecord
25 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BADMINTON
26 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BASEBALL
27 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BASKETBALL
28 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BIKING
29 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BIKING_STATIONARY
30 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BOOT_CAMP
31 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_BOXING
32 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_CALISTHENICS
33 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_CRICKET
34 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_DANCING
35 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ELLIPTICAL
36 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_EXERCISE_CLASS
37 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_FENCING
38 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN
39 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_FOOTBALL_AUSTRALIAN
40 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_FRISBEE_DISC
41 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_GOLF
42 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_GUIDED_BREATHING
43 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_GYMNASTICS
44 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_HANDBALL
45 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING
46 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_HIKING
47 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ICE_HOCKEY
48 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ICE_SKATING
49 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_MARTIAL_ARTS
50 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_OTHER_WORKOUT
51 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_PADDLING
52 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_PARAGLIDING
53 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_PILATES
54 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_RACQUETBALL
55 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ROCK_CLIMBING
56 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ROLLER_HOCKEY
57 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ROWING
58 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_ROWING_MACHINE
59 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_RUGBY
60 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING
61 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING_TREADMILL
62 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SAILING
63 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SCUBA_DIVING
64 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SKATING
65 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SKIING
66 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SNOWBOARDING
67 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SNOWSHOEING
68 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SOCCER
69 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SOFTBALL
70 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SQUASH
71 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_STAIR_CLIMBING
72 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE
73 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_STRENGTH_TRAINING
74 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_STRETCHING
75 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SURFING
76 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SWIMMING_OPEN_WATER
77 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_SWIMMING_POOL
78 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_TABLE_TENNIS
79 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_TENNIS
80 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_VOLLEYBALL
81 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_WALKING
82 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_WATER_POLO
83 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_WEIGHTLIFTING
84 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_WHEELCHAIR
85 import android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_YOGA
86 import android.icu.text.MessageFormat.format
87 import com.android.healthconnect.controller.R
88 import com.android.healthconnect.controller.data.entries.FormattedEntry
89 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExerciseSessionEntry
90 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSessionDetail
91 import com.android.healthconnect.controller.data.entries.FormattedEntry.SessionHeader
92 import com.android.healthconnect.controller.dataentries.formatters.DurationFormatter.formatDurationLong
93 import com.android.healthconnect.controller.dataentries.formatters.DurationFormatter.formatDurationShort
94 import com.android.healthconnect.controller.dataentries.formatters.shared.BaseFormatter
95 import com.android.healthconnect.controller.dataentries.formatters.shared.LengthFormatter
96 import com.android.healthconnect.controller.dataentries.formatters.shared.RecordDetailsFormatter
97 import com.android.healthconnect.controller.dataentries.units.UnitPreferences
98 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
99 import dagger.hilt.android.qualifiers.ApplicationContext
100 import java.time.Duration
101 import javax.inject.Inject
102 
103 /** Formatter for printing ExerciseSessionRecord data. */
104 class ExerciseSessionFormatter
105 @Inject
106 constructor(
107     @ApplicationContext private val context: Context,
108     private val exerciseSegmentTypeFormatter: ExerciseSegmentTypeFormatter
109 ) : BaseFormatter<ExerciseSessionRecord>(context), RecordDetailsFormatter<ExerciseSessionRecord> {
110 
111     private val timeFormatter = LocalDateTimeFormatter(context)
112 
113     override suspend fun formatRecord(
114         record: ExerciseSessionRecord,
115         header: String,
116         headerA11y: String,
117         unitPreferences: UnitPreferences
118     ): FormattedEntry {
119         return ExerciseSessionEntry(
120             uuid = record.metadata.id,
121             header = header,
122             headerA11y = headerA11y,
123             title = formatValue(record),
124             titleA11y = formatA11yValue(record),
125             dataType = getDataType(record),
126             notes = getNotes(record),
127             route = record.route)
128     }
129 
130     fun formatValue(record: ExerciseSessionRecord): String {
131         return formatSession(record) { duration -> formatDurationShort(context, duration) }
132     }
133 
134     fun formatA11yValue(record: ExerciseSessionRecord): String {
135         return formatSession(record) { duration -> formatDurationLong(context, duration) }
136     }
137 
138     fun getNotes(record: ExerciseSessionRecord): String? {
139         return record.notes?.toString()
140     }
141 
142     override suspend fun formatRecordDetails(record: ExerciseSessionRecord): List<FormattedEntry> {
143         val sortedSegments =
144             record.segments.sortedBy { it.startTime }.map { formatSegment(record.metadata.id, it) }
145         val sortedLaps =
146             record.laps.sortedBy { it.startTime }.map { formatLaps(record.metadata.id, it) }
147         return buildList {
148             if (sortedSegments.isNotEmpty()) {
149                 add(SessionHeader(context.getString(R.string.exercise_segments_header)))
150                 addAll(sortedSegments)
151             }
152             if (sortedLaps.isNotEmpty()) {
153                 add(SessionHeader(context.getString(R.string.exercise_laps_header)))
154                 addAll(sortedLaps)
155             }
156         }
157     }
158 
159     private fun formatSession(
160         record: ExerciseSessionRecord,
161         formatDuration: (duration: Duration) -> String
162     ): String {
163         val type = getExerciseType(context, record.exerciseType)
164         return if (!record.title.isNullOrBlank()) {
165             context.getString(R.string.session_title, record.title, type)
166         } else {
167             val duration = Duration.between(record.startTime, record.endTime)
168             context.getString(R.string.session_title, formatDuration(duration), type)
169         }
170     }
171 
172     private fun formatSegment(id: String, segment: ExerciseSegment): FormattedSessionDetail {
173         return FormattedSessionDetail(
174             uuid = id,
175             header = timeFormatter.formatTimeRange(segment.startTime, segment.endTime),
176             headerA11y = timeFormatter.formatTimeRangeA11y(segment.startTime, segment.endTime),
177             title = formatSegmentTitle(segment.repetitionsCount, segment.segmentType),
178             titleA11y = formatSegmentTitleA11y(segment.repetitionsCount, segment.segmentType),
179         )
180     }
181 
182     private fun formatLaps(id: String, lap: ExerciseLap): FormattedSessionDetail {
183         return FormattedSessionDetail(
184             uuid = id,
185             header = timeFormatter.formatTimeRange(lap.startTime, lap.endTime),
186             headerA11y = timeFormatter.formatTimeRangeA11y(lap.startTime, lap.endTime),
187             title = LengthFormatter.formatValue(context, lap.length, unitPreferences),
188             titleA11y = LengthFormatter.formatA11yValue(context, lap.length, unitPreferences),
189         )
190     }
191 
192     private fun formatSegmentTitle(repetitionsCount: Int, type: Int): String {
193         val segmentType = exerciseSegmentTypeFormatter.getSegmentType(type)
194         val repetitions =
195             format(context.getString(R.string.repetitions), mapOf("count" to repetitionsCount))
196         return context.getString(R.string.repetitions_format, segmentType, repetitions)
197     }
198 
199     private fun formatSegmentTitleA11y(repetitionsCount: Int, type: Int): String {
200         val segmentType = exerciseSegmentTypeFormatter.getSegmentType(type)
201         val repetitions =
202             format(context.getString(R.string.repetitions_long), mapOf("count" to repetitionsCount))
203         return context.getString(R.string.repetitions_format, segmentType, repetitions)
204     }
205 
206     companion object {
207         fun getExerciseType(context: Context, type: Int): String {
208             return when (type) {
209                 EXERCISE_SESSION_TYPE_BADMINTON -> context.getString(R.string.badminton)
210                 EXERCISE_SESSION_TYPE_BASEBALL -> context.getString(R.string.baseball)
211                 EXERCISE_SESSION_TYPE_BASKETBALL -> context.getString(R.string.basketball)
212                 EXERCISE_SESSION_TYPE_BIKING -> context.getString(R.string.biking)
213                 EXERCISE_SESSION_TYPE_BIKING_STATIONARY ->
214                     context.getString(R.string.biking_stationary)
215                 EXERCISE_SESSION_TYPE_BOOT_CAMP -> context.getString(R.string.boot_camp)
216                 EXERCISE_SESSION_TYPE_BOXING -> context.getString(R.string.boxing)
217                 EXERCISE_SESSION_TYPE_CALISTHENICS -> context.getString(R.string.calisthenics)
218                 EXERCISE_SESSION_TYPE_CRICKET -> context.getString(R.string.cricket)
219                 EXERCISE_SESSION_TYPE_DANCING -> context.getString(R.string.dancing)
220                 EXERCISE_SESSION_TYPE_ELLIPTICAL -> context.getString(R.string.elliptical)
221                 EXERCISE_SESSION_TYPE_EXERCISE_CLASS -> context.getString(R.string.exercise_class)
222                 EXERCISE_SESSION_TYPE_FENCING -> context.getString(R.string.fencing)
223                 EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN ->
224                     context.getString(R.string.football_american)
225                 EXERCISE_SESSION_TYPE_FOOTBALL_AUSTRALIAN ->
226                     context.getString(R.string.activity_type_australian_football)
227                 EXERCISE_SESSION_TYPE_FRISBEE_DISC -> context.getString(R.string.frisbee_disc)
228                 EXERCISE_SESSION_TYPE_GOLF -> context.getString(R.string.golf)
229                 EXERCISE_SESSION_TYPE_GUIDED_BREATHING ->
230                     context.getString(R.string.guided_breathing)
231                 EXERCISE_SESSION_TYPE_GYMNASTICS -> context.getString(R.string.gymnastics)
232                 EXERCISE_SESSION_TYPE_HANDBALL -> context.getString(R.string.handball)
233                 EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING ->
234                     context.getString(R.string.high_intensity_interval_training)
235                 EXERCISE_SESSION_TYPE_HIKING -> context.getString(R.string.hiking)
236                 EXERCISE_SESSION_TYPE_ICE_HOCKEY -> context.getString(R.string.ice_hockey)
237                 EXERCISE_SESSION_TYPE_ICE_SKATING -> context.getString(R.string.ice_skating)
238                 EXERCISE_SESSION_TYPE_MARTIAL_ARTS -> context.getString(R.string.martial_arts)
239                 EXERCISE_SESSION_TYPE_OTHER_WORKOUT -> context.getString(R.string.workout)
240                 EXERCISE_SESSION_TYPE_PADDLING -> context.getString(R.string.paddling)
241                 EXERCISE_SESSION_TYPE_PILATES -> context.getString(R.string.pilates)
242                 EXERCISE_SESSION_TYPE_RACQUETBALL -> context.getString(R.string.racquetball)
243                 EXERCISE_SESSION_TYPE_ROCK_CLIMBING -> context.getString(R.string.rock_climbing)
244                 EXERCISE_SESSION_TYPE_ROLLER_HOCKEY -> context.getString(R.string.roller_hockey)
245                 EXERCISE_SESSION_TYPE_ROWING -> context.getString(R.string.rowing)
246                 EXERCISE_SESSION_TYPE_ROWING_MACHINE -> context.getString(R.string.rowing_machine)
247                 EXERCISE_SESSION_TYPE_RUGBY -> context.getString(R.string.rugby)
248                 EXERCISE_SESSION_TYPE_RUNNING -> context.getString(R.string.running)
249                 EXERCISE_SESSION_TYPE_RUNNING_TREADMILL ->
250                     context.getString(R.string.running_treadmill)
251                 EXERCISE_SESSION_TYPE_SAILING -> context.getString(R.string.sailing)
252                 EXERCISE_SESSION_TYPE_SCUBA_DIVING -> context.getString(R.string.scuba_diving)
253                 EXERCISE_SESSION_TYPE_SKATING -> context.getString(R.string.skating)
254                 EXERCISE_SESSION_TYPE_SKIING -> context.getString(R.string.skiing)
255                 EXERCISE_SESSION_TYPE_SNOWBOARDING -> context.getString(R.string.snowboarding)
256                 EXERCISE_SESSION_TYPE_SNOWSHOEING -> context.getString(R.string.snowshoeing)
257                 EXERCISE_SESSION_TYPE_SOCCER -> context.getString(R.string.soccer)
258                 EXERCISE_SESSION_TYPE_SOFTBALL -> context.getString(R.string.softball)
259                 EXERCISE_SESSION_TYPE_SQUASH -> context.getString(R.string.squash)
260                 EXERCISE_SESSION_TYPE_STAIR_CLIMBING -> context.getString(R.string.stair_climbing)
261                 EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE ->
262                     context.getString(R.string.stair_climbing_machine)
263                 EXERCISE_SESSION_TYPE_STRENGTH_TRAINING ->
264                     context.getString(R.string.strength_training)
265                 EXERCISE_SESSION_TYPE_STRETCHING -> context.getString(R.string.stretching)
266                 EXERCISE_SESSION_TYPE_SURFING -> context.getString(R.string.surfing)
267                 EXERCISE_SESSION_TYPE_SWIMMING_OPEN_WATER ->
268                     context.getString(R.string.swimming_open_water)
269                 EXERCISE_SESSION_TYPE_SWIMMING_POOL -> context.getString(R.string.swimming_pool)
270                 EXERCISE_SESSION_TYPE_TABLE_TENNIS -> context.getString(R.string.table_tennis)
271                 EXERCISE_SESSION_TYPE_TENNIS -> context.getString(R.string.tennis)
272                 EXERCISE_SESSION_TYPE_VOLLEYBALL -> context.getString(R.string.volleyball)
273                 EXERCISE_SESSION_TYPE_WALKING -> context.getString(R.string.walking)
274                 EXERCISE_SESSION_TYPE_WATER_POLO -> context.getString(R.string.water_polo)
275                 EXERCISE_SESSION_TYPE_WEIGHTLIFTING -> context.getString(R.string.weightlifting)
276                 EXERCISE_SESSION_TYPE_WHEELCHAIR -> context.getString(R.string.wheelchair)
277                 EXERCISE_SESSION_TYPE_YOGA -> context.getString(R.string.yoga)
278                 EXERCISE_SESSION_TYPE_PARAGLIDING -> context.getString(R.string.paragliding)
279                 else -> throw IllegalArgumentException("Unknown exercise session type $type")
280             }
281         }
282     }
283 }
284