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