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