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 package com.android.healthconnect.controller.data.entries.api
19 
20 import android.health.connect.TimeInstantRangeFilter
21 import android.health.connect.datatypes.MenstruationFlowRecord
22 import android.health.connect.datatypes.MenstruationPeriodRecord
23 import com.android.healthconnect.controller.data.entries.FormattedEntry
24 import com.android.healthconnect.controller.data.entries.datenavigation.DateNavigationPeriod
25 import com.android.healthconnect.controller.dataentries.formatters.MenstruationPeriodFormatter
26 import com.android.healthconnect.controller.service.IoDispatcher
27 import com.android.healthconnect.controller.shared.usecase.BaseUseCase
28 import com.android.healthconnect.controller.shared.usecase.UseCaseResults
29 import java.time.Duration.ofDays
30 import java.time.Instant
31 import javax.inject.Inject
32 import kotlinx.coroutines.CoroutineDispatcher
33 
34 /** Use case to load menstruation data entries. */
35 class LoadMenstruationDataUseCase
36 @Inject
37 constructor(
38     private val loadEntriesHelper: LoadEntriesHelper,
39     private val menstruationPeriodFormatter: MenstruationPeriodFormatter,
40     @IoDispatcher private val dispatcher: CoroutineDispatcher,
41 ) :
42     BaseUseCase<LoadMenstruationDataInput, List<FormattedEntry>>(dispatcher),
43     ILoadMenstruationDataUseCase {
44 
45     companion object {
46         private val SEARCH_RANGE = ofDays(30)
47     }
48 
49     override suspend fun execute(input: LoadMenstruationDataInput): List<FormattedEntry> {
50         val packageName = input.packageName
51         val selectedDate = input.displayedStartTime
52         val period = input.period
53         val showDataOrigin = input.showDataOrigin
54         val data = buildList {
55             addAll(getMenstruationPeriodRecords(packageName, selectedDate, period, showDataOrigin))
56             addAll(getMenstruationFlowRecords(packageName, selectedDate, period, showDataOrigin))
57         }
58         return data
59     }
60 
61     private suspend fun getMenstruationPeriodRecords(
62         packageName: String?,
63         selectedDate: Instant,
64         period: DateNavigationPeriod,
65         showDataOrigin: Boolean
66     ): List<FormattedEntry> {
67         val exactTimeRange =
68             loadEntriesHelper.getTimeFilter(selectedDate, period, endTimeExclusive = true)
69         val exactEnd = exactTimeRange.endTime!!
70         val exactStart = exactTimeRange.startTime!!
71 
72         // Special-casing MenstruationPeriod as it spans multiple days and we show it on all these
73         // days in the UI (not just the first day).
74         // Hardcode max period length to 30 days (completely arbitrary number).
75         val extendedSearchTimeRange =
76             TimeInstantRangeFilter.Builder()
77                 .setStartTime(exactEnd.minus(SEARCH_RANGE))
78                 .setEndTime(exactEnd)
79                 .build()
80 
81         val records =
82             loadEntriesHelper
83                 .readDataType(
84                     MenstruationPeriodRecord::class.java, extendedSearchTimeRange, packageName)
85                 .filter { menstruationPeriodRecord ->
86                     menstruationPeriodRecord is MenstruationPeriodRecord &&
87                         menstruationPeriodRecord.startTime.isBefore(exactEnd) &&
88                         menstruationPeriodRecord.endTime.isAfter(exactStart)
89                 }
90 
91         return records.map { record ->
92             menstruationPeriodFormatter.format(
93                 exactStart, record as MenstruationPeriodRecord, showDataOrigin)
94         }
95     }
96 
97     private suspend fun getMenstruationFlowRecords(
98         packageName: String?,
99         selectedDate: Instant,
100         period: DateNavigationPeriod,
101         showDataOrigin: Boolean
102     ): List<FormattedEntry> {
103         val timeRange =
104             loadEntriesHelper.getTimeFilter(selectedDate, period, endTimeExclusive = true)
105         val records =
106             loadEntriesHelper.readDataType(
107                 MenstruationFlowRecord::class.java, timeRange, packageName)
108 
109         return loadEntriesHelper.maybeAddDateSectionHeaders(records, period, showDataOrigin)
110     }
111 }
112 
113 data class LoadMenstruationDataInput(
114     val packageName: String?,
115     val displayedStartTime: Instant,
116     val period: DateNavigationPeriod,
117     val showDataOrigin: Boolean
118 )
119 
120 interface ILoadMenstruationDataUseCase {
invokenull121     suspend fun invoke(input: LoadMenstruationDataInput): UseCaseResults<List<FormattedEntry>>
122 
123     suspend fun execute(input: LoadMenstruationDataInput): List<FormattedEntry>
124 }
125