• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.shared
17 
18 import android.content.Context
19 import android.graphics.drawable.Drawable
20 import android.health.connect.HealthDataCategory
21 import android.health.connect.internal.datatypes.utils.HealthConnectMappings
22 import androidx.annotation.StringRes
23 import com.android.healthconnect.controller.R
24 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType
25 import com.android.healthconnect.controller.permissions.data.HealthPermissionType
26 import com.android.healthconnect.controller.permissions.data.MedicalPermissionType
27 import com.android.healthconnect.controller.permissions.data.fromHealthPermissionCategory
28 import com.android.healthconnect.controller.shared.CategoriesMappers.ACTIVITY_PERMISSION_GROUPS
29 import com.android.healthconnect.controller.shared.CategoriesMappers.BODY_MEASUREMENTS_PERMISSION_GROUPS
30 import com.android.healthconnect.controller.shared.CategoriesMappers.CYCLE_TRACKING_PERMISSION_GROUPS
31 import com.android.healthconnect.controller.shared.CategoriesMappers.NUTRITION_PERMISSION_GROUPS
32 import com.android.healthconnect.controller.shared.CategoriesMappers.SLEEP_PERMISSION_GROUPS
33 import com.android.healthconnect.controller.shared.CategoriesMappers.VITALS_PERMISSION_GROUPS
34 import com.android.healthconnect.controller.shared.CategoriesMappers.WELLNESS_PERMISSION_GROUPS
35 import com.android.healthconnect.controller.utils.AttributeResolver
36 import com.android.healthfitness.flags.Flags
37 
38 object HealthDataCategoryExtensions {
39     /** Additional category for medical permission types. */
40     const val MEDICAL = 1000
41 
42     private val DATA_CATEGORY_TO_HEALTH_PERMISSION_TYPE_MAP =
43         createDataCategoryToHealthPermissionTypeMap()
44 
45     private fun createDataCategoryToHealthPermissionTypeMap():
46         Map<Int, List<HealthPermissionType>> {
47 
48         if (!Flags.healthConnectMappings()) {
49             return emptyMap()
50         }
51 
52         val specialCases =
53             mapOf(
54                 HealthDataCategory.ACTIVITY to listOf(FitnessPermissionType.EXERCISE_ROUTE),
55                 MEDICAL to MedicalPermissionType.entries,
56             )
57 
58         val healthConnectMappings = HealthConnectMappings.getInstance()
59 
60         return healthConnectMappings.allRecordTypeIdentifiers
61             .map { recordTypeId ->
62                 healthConnectMappings.getRecordCategoryForRecordType(recordTypeId) to
63                     healthConnectMappings.getHealthPermissionCategoryForRecordType(recordTypeId)
64             }
65             .groupBy({ it.first }, { fromHealthPermissionCategory(it.second) })
66             .toMutableMap()
67             .apply { specialCases.forEach { merge(it.key, it.value) { a, b -> a + b } } }
68             .mapValues { it.value.distinct() }
69             .toMap()
70     }
71 
72     fun @receiver:HealthDataCategoryInt Int.healthPermissionTypes(): List<HealthPermissionType> {
73         if (!Flags.healthConnectMappings()) {
74             return this.healthPermissionTypesLegacy()
75         }
76 
77         return DATA_CATEGORY_TO_HEALTH_PERMISSION_TYPE_MAP[this]
78             ?: throw IllegalArgumentException("Category $this is not supported.")
79     }
80 
81     private fun @receiver:HealthDataCategoryInt Int.healthPermissionTypesLegacy():
82         List<HealthPermissionType> {
83         return when (this) {
84             HealthDataCategory.ACTIVITY -> ACTIVITY_PERMISSION_GROUPS
85             HealthDataCategory.BODY_MEASUREMENTS -> BODY_MEASUREMENTS_PERMISSION_GROUPS
86             HealthDataCategory.CYCLE_TRACKING -> CYCLE_TRACKING_PERMISSION_GROUPS
87             HealthDataCategory.NUTRITION -> NUTRITION_PERMISSION_GROUPS
88             HealthDataCategory.SLEEP -> SLEEP_PERMISSION_GROUPS
89             HealthDataCategory.VITALS -> VITALS_PERMISSION_GROUPS
90             HealthDataCategory.WELLNESS -> WELLNESS_PERMISSION_GROUPS
91             MEDICAL -> MedicalPermissionType.entries
92             else -> throw IllegalArgumentException("Category $this is not supported.")
93         }
94     }
95 
96     @StringRes
97     fun @receiver:HealthDataCategoryInt Int.lowercaseTitle(): Int {
98         return when (this) {
99             HealthDataCategory.ACTIVITY -> R.string.activity_category_lowercase
100             HealthDataCategory.BODY_MEASUREMENTS -> R.string.body_measurements_category_lowercase
101             HealthDataCategory.CYCLE_TRACKING -> R.string.cycle_tracking_category_lowercase
102             HealthDataCategory.NUTRITION -> R.string.nutrition_category_lowercase
103             HealthDataCategory.SLEEP -> R.string.sleep_category_lowercase
104             HealthDataCategory.VITALS -> R.string.vitals_category_lowercase
105             HealthDataCategory.WELLNESS -> R.string.wellness_category_lowercase
106             MEDICAL -> R.string.medical_permissions_lowercase
107             else -> throw IllegalArgumentException("Category $this is not supported.")
108         }
109     }
110 
111     @StringRes
112     fun @receiver:HealthDataCategoryInt Int.uppercaseTitle(): Int {
113         return when (this) {
114             HealthDataCategory.ACTIVITY -> R.string.activity_category_uppercase
115             HealthDataCategory.BODY_MEASUREMENTS -> R.string.body_measurements_category_uppercase
116             HealthDataCategory.CYCLE_TRACKING -> R.string.cycle_tracking_category_uppercase
117             HealthDataCategory.NUTRITION -> R.string.nutrition_category_uppercase
118             HealthDataCategory.SLEEP -> R.string.sleep_category_uppercase
119             HealthDataCategory.VITALS -> R.string.vitals_category_uppercase
120             HealthDataCategory.WELLNESS -> R.string.wellness_category_uppercase
121             MEDICAL -> R.string.medical_permissions
122             else -> throw IllegalArgumentException("Category $this is not supported.")
123         }
124     }
125 
126     fun @receiver:HealthDataCategoryInt Int.icon(context: Context): Drawable? {
127         val attrRes: Int =
128             when (this) {
129                 HealthDataCategory.ACTIVITY -> R.attr.activityCategoryIcon
130                 HealthDataCategory.BODY_MEASUREMENTS -> R.attr.bodyMeasurementsCategoryIcon
131                 HealthDataCategory.CYCLE_TRACKING -> R.attr.cycleTrackingCategoryIcon
132                 HealthDataCategory.NUTRITION -> R.attr.nutritionCategoryIcon
133                 HealthDataCategory.SLEEP -> R.attr.sleepCategoryIcon
134                 HealthDataCategory.VITALS -> R.attr.vitalsCategoryIcon
135                 HealthDataCategory.WELLNESS -> R.attr.wellnessCategoryIcon
136                 // TODO(b/342156345): Add default medical icon.
137                 MEDICAL -> R.attr.vitalsCategoryIcon
138                 else -> throw IllegalArgumentException("Category $this is not supported.")
139             }
140         return AttributeResolver.getDrawable(context, attrRes)
141     }
142 
143     @HealthDataCategoryInt
144     fun fromFitnessPermissionType(type: FitnessPermissionType): Int {
145         val result = safelyFromFitnessPermissionType(type)
146         return result
147             ?: throw IllegalArgumentException("No Category for fitness permission type $type")
148     }
149 
150     @HealthDataCategoryInt
151     fun safelyFromFitnessPermissionType(type: FitnessPermissionType): Int? {
152         return getAllFitnessDataCategories().firstOrNull {
153             it.healthPermissionTypes().contains(type)
154         }
155     }
156 }
157 
158 /** Permission groups for each {@link HealthDataCategory}. */
159 private object CategoriesMappers {
160     val ACTIVITY_PERMISSION_GROUPS =
161         listOf(
162             FitnessPermissionType.ACTIVE_CALORIES_BURNED,
163             FitnessPermissionType.DISTANCE,
164             FitnessPermissionType.ELEVATION_GAINED,
165             FitnessPermissionType.EXERCISE,
166             FitnessPermissionType.EXERCISE_ROUTE,
167             FitnessPermissionType.FLOORS_CLIMBED,
168             FitnessPermissionType.POWER,
169             FitnessPermissionType.SPEED,
170             FitnessPermissionType.STEPS,
171             FitnessPermissionType.TOTAL_CALORIES_BURNED,
172             FitnessPermissionType.VO2_MAX,
173             FitnessPermissionType.WHEELCHAIR_PUSHES,
174             FitnessPermissionType.PLANNED_EXERCISE,
175         )
176 
177     val BODY_MEASUREMENTS_PERMISSION_GROUPS =
178         listOf(
179             FitnessPermissionType.BASAL_METABOLIC_RATE,
180             FitnessPermissionType.BODY_FAT,
181             FitnessPermissionType.BODY_WATER_MASS,
182             FitnessPermissionType.BONE_MASS,
183             FitnessPermissionType.HEIGHT,
184             FitnessPermissionType.LEAN_BODY_MASS,
185             FitnessPermissionType.WEIGHT,
186         )
187 
188     val CYCLE_TRACKING_PERMISSION_GROUPS =
189         listOf(
190             FitnessPermissionType.CERVICAL_MUCUS,
191             FitnessPermissionType.INTERMENSTRUAL_BLEEDING,
192             FitnessPermissionType.MENSTRUATION,
193             FitnessPermissionType.OVULATION_TEST,
194             FitnessPermissionType.SEXUAL_ACTIVITY,
195         )
196 
197     val NUTRITION_PERMISSION_GROUPS =
198         listOf(FitnessPermissionType.HYDRATION, FitnessPermissionType.NUTRITION)
199 
200     val SLEEP_PERMISSION_GROUPS = listOf(FitnessPermissionType.SLEEP)
201 
202     val VITALS_PERMISSION_GROUPS =
203         listOf(
204             FitnessPermissionType.BASAL_BODY_TEMPERATURE,
205             FitnessPermissionType.BLOOD_GLUCOSE,
206             FitnessPermissionType.BLOOD_PRESSURE,
207             FitnessPermissionType.BODY_TEMPERATURE,
208             FitnessPermissionType.HEART_RATE,
209             FitnessPermissionType.HEART_RATE_VARIABILITY,
210             FitnessPermissionType.OXYGEN_SATURATION,
211             FitnessPermissionType.RESPIRATORY_RATE,
212             FitnessPermissionType.RESTING_HEART_RATE,
213             FitnessPermissionType.SKIN_TEMPERATURE,
214         )
215 
216     val WELLNESS_PERMISSION_GROUPS = listOf(FitnessPermissionType.MINDFULNESS)
217 }
218 
219 /** List of available Health data categories. */
220 val FITNESS_DATA_CATEGORIES = getAllFitnessDataCategories()
221 
222 /**
223  * List of available Health data categories.
224  *
225  * Allows code being unit tested with different flag values.
226  */
getAllFitnessDataCategoriesnull227 fun getAllFitnessDataCategories() =
228     if (Flags.healthConnectMappings()) HealthConnectMappings.getInstance().allHealthDataCategories
229     else getAllFitnessDataCategoriesLegacy()
230 
231 fun getAllFitnessDataCategoriesLegacy() =
232     listOf(
233         HealthDataCategory.ACTIVITY,
234         HealthDataCategory.BODY_MEASUREMENTS,
235         HealthDataCategory.CYCLE_TRACKING,
236         HealthDataCategory.NUTRITION,
237         HealthDataCategory.SLEEP,
238         HealthDataCategory.VITALS,
239         HealthDataCategory.WELLNESS,
240     )
241 
242 /** Denotes that the annotated [Integer] represents a [HealthDataCategory]. */
243 @Retention(AnnotationRetention.BINARY)
244 @Target(
245     AnnotationTarget.VALUE_PARAMETER,
246     AnnotationTarget.FUNCTION,
247     AnnotationTarget.PROPERTY_GETTER,
248     AnnotationTarget.PROPERTY_SETTER,
249     AnnotationTarget.LOCAL_VARIABLE,
250     AnnotationTarget.FIELD,
251     AnnotationTarget.TYPE,
252 )
253 annotation class HealthDataCategoryInt
254