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 = record::class, 126 notes = getNotes(record), 127 route = record.route, 128 ) 129 } 130 131 fun formatValue(record: ExerciseSessionRecord): String { 132 return formatSession(record) { duration -> formatDurationShort(context, duration) } 133 } 134 135 fun formatA11yValue(record: ExerciseSessionRecord): String { 136 return formatSession(record) { duration -> formatDurationLong(context, duration) } 137 } 138 139 fun getNotes(record: ExerciseSessionRecord): String? { 140 return record.notes?.toString() 141 } 142 143 override suspend fun formatRecordDetails(record: ExerciseSessionRecord): List<FormattedEntry> { 144 val sortedSegments = 145 record.segments.sortedBy { it.startTime }.map { formatSegment(record.metadata.id, it) } 146 val sortedLaps = 147 record.laps.sortedBy { it.startTime }.map { formatLaps(record.metadata.id, it) } 148 return buildList { 149 if (sortedSegments.isNotEmpty()) { 150 add(SessionHeader(context.getString(R.string.exercise_segments_header))) 151 addAll(sortedSegments) 152 } 153 if (sortedLaps.isNotEmpty()) { 154 add(SessionHeader(context.getString(R.string.exercise_laps_header))) 155 addAll(sortedLaps) 156 } 157 } 158 } 159 160 private fun formatSession( 161 record: ExerciseSessionRecord, 162 formatDuration: (duration: Duration) -> String, 163 ): String { 164 val type = getExerciseType(context, record.exerciseType) 165 return if (!record.title.isNullOrBlank()) { 166 context.getString(R.string.session_title, record.title, type) 167 } else { 168 val duration = Duration.between(record.startTime, record.endTime) 169 context.getString(R.string.session_title, formatDuration(duration), type) 170 } 171 } 172 173 private fun formatSegment(id: String, segment: ExerciseSegment): FormattedSessionDetail { 174 return FormattedSessionDetail( 175 uuid = id, 176 header = timeFormatter.formatTimeRange(segment.startTime, segment.endTime), 177 headerA11y = timeFormatter.formatTimeRangeA11y(segment.startTime, segment.endTime), 178 title = formatSegmentTitle(segment.repetitionsCount, segment.segmentType), 179 titleA11y = formatSegmentTitleA11y(segment.repetitionsCount, segment.segmentType), 180 ) 181 } 182 183 private fun formatLaps(id: String, lap: ExerciseLap): FormattedSessionDetail { 184 return FormattedSessionDetail( 185 uuid = id, 186 header = timeFormatter.formatTimeRange(lap.startTime, lap.endTime), 187 headerA11y = timeFormatter.formatTimeRangeA11y(lap.startTime, lap.endTime), 188 title = LengthFormatter.formatValue(context, lap.length, unitPreferences), 189 titleA11y = LengthFormatter.formatA11yValue(context, lap.length, unitPreferences), 190 ) 191 } 192 193 private fun formatSegmentTitle(repetitionsCount: Int, type: Int): String { 194 val segmentType = exerciseSegmentTypeFormatter.getSegmentType(type) 195 val repetitions = 196 format(context.getString(R.string.repetitions), mapOf("count" to repetitionsCount)) 197 return context.getString(R.string.repetitions_format, segmentType, repetitions) 198 } 199 200 private fun formatSegmentTitleA11y(repetitionsCount: Int, type: Int): String { 201 val segmentType = exerciseSegmentTypeFormatter.getSegmentType(type) 202 val repetitions = 203 format(context.getString(R.string.repetitions_long), mapOf("count" to repetitionsCount)) 204 return context.getString(R.string.repetitions_format, segmentType, repetitions) 205 } 206 207 companion object { 208 fun getExerciseType(context: Context, type: Int): String { 209 return when (type) { 210 EXERCISE_SESSION_TYPE_BADMINTON -> context.getString(R.string.badminton) 211 EXERCISE_SESSION_TYPE_BASEBALL -> context.getString(R.string.baseball) 212 EXERCISE_SESSION_TYPE_BASKETBALL -> context.getString(R.string.basketball) 213 EXERCISE_SESSION_TYPE_BIKING -> context.getString(R.string.biking) 214 EXERCISE_SESSION_TYPE_BIKING_STATIONARY -> 215 context.getString(R.string.biking_stationary) 216 217 EXERCISE_SESSION_TYPE_BOOT_CAMP -> context.getString(R.string.boot_camp) 218 EXERCISE_SESSION_TYPE_BOXING -> context.getString(R.string.boxing) 219 EXERCISE_SESSION_TYPE_CALISTHENICS -> context.getString(R.string.calisthenics) 220 EXERCISE_SESSION_TYPE_CRICKET -> context.getString(R.string.cricket) 221 EXERCISE_SESSION_TYPE_DANCING -> context.getString(R.string.dancing) 222 EXERCISE_SESSION_TYPE_ELLIPTICAL -> context.getString(R.string.elliptical) 223 EXERCISE_SESSION_TYPE_EXERCISE_CLASS -> context.getString(R.string.exercise_class) 224 EXERCISE_SESSION_TYPE_FENCING -> context.getString(R.string.fencing) 225 EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN -> 226 context.getString(R.string.football_american) 227 228 EXERCISE_SESSION_TYPE_FOOTBALL_AUSTRALIAN -> 229 context.getString(R.string.activity_type_australian_football) 230 231 EXERCISE_SESSION_TYPE_FRISBEE_DISC -> context.getString(R.string.frisbee_disc) 232 EXERCISE_SESSION_TYPE_GOLF -> context.getString(R.string.golf) 233 EXERCISE_SESSION_TYPE_GUIDED_BREATHING -> 234 context.getString(R.string.guided_breathing) 235 236 EXERCISE_SESSION_TYPE_GYMNASTICS -> context.getString(R.string.gymnastics) 237 EXERCISE_SESSION_TYPE_HANDBALL -> context.getString(R.string.handball) 238 EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING -> 239 context.getString(R.string.high_intensity_interval_training) 240 241 EXERCISE_SESSION_TYPE_HIKING -> context.getString(R.string.hiking) 242 EXERCISE_SESSION_TYPE_ICE_HOCKEY -> context.getString(R.string.ice_hockey) 243 EXERCISE_SESSION_TYPE_ICE_SKATING -> context.getString(R.string.ice_skating) 244 EXERCISE_SESSION_TYPE_MARTIAL_ARTS -> context.getString(R.string.martial_arts) 245 EXERCISE_SESSION_TYPE_OTHER_WORKOUT -> context.getString(R.string.workout) 246 EXERCISE_SESSION_TYPE_PADDLING -> context.getString(R.string.paddling) 247 EXERCISE_SESSION_TYPE_PILATES -> context.getString(R.string.pilates) 248 EXERCISE_SESSION_TYPE_RACQUETBALL -> context.getString(R.string.racquetball) 249 EXERCISE_SESSION_TYPE_ROCK_CLIMBING -> context.getString(R.string.rock_climbing) 250 EXERCISE_SESSION_TYPE_ROLLER_HOCKEY -> context.getString(R.string.roller_hockey) 251 EXERCISE_SESSION_TYPE_ROWING -> context.getString(R.string.rowing) 252 EXERCISE_SESSION_TYPE_ROWING_MACHINE -> context.getString(R.string.rowing_machine) 253 EXERCISE_SESSION_TYPE_RUGBY -> context.getString(R.string.rugby) 254 EXERCISE_SESSION_TYPE_RUNNING -> context.getString(R.string.running) 255 EXERCISE_SESSION_TYPE_RUNNING_TREADMILL -> 256 context.getString(R.string.running_treadmill) 257 258 EXERCISE_SESSION_TYPE_SAILING -> context.getString(R.string.sailing) 259 EXERCISE_SESSION_TYPE_SCUBA_DIVING -> context.getString(R.string.scuba_diving) 260 EXERCISE_SESSION_TYPE_SKATING -> context.getString(R.string.skating) 261 EXERCISE_SESSION_TYPE_SKIING -> context.getString(R.string.skiing) 262 EXERCISE_SESSION_TYPE_SNOWBOARDING -> context.getString(R.string.snowboarding) 263 EXERCISE_SESSION_TYPE_SNOWSHOEING -> context.getString(R.string.snowshoeing) 264 EXERCISE_SESSION_TYPE_SOCCER -> context.getString(R.string.soccer) 265 EXERCISE_SESSION_TYPE_SOFTBALL -> context.getString(R.string.softball) 266 EXERCISE_SESSION_TYPE_SQUASH -> context.getString(R.string.squash) 267 EXERCISE_SESSION_TYPE_STAIR_CLIMBING -> context.getString(R.string.stair_climbing) 268 EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE -> 269 context.getString(R.string.stair_climbing_machine) 270 271 EXERCISE_SESSION_TYPE_STRENGTH_TRAINING -> 272 context.getString(R.string.strength_training) 273 274 EXERCISE_SESSION_TYPE_STRETCHING -> context.getString(R.string.stretching) 275 EXERCISE_SESSION_TYPE_SURFING -> context.getString(R.string.surfing) 276 EXERCISE_SESSION_TYPE_SWIMMING_OPEN_WATER -> 277 context.getString(R.string.swimming_open_water) 278 279 EXERCISE_SESSION_TYPE_SWIMMING_POOL -> context.getString(R.string.swimming_pool) 280 EXERCISE_SESSION_TYPE_TABLE_TENNIS -> context.getString(R.string.table_tennis) 281 EXERCISE_SESSION_TYPE_TENNIS -> context.getString(R.string.tennis) 282 EXERCISE_SESSION_TYPE_VOLLEYBALL -> context.getString(R.string.volleyball) 283 EXERCISE_SESSION_TYPE_WALKING -> context.getString(R.string.walking) 284 EXERCISE_SESSION_TYPE_WATER_POLO -> context.getString(R.string.water_polo) 285 EXERCISE_SESSION_TYPE_WEIGHTLIFTING -> context.getString(R.string.weightlifting) 286 EXERCISE_SESSION_TYPE_WHEELCHAIR -> context.getString(R.string.wheelchair) 287 EXERCISE_SESSION_TYPE_YOGA -> context.getString(R.string.yoga) 288 EXERCISE_SESSION_TYPE_PARAGLIDING -> context.getString(R.string.paragliding) 289 else -> context.getString(R.string.unknown_type) 290 } 291 } 292 } 293 } 294