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.dataentries.formatters.shared.HealthDataEntryFormatter 28 import com.android.healthconnect.controller.permissions.data.HealthPermissionType 29 import com.android.healthconnect.controller.service.IoDispatcher 30 import com.android.healthconnect.controller.shared.HealthPermissionToDatatypeMapper.getDataTypes 31 import com.android.healthconnect.controller.shared.usecase.BaseUseCase 32 import java.time.Duration.ofHours 33 import java.time.Duration.ofMinutes 34 import java.time.Instant 35 import java.time.ZoneId 36 import javax.inject.Inject 37 import javax.inject.Singleton 38 import kotlinx.coroutines.CoroutineDispatcher 39 import kotlinx.coroutines.suspendCancellableCoroutine 40 41 @Singleton 42 class LoadDataEntriesUseCase 43 @Inject 44 constructor( 45 private val healthDataEntryFormatter: HealthDataEntryFormatter, 46 private val healthConnectManager: HealthConnectManager, 47 @IoDispatcher private val dispatcher: CoroutineDispatcher 48 ) : BaseUseCase<LoadDataEntriesInput, List<FormattedEntry>>(dispatcher) { 49 50 companion object { 51 private const val TAG = "LoadDataEntriesUseCase" 52 } 53 54 override suspend fun execute(input: LoadDataEntriesInput): List<FormattedEntry> { 55 val timeFilterRange = getTimeFilter(input.selectedDate) 56 val dataTypes = getDataTypes(input.permissionType) 57 return dataTypes.map { dataType -> readDataType(dataType, timeFilterRange) }.flatten() 58 } 59 60 private fun getTimeFilter(selectedDate: Instant): TimeInstantRangeFilter { 61 val start = 62 selectedDate 63 .atZone(ZoneId.systemDefault()) 64 .toLocalDate() 65 .atStartOfDay(ZoneId.systemDefault()) 66 .toInstant() 67 val end = start.plus(ofHours(23)).plus(ofMinutes(59)) 68 return TimeInstantRangeFilter.Builder().setStartTime(start).setEndTime(end).build() 69 } 70 71 private suspend fun readDataType( 72 data: Class<out Record>, 73 timeFilterRange: TimeInstantRangeFilter 74 ): List<FormattedEntry> { 75 val filter = 76 ReadRecordsRequestUsingFilters.Builder(data).setTimeRangeFilter(timeFilterRange).build() 77 val records = 78 suspendCancellableCoroutine<ReadRecordsResponse<*>> { continuation -> 79 healthConnectManager.readRecords( 80 filter, Runnable::run, continuation.asOutcomeReceiver()) 81 } 82 .records 83 .sortedByDescending { record -> getStartTime(record) } 84 return records.mapNotNull { record -> getFormatterRecord(record) } 85 } 86 87 private suspend fun getFormatterRecord(record: Record): FormattedEntry? { 88 return try { 89 healthDataEntryFormatter.format(record) 90 } catch (ex: Exception) { 91 Log.i(TAG, "Failed to format record!") 92 null 93 } 94 } 95 96 private fun getStartTime(record: Record): Instant { 97 return when (record) { 98 is InstantRecord -> { 99 record.time 100 } 101 is IntervalRecord -> { 102 record.startTime 103 } 104 else -> { 105 throw IllegalArgumentException("unsupported record type!") 106 } 107 } 108 } 109 } 110 111 data class LoadDataEntriesInput( 112 val permissionType: HealthPermissionType, 113 val selectedDate: Instant 114 ) 115