1 /** <lambda>null2 * Copyright (C) 2023 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 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package com.android.healthconnect.controller.dataentries.formatters 15 16 import android.content.Context 17 import android.health.connect.datatypes.SleepSessionRecord 18 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_AWAKE 19 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_AWAKE_IN_BED 20 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_AWAKE_OUT_OF_BED 21 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING 22 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_DEEP 23 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_LIGHT 24 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_REM 25 import android.health.connect.datatypes.SleepSessionRecord.StageType.STAGE_TYPE_UNKNOWN 26 import com.android.healthconnect.controller.R 27 import com.android.healthconnect.controller.data.entries.FormattedEntry 28 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSessionDetail 29 import com.android.healthconnect.controller.dataentries.formatters.DurationFormatter.formatDurationLong 30 import com.android.healthconnect.controller.dataentries.formatters.DurationFormatter.formatDurationShort 31 import com.android.healthconnect.controller.dataentries.formatters.shared.BaseFormatter 32 import com.android.healthconnect.controller.dataentries.formatters.shared.RecordDetailsFormatter 33 import com.android.healthconnect.controller.dataentries.formatters.shared.UnitFormatter 34 import com.android.healthconnect.controller.dataentries.units.UnitPreferences 35 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter 36 import com.google.common.annotations.VisibleForTesting 37 import dagger.hilt.android.qualifiers.ApplicationContext 38 import java.time.Duration 39 import javax.inject.Inject 40 41 /** Formatter for printing SleepSessionRecord data. */ 42 class SleepSessionFormatter @Inject constructor(@ApplicationContext private val context: Context) : 43 BaseFormatter<SleepSessionRecord>(context), 44 RecordDetailsFormatter<SleepSessionRecord>, 45 UnitFormatter<Long> { 46 47 private val timeFormatter = LocalDateTimeFormatter(context) 48 49 override suspend fun formatRecord( 50 record: SleepSessionRecord, 51 header: String, 52 headerA11y: String, 53 unitPreferences: UnitPreferences 54 ): FormattedEntry { 55 return FormattedEntry.SleepSessionEntry( 56 uuid = record.metadata.id, 57 header = header, 58 headerA11y = headerA11y, 59 title = formatValue(record), 60 titleA11y = formatA11yValue(record), 61 dataType = getDataType(record), 62 notes = getNotes(record)) 63 } 64 65 @VisibleForTesting 66 fun formatValue(record: SleepSessionRecord): String { 67 return formatSleepSession(record) { duration -> formatDurationShort(context, duration) } 68 } 69 70 @VisibleForTesting 71 fun formatA11yValue(record: SleepSessionRecord): String { 72 return formatSleepSession(record) { duration -> formatDurationLong(context, duration) } 73 } 74 75 override fun formatA11yUnit(unit: Long): String { 76 return formatDurationLong(context, Duration.ofMillis(unit)) 77 } 78 79 override fun formatUnit(unit: Long): String { 80 return formatDurationShort(context, Duration.ofMillis(unit)) 81 } 82 83 private fun getNotes(record: SleepSessionRecord): String? { 84 return record.notes?.toString() 85 } 86 87 private fun formatSleepSession( 88 record: SleepSessionRecord, 89 formatDuration: (duration: Duration) -> String 90 ): String { 91 return if (!record.title.isNullOrBlank()) { 92 context.getString(R.string.sleep_session_with_one_field, record.title) 93 } else { 94 val duration = Duration.between(record.startTime, record.endTime) 95 context.getString(R.string.sleep_session_default, formatDuration(duration)) 96 } 97 } 98 99 override suspend fun formatRecordDetails(record: SleepSessionRecord): List<FormattedEntry> { 100 val sortedStages = record.stages.sortedBy { it.startTime } 101 return sortedStages.map { stage -> formatSleepStage(record.metadata.id, stage) } 102 } 103 104 private fun formatSleepStage(id: String, stage: SleepSessionRecord.Stage): FormattedEntry { 105 return FormattedSessionDetail( 106 uuid = id, 107 header = timeFormatter.formatTimeRange(stage.startTime, stage.endTime), 108 headerA11y = timeFormatter.formatTimeRangeA11y(stage.startTime, stage.endTime), 109 title = formatStageType(stage) { duration -> formatDurationShort(context, duration) }, 110 titleA11y = 111 formatStageType(stage) { duration -> formatDurationLong(context, duration) }, 112 ) 113 } 114 115 private fun formatStageType( 116 stage: SleepSessionRecord.Stage, 117 formatDuration: (duration: Duration) -> String 118 ): String { 119 val stageStringRes = 120 when (stage.type) { 121 STAGE_TYPE_UNKNOWN -> R.string.sleep_stage_unknown 122 STAGE_TYPE_AWAKE -> R.string.sleep_stage_awake 123 STAGE_TYPE_AWAKE_IN_BED -> R.string.sleep_stage_awake_in_bed 124 STAGE_TYPE_SLEEPING_DEEP -> R.string.sleep_stage_deep 125 STAGE_TYPE_SLEEPING_LIGHT -> R.string.sleep_stage_light 126 STAGE_TYPE_AWAKE_OUT_OF_BED -> R.string.sleep_stage_out_of_bed 127 STAGE_TYPE_SLEEPING_REM -> R.string.sleep_stage_rem 128 STAGE_TYPE_SLEEPING -> R.string.sleep_stage_sleeping 129 else -> { 130 throw IllegalArgumentException("Unrecognised sleep stage.") 131 } 132 } 133 val stageString = context.getString(stageStringRes) 134 val duration = Duration.between(stage.startTime, stage.endTime) 135 return context.getString( 136 R.string.sleep_stage_default, formatDuration(duration), stageString) 137 } 138 } 139