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