1 /** <lambda>null2 * Copyright (C) 2022 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 * ``` 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * ``` 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.healthconnect.controller.dataentries 17 18 import android.health.connect.HealthConnectManager 19 import android.health.connect.ReadRecordsRequestUsingFilters 20 import android.health.connect.ReadRecordsResponse 21 import android.health.connect.TimeInstantRangeFilter 22 import android.health.connect.datatypes.InstantRecord 23 import android.health.connect.datatypes.IntervalRecord 24 import android.health.connect.datatypes.Record 25 import android.util.Log 26 import androidx.core.os.asOutcomeReceiver 27 import com.android.healthconnect.controller.data.entries.FormattedEntry 28 import com.android.healthconnect.controller.dataentries.formatters.shared.HealthDataEntryFormatter 29 import com.android.healthconnect.controller.permissions.data.HealthPermissionType 30 import com.android.healthconnect.controller.service.IoDispatcher 31 import com.android.healthconnect.controller.shared.HealthPermissionToDatatypeMapper.getDataTypes 32 import com.android.healthconnect.controller.shared.usecase.BaseUseCase 33 import java.time.Duration.ofHours 34 import java.time.Duration.ofMinutes 35 import java.time.Instant 36 import java.time.ZoneId 37 import javax.inject.Inject 38 import javax.inject.Singleton 39 import kotlinx.coroutines.CoroutineDispatcher 40 import kotlinx.coroutines.suspendCancellableCoroutine 41 42 @Singleton 43 class LoadDataEntriesUseCase 44 @Inject 45 constructor( 46 private val healthDataEntryFormatter: HealthDataEntryFormatter, 47 private val healthConnectManager: HealthConnectManager, 48 @IoDispatcher private val dispatcher: CoroutineDispatcher 49 ) : BaseUseCase<LoadDataEntriesInput, List<FormattedEntry>>(dispatcher) { 50 51 companion object { 52 private const val TAG = "LoadDataEntriesUseCase" 53 } 54 55 override suspend fun execute(input: LoadDataEntriesInput): List<FormattedEntry> { 56 val timeFilterRange = getTimeFilter(input.selectedDate) 57 val dataTypes = getDataTypes(input.permissionType) 58 return dataTypes.map { dataType -> readDataType(dataType, timeFilterRange) }.flatten() 59 } 60 61 private fun getTimeFilter(selectedDate: Instant): TimeInstantRangeFilter { 62 val start = 63 selectedDate 64 .atZone(ZoneId.systemDefault()) 65 .toLocalDate() 66 .atStartOfDay(ZoneId.systemDefault()) 67 .toInstant() 68 val end = start.plus(ofHours(23)).plus(ofMinutes(59)) 69 return TimeInstantRangeFilter.Builder().setStartTime(start).setEndTime(end).build() 70 } 71 72 private suspend fun readDataType( 73 data: Class<out Record>, 74 timeFilterRange: TimeInstantRangeFilter 75 ): List<FormattedEntry> { 76 val filter = 77 ReadRecordsRequestUsingFilters.Builder(data).setTimeRangeFilter(timeFilterRange).build() 78 val records = 79 suspendCancellableCoroutine<ReadRecordsResponse<*>> { continuation -> 80 healthConnectManager.readRecords( 81 filter, Runnable::run, continuation.asOutcomeReceiver()) 82 } 83 .records 84 .sortedByDescending { record -> getStartTime(record) } 85 return records.mapNotNull { record -> getFormatterRecord(record) } 86 } 87 88 private suspend fun getFormatterRecord(record: Record): FormattedEntry? { 89 return try { 90 healthDataEntryFormatter.format(record) 91 } catch (ex: Exception) { 92 Log.i(TAG, "Failed to format record!") 93 null 94 } 95 } 96 97 private fun getStartTime(record: Record): Instant { 98 return when (record) { 99 is InstantRecord -> { 100 record.time 101 } 102 is IntervalRecord -> { 103 record.startTime 104 } 105 else -> { 106 throw IllegalArgumentException("unsupported record type!") 107 } 108 } 109 } 110 } 111 112 data class LoadDataEntriesInput( 113 val permissionType: HealthPermissionType, 114 val selectedDate: Instant 115 ) 116