1 /**
2  * 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.permissions.data
17 
18 import android.health.connect.HealthPermissions
19 
20 sealed class HealthPermission {
21 
22     companion object {
23         /** Special health permissions that don't represent health data types. */
24         private val additionalPermissions =
25             setOf(
26                 HealthPermissions.READ_EXERCISE_ROUTES,
27                 HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND,
28                 HealthPermissions.READ_HEALTH_DATA_HISTORY)
29 
30         /** Permissions that are grouped separately to general health data types */
31         private val medicalPermissions =
32             setOf(
33                 HealthPermissions.WRITE_MEDICAL_DATA,
34                 HealthPermissions.READ_MEDICAL_DATA_IMMUNIZATION)
35 
fromPermissionStringnull36         fun fromPermissionString(permission: String): HealthPermission {
37             return if (permission in additionalPermissions) {
38                 AdditionalPermission.fromPermissionString(permission)
39             } else if (permission in medicalPermissions) {
40                 MedicalPermission.fromPermissionString(permission)
41             } else {
42                 FitnessPermission.fromPermissionString(permission)
43             }
44         }
45     }
46 
47     /** Pair of {@link HealthPermissionType} and {@link PermissionsAccessType}. */
48     data class FitnessPermission(
49         val healthPermissionType: HealthPermissionType,
50         val permissionsAccessType: PermissionsAccessType
51     ) : HealthPermission() {
52         companion object {
53             private const val READ_PERMISSION_PREFIX = "android.permission.health.READ_"
54             private const val WRITE_PERMISSION_PREFIX = "android.permission.health.WRITE_"
55 
fromPermissionStringnull56             fun fromPermissionString(permission: String): FitnessPermission {
57                 return if (permission.startsWith(READ_PERMISSION_PREFIX)) {
58                     val type =
59                         getHealthPermissionType(permission.substring(READ_PERMISSION_PREFIX.length))
60                     FitnessPermission(type, PermissionsAccessType.READ)
61                 } else if (permission.startsWith(WRITE_PERMISSION_PREFIX)) {
62                     val type =
63                         getHealthPermissionType(
64                             permission.substring(WRITE_PERMISSION_PREFIX.length))
65                     FitnessPermission(type, PermissionsAccessType.WRITE)
66                 } else {
67                     throw IllegalArgumentException("Fitness permission not supported! $permission")
68                 }
69             }
70 
getHealthPermissionTypenull71             private fun getHealthPermissionType(value: String): HealthPermissionType {
72                 return HealthPermissionType.valueOf(value)
73             }
74         }
75 
toStringnull76         override fun toString(): String {
77             return if (permissionsAccessType == PermissionsAccessType.READ) {
78                 "$READ_PERMISSION_PREFIX${healthPermissionType.name}"
79             } else {
80                 "$WRITE_PERMISSION_PREFIX${healthPermissionType.name}"
81             }
82         }
83     }
84 
85     data class AdditionalPermission(val additionalPermission: String) : HealthPermission() {
86 
87         companion object {
fromPermissionStringnull88             fun fromPermissionString(permission: String): AdditionalPermission =
89                 AdditionalPermission(permission)
90 
91             // Predefined instances for convenience
92             val READ_HEALTH_DATA_HISTORY =
93                 AdditionalPermission(HealthPermissions.READ_HEALTH_DATA_HISTORY)
94             val READ_HEALTH_DATA_IN_BACKGROUND =
95                 AdditionalPermission(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND)
96             val READ_EXERCISE_ROUTES = AdditionalPermission(HealthPermissions.READ_EXERCISE_ROUTES)
97         }
98 
99         override fun toString(): String {
100             return additionalPermission
101         }
102 
isExerciseRoutesPermissionnull103         fun isExerciseRoutesPermission(): Boolean =
104             this.additionalPermission == HealthPermissions.READ_EXERCISE_ROUTES
105 
106         fun isBackgroundReadPermission(): Boolean =
107             this.additionalPermission == HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND
108 
109         fun isHistoryReadPermission(): Boolean =
110             this.additionalPermission == HealthPermissions.READ_HEALTH_DATA_HISTORY
111     }
112 
113     data class MedicalPermission(val medicalPermissionType: MedicalPermissionType) :
114         HealthPermission() {
115         companion object {
116             private const val WRITE_MEDICAL_DATA =
117                 "android.permission.health.WRITE_MEDICAL_DATA"
118             private const val READ_MEDICAL_DATA_PREFIX =
119                 "android.permission.health.READ_MEDICAL_DATA_"
120 
121             fun fromPermissionString(permission: String): MedicalPermission {
122                 return if (permission == WRITE_MEDICAL_DATA) {
123                     MedicalPermission(MedicalPermissionType.ALL_MEDICAL_DATA)
124                 } else if (permission.startsWith(READ_MEDICAL_DATA_PREFIX)) {
125                     val medicalType =
126                         getHealthPermissionType(
127                             permission.substring(READ_MEDICAL_DATA_PREFIX.length))
128                     MedicalPermission(medicalType)
129                 } else {
130                     throw IllegalArgumentException(" Medical permission not supported! $permission")
131                 }
132             }
133 
134             private fun getHealthPermissionType(value: String): MedicalPermissionType {
135                 return MedicalPermissionType.valueOf(value)
136             }
137         }
138 
139         override fun toString(): String {
140             return if (medicalPermissionType == MedicalPermissionType.ALL_MEDICAL_DATA) {
141                 WRITE_MEDICAL_DATA
142             } else {
143                 "$READ_MEDICAL_DATA_PREFIX${medicalPermissionType.name}"
144             }
145         }
146     }
147 }
148