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