diff options
Diffstat (limited to 'apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt')
-rw-r--r-- | apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt b/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt new file mode 100644 index 00000000..64232cb8 --- /dev/null +++ b/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.healthconnect.controller.datasources.api + +import android.health.connect.HealthConnectManager +import androidx.core.os.asOutcomeReceiver +import com.android.healthconnect.controller.data.entries.api.LoadDataEntriesInput +import com.android.healthconnect.controller.data.entries.api.LoadEntriesHelper +import com.android.healthconnect.controller.data.entries.datenavigation.DateNavigationPeriod +import com.android.healthconnect.controller.permissions.data.HealthPermissionType +import com.android.healthconnect.controller.permissiontypes.api.ILoadPriorityListUseCase +import com.android.healthconnect.controller.service.IoDispatcher +import com.android.healthconnect.controller.shared.HealthDataCategoryExtensions.fromHealthPermissionType +import com.android.healthconnect.controller.shared.HealthPermissionToDatatypeMapper +import com.android.healthconnect.controller.shared.usecase.UseCaseResults +import com.android.healthconnect.controller.utils.TimeSource +import com.android.healthconnect.controller.utils.toInstantAtStartOfDay +import com.android.healthconnect.controller.utils.toLocalDate +import com.google.common.collect.Comparators.max +import java.time.LocalDate +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext + +@Singleton +class LoadLastDateWithPriorityDataUseCase +@Inject +constructor( + private val healthConnectManager: HealthConnectManager, + private val loadEntriesHelper: LoadEntriesHelper, + private val loadPriorityListUseCase: ILoadPriorityListUseCase, + private val timeSource: TimeSource, + @IoDispatcher private val dispatcher: CoroutineDispatcher +) : ILoadLastDateWithPriorityDataUseCase { + + /** + * Returns the last local date with data for this health permission type, from the data owned by + * apps on the priority list. + */ + override suspend fun invoke( + healthPermissionType: HealthPermissionType + ): UseCaseResults<LocalDate?> = + withContext(dispatcher) { + var latestDateWithData: LocalDate? = null + try { + when (val priorityAppsResult = + loadPriorityListUseCase.invoke( + fromHealthPermissionType(healthPermissionType))) { + is UseCaseResults.Success -> { + val priorityApps = priorityAppsResult.data + + priorityApps.forEach { priorityApp -> + val lastDateWithDataForApp = + loadLastDateWithDataForApp( + healthPermissionType, priorityApp.packageName) + + latestDateWithData = + maxDateOrNull(latestDateWithData, lastDateWithDataForApp) + } + } + is UseCaseResults.Failed -> { + return@withContext UseCaseResults.Failed(priorityAppsResult.exception) + } + } + + return@withContext UseCaseResults.Success(latestDateWithData) + } catch (e: Exception) { + UseCaseResults.Failed(e) + } + } + + /** + * Returns the last date with data from a particular packageName, or null if no such date + * exists. + * + * To avoid querying all entries of all time, we first query for the activity dates for this + * healthPermissionType. We sort the dates in descending order and we find the first date which + * contains data from this packageName. + */ + private suspend fun loadLastDateWithDataForApp( + healthPermissionType: HealthPermissionType, + packageName: String + ): LocalDate? { + + val recordTypes = HealthPermissionToDatatypeMapper.getDataTypes(healthPermissionType) + + val datesWithData = suspendCancellableCoroutine { continuation -> + healthConnectManager.queryActivityDates( + recordTypes, Runnable::run, continuation.asOutcomeReceiver()) + } + + val today = timeSource.currentLocalDateTime().toLocalDate() + val recentDates = + datesWithData.filter { date -> + date.isAfter(today.minusMonths(1)) && !date.isAfter(today) + } + + if (recentDates.isEmpty()) return null + + val minDate = recentDates.min() + + // Query the data entries from this last month in one single API call + val input = + LoadDataEntriesInput( + permissionType = healthPermissionType, + packageName = packageName, + displayedStartTime = minDate.toInstantAtStartOfDay(), + period = DateNavigationPeriod.PERIOD_MONTH, + showDataOrigin = false) + + val entryRecords = loadEntriesHelper.readRecords(input) + + if (entryRecords.isNotEmpty()) { + // The records are returned in descending order by startTime + return loadEntriesHelper.getStartTime(entryRecords[0]).toLocalDate() + } + + return null + } + + private fun maxDateOrNull(firstDate: LocalDate?, secondDate: LocalDate?): LocalDate? { + if (firstDate == null && secondDate == null) return null + if (firstDate == null) return secondDate + if (secondDate == null) return firstDate + + return max(firstDate, secondDate) + } +} + +interface ILoadLastDateWithPriorityDataUseCase { + suspend fun invoke(healthPermissionType: HealthPermissionType): UseCaseResults<LocalDate?> +} |