1 /* <lambda>null2 * Copyright (C) 2023 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.data.appdata 17 18 import android.health.connect.HealthConnectManager 19 import android.health.connect.RecordTypeInfoResponse 20 import android.health.connect.datatypes.Record 21 import android.util.Log 22 import androidx.core.os.asOutcomeReceiver 23 import com.android.healthconnect.controller.permissions.data.HealthPermissionType 24 import com.android.healthconnect.controller.permissions.data.fromHealthPermissionCategory 25 import com.android.healthconnect.controller.service.IoDispatcher 26 import com.android.healthconnect.controller.shared.HEALTH_DATA_CATEGORIES 27 import com.android.healthconnect.controller.shared.HealthDataCategoryExtensions.healthPermissionTypes 28 import com.android.healthconnect.controller.shared.HealthDataCategoryInt 29 import com.android.healthconnect.controller.shared.usecase.UseCaseResults 30 import javax.inject.Inject 31 import javax.inject.Singleton 32 import kotlinx.coroutines.CoroutineDispatcher 33 import kotlinx.coroutines.suspendCancellableCoroutine 34 import kotlinx.coroutines.withContext 35 36 @Singleton 37 class AppDataUseCase 38 @Inject 39 constructor( 40 private val healthConnectManager: HealthConnectManager, 41 @IoDispatcher private val dispatcher: CoroutineDispatcher 42 ) { 43 44 /** Returns list of all health categories and permission types to be shown on the HC UI. */ 45 suspend fun loadAllData(): UseCaseResults<List<PermissionTypesPerCategory>> = 46 withContext(dispatcher) { 47 try { 48 val recordTypeInfoMap: Map<Class<out Record>, RecordTypeInfoResponse> = 49 suspendCancellableCoroutine { continuation -> 50 healthConnectManager.queryAllRecordTypesInfo( 51 Runnable::run, continuation.asOutcomeReceiver()) 52 } 53 val categories = 54 HEALTH_DATA_CATEGORIES.map { 55 PermissionTypesPerCategory( 56 it, 57 getPermissionTypesPerCategory( 58 it, recordTypeInfoMap, packageName = null)) 59 } 60 UseCaseResults.Success(categories) 61 } catch (e: Exception) { 62 Log.e("TAG_ERROR", "Loading error ", e) 63 UseCaseResults.Failed(e) 64 } 65 } 66 67 /** 68 * Returns list of health categories and permission types written by the given app to be shown 69 * on the HC UI. 70 */ 71 suspend fun loadAppData(packageName: String): UseCaseResults<List<PermissionTypesPerCategory>> = 72 withContext(dispatcher) { 73 try { 74 val recordTypeInfoMap: Map<Class<out Record>, RecordTypeInfoResponse> = 75 suspendCancellableCoroutine { continuation -> 76 healthConnectManager.queryAllRecordTypesInfo( 77 Runnable::run, continuation.asOutcomeReceiver()) 78 } 79 val categories = 80 HEALTH_DATA_CATEGORIES.map { 81 PermissionTypesPerCategory( 82 it, getPermissionTypesPerCategory(it, recordTypeInfoMap, packageName)) 83 } 84 UseCaseResults.Success(categories) 85 } catch (e: Exception) { 86 UseCaseResults.Failed(e) 87 } 88 } 89 90 /** 91 * Returns those [HealthPermissionType]s that have some data written by the given [packageName] 92 * app. If the is no app provided then return all data. 93 */ 94 private fun getPermissionTypesPerCategory( 95 category: @HealthDataCategoryInt Int, 96 recordTypeInfoMap: Map<Class<out Record>, RecordTypeInfoResponse>, 97 packageName: String? 98 ): List<HealthPermissionType> { 99 if (packageName == null) { 100 return category.healthPermissionTypes().filter { hasData(it, recordTypeInfoMap) } 101 } 102 return category.healthPermissionTypes().filter { 103 hasDataByApp(it, recordTypeInfoMap, packageName) 104 } 105 } 106 107 private fun hasData( 108 permissionType: HealthPermissionType, 109 recordTypeInfoMap: Map<Class<out Record>, RecordTypeInfoResponse>, 110 ): Boolean = 111 recordTypeInfoMap.values.firstOrNull { 112 fromHealthPermissionCategory(it.permissionCategory) == permissionType && 113 it.contributingPackages.isNotEmpty() 114 } != null 115 116 private fun hasDataByApp( 117 permissionType: HealthPermissionType, 118 recordTypeInfoMap: Map<Class<out Record>, RecordTypeInfoResponse>, 119 packageName: String 120 ): Boolean = 121 recordTypeInfoMap.values.firstOrNull { 122 fromHealthPermissionCategory(it.permissionCategory) == permissionType && 123 it.contributingPackages.isNotEmpty() && 124 it.contributingPackages 125 .map { contributingApp -> contributingApp.packageName } 126 .contains(packageName) 127 } != null 128 } 129 130 /** 131 * Represents Health Category group to be shown in health connect screens. 132 * 133 * @param category Category id 134 * @param data [HealthPermissionType]s within the category that have data written by given app. 135 */ 136 data class PermissionTypesPerCategory( 137 val category: @HealthDataCategoryInt Int, 138 val data: List<HealthPermissionType> 139 ) 140