• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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