HealthPermission.kt

/*
 * Copyright (C) 2022 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 androidx.health.connect.client.permission

import androidx.annotation.RestrictTo
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
import androidx.health.connect.client.records.BasalBodyTemperatureRecord
import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.BodyWaterMassRecord
import androidx.health.connect.client.records.BoneMassRecord
import androidx.health.connect.client.records.CervicalMucusRecord
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ElevationGainedRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.HydrationRecord
import androidx.health.connect.client.records.IntermenstrualBleedingRecord
import androidx.health.connect.client.records.LeanBodyMassRecord
import androidx.health.connect.client.records.MenstruationFlowRecord
import androidx.health.connect.client.records.MenstruationPeriodRecord
import androidx.health.connect.client.records.NutritionRecord
import androidx.health.connect.client.records.OvulationTestRecord
import androidx.health.connect.client.records.OxygenSaturationRecord
import androidx.health.connect.client.records.PowerRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.RespiratoryRateRecord
import androidx.health.connect.client.records.RestingHeartRateRecord
import androidx.health.connect.client.records.SexualActivityRecord
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.SleepStageRecord
import androidx.health.connect.client.records.SpeedRecord
import androidx.health.connect.client.records.StepsCadenceRecord
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.records.Vo2MaxRecord
import androidx.health.connect.client.records.WeightRecord
import androidx.health.connect.client.records.WheelchairPushesRecord
import kotlin.reflect.KClass

/**
 * A Permission either to read or write data associated with a [Record] type.
 *
 * @see androidx.health.connect.client.PermissionController
 */
public class HealthPermission
internal constructor(
    /** type of [Record] the permission gives access for. */
    internal val recordType: KClass<out Record>,
    /** whether read or write access. */
    @property:AccessType internal val accessType: Int,
) {
    companion object {
        /**
         * Creates [HealthPermission] to read provided [recordType], such as `Steps::class`.
         *
         * @return Permission object to use with
         *   [androidx.health.connect.client.PermissionController].
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY) // To be deleted.
        @JvmStatic
        public fun createReadPermissionLegacy(recordType: KClass<out Record>): HealthPermission {
            return HealthPermission(recordType, AccessTypes.READ)
        }

        /**
         * Returns a permission defined in [HealthPermission] to read provided [recordType], such as
         * `Steps::class`.
         *
         * @return Permission to use with [androidx.health.connect.client.PermissionController].
         * @throws IllegalArgumentException if the given record type is invalid.
         */
        @JvmStatic
        public fun getReadPermission(recordType: KClass<out Record>): String {
            if (RECORD_TYPE_TO_PERMISSION[recordType] == null) {
                throw IllegalArgumentException(
                    "Given recordType is not valid : $recordType.simpleName"
                )
            }
            return READ_PERMISSION_PREFIX + RECORD_TYPE_TO_PERMISSION[recordType]
        }

        /**
         * Creates [HealthPermission] to write provided [recordType], such as `Steps::class`.
         *
         * @return Permission to use with [androidx.health.connect.client.PermissionController].
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY) // To be deleted.
        @JvmStatic
        public fun createWritePermissionLegacy(recordType: KClass<out Record>): HealthPermission {
            return HealthPermission(recordType, AccessTypes.WRITE)
        }

        /**
         * Returns a permission defined in [HealthPermission] to read provided [recordType], such as
         * `Steps::class`.
         *
         * @return Permission object to use with
         *   [androidx.health.connect.client.PermissionController].
         * @throws IllegalArgumentException if the given record type is invalid.
         */
        @JvmStatic
        public fun getWritePermission(recordType: KClass<out Record>): String {
            if (RECORD_TYPE_TO_PERMISSION[recordType] == null) {
                throw IllegalArgumentException(
                    "Given recordType is not valid : $recordType.simpleName"
                )
            }
            return WRITE_PERMISSION_PREFIX + RECORD_TYPE_TO_PERMISSION.getOrDefault(recordType, "")
        }

        internal const val PERMISSION_PREFIX = "android.permission.health."

        /**
         * A permission to write exercise routes.
         *
         * This permission must be granted to successfully insert a route as a field of the
         * corresponding [androidx.health.connect.client.records.ExerciseSessionRecord]. An attempt
         * to insert/update a session with a set route without the permission granted will result in
         * a failed call and the session insertion/update will be rejected.
         *
         * If the permission is not granted the previously written route will not be deleted if the
         * session gets updated with no route set.
         *
         * @sample androidx.health.connect.client.samples.InsertExerciseRoute
         */
        const val PERMISSION_WRITE_EXERCISE_ROUTE = PERMISSION_PREFIX + "WRITE_EXERCISE_ROUTE"

        /**
         * A permission to read data in background.
         *
         * An attempt to read data in background without this permission may result
         * in an error.
         *
         * @sample androidx.health.connect.client.samples.RequestBackgroundReadPermission
         * @sample androidx.health.connect.client.samples.ReadRecordsInBackground
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY) // Hidden for now
        const val PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND =
            PERMISSION_PREFIX + "READ_HEALTH_DATA_IN_BACKGROUND"

        // Read permissions for ACTIVITY.
        internal const val READ_ACTIVE_CALORIES_BURNED =
            PERMISSION_PREFIX + "READ_ACTIVE_CALORIES_BURNED"
        internal const val READ_DISTANCE = PERMISSION_PREFIX + "READ_DISTANCE"
        internal const val READ_ELEVATION_GAINED = PERMISSION_PREFIX + "READ_ELEVATION_GAINED"
        internal const val READ_EXERCISE = PERMISSION_PREFIX + "READ_EXERCISE"
        internal const val READ_FLOORS_CLIMBED = PERMISSION_PREFIX + "READ_FLOORS_CLIMBED"
        internal const val READ_STEPS = PERMISSION_PREFIX + "READ_STEPS"
        internal const val READ_TOTAL_CALORIES_BURNED =
            PERMISSION_PREFIX + "READ_TOTAL_CALORIES_BURNED"
        internal const val READ_VO2_MAX = PERMISSION_PREFIX + "READ_VO2_MAX"
        internal const val READ_WHEELCHAIR_PUSHES = PERMISSION_PREFIX + "READ_WHEELCHAIR_PUSHES"
        internal const val READ_POWER = PERMISSION_PREFIX + "READ_POWER"
        internal const val READ_SPEED = PERMISSION_PREFIX + "READ_SPEED"

        // Read permissions for BODY_MEASUREMENTS.
        internal const val READ_BASAL_METABOLIC_RATE =
            PERMISSION_PREFIX + "READ_BASAL_METABOLIC_RATE"
        internal const val READ_BODY_FAT = PERMISSION_PREFIX + "READ_BODY_FAT"
        internal const val READ_BODY_WATER_MASS = PERMISSION_PREFIX + "READ_BODY_WATER_MASS"
        internal const val READ_BONE_MASS = PERMISSION_PREFIX + "READ_BONE_MASS"
        internal const val READ_HEIGHT = PERMISSION_PREFIX + "READ_HEIGHT"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val READ_HIP_CIRCUMFERENCE = PERMISSION_PREFIX + "READ_HIP_CIRCUMFERENCE"
        internal const val READ_LEAN_BODY_MASS = PERMISSION_PREFIX + "READ_LEAN_BODY_MASS"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val READ_WAIST_CIRCUMFERENCE = PERMISSION_PREFIX + "READ_WAIST_CIRCUMFERENCE"
        internal const val READ_WEIGHT = PERMISSION_PREFIX + "READ_WEIGHT"

        // Read permissions for CYCLE_TRACKING.
        internal const val READ_CERVICAL_MUCUS = PERMISSION_PREFIX + "READ_CERVICAL_MUCUS"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val READ_INTERMENSTRUAL_BLEEDING =
            PERMISSION_PREFIX + "READ_INTERMENSTRUAL_BLEEDING"
        internal const val READ_MENSTRUATION = PERMISSION_PREFIX + "READ_MENSTRUATION"
        internal const val READ_OVULATION_TEST = PERMISSION_PREFIX + "READ_OVULATION_TEST"
        internal const val READ_SEXUAL_ACTIVITY = PERMISSION_PREFIX + "READ_SEXUAL_ACTIVITY"

        // Read permissions for NUTRITION.
        internal const val READ_HYDRATION = PERMISSION_PREFIX + "READ_HYDRATION"
        internal const val READ_NUTRITION = PERMISSION_PREFIX + "READ_NUTRITION"

        // Read permissions for SLEEP.
        internal const val READ_SLEEP = PERMISSION_PREFIX + "READ_SLEEP"

        // Read permissions for VITALS.
        internal const val READ_BASAL_BODY_TEMPERATURE =
            PERMISSION_PREFIX + "READ_BASAL_BODY_TEMPERATURE"
        internal const val READ_BLOOD_GLUCOSE = PERMISSION_PREFIX + "READ_BLOOD_GLUCOSE"
        internal const val READ_BLOOD_PRESSURE = PERMISSION_PREFIX + "READ_BLOOD_PRESSURE"
        internal const val READ_BODY_TEMPERATURE = PERMISSION_PREFIX + "READ_BODY_TEMPERATURE"
        internal const val READ_HEART_RATE = PERMISSION_PREFIX + "READ_HEART_RATE"
        internal const val READ_HEART_RATE_VARIABILITY =
            PERMISSION_PREFIX + "READ_HEART_RATE_VARIABILITY"
        internal const val READ_OXYGEN_SATURATION = PERMISSION_PREFIX + "READ_OXYGEN_SATURATION"
        internal const val READ_RESPIRATORY_RATE = PERMISSION_PREFIX + "READ_RESPIRATORY_RATE"
        internal const val READ_RESTING_HEART_RATE = PERMISSION_PREFIX + "READ_RESTING_HEART_RATE"

        // Write permissions for ACTIVITY.
        internal const val WRITE_ACTIVE_CALORIES_BURNED =
            PERMISSION_PREFIX + "WRITE_ACTIVE_CALORIES_BURNED"
        internal const val WRITE_DISTANCE = PERMISSION_PREFIX + "WRITE_DISTANCE"
        internal const val WRITE_ELEVATION_GAINED = PERMISSION_PREFIX + "WRITE_ELEVATION_GAINED"
        internal const val WRITE_EXERCISE = PERMISSION_PREFIX + "WRITE_EXERCISE"
        internal const val WRITE_FLOORS_CLIMBED = PERMISSION_PREFIX + "WRITE_FLOORS_CLIMBED"
        internal const val WRITE_STEPS = PERMISSION_PREFIX + "WRITE_STEPS"
        internal const val WRITE_TOTAL_CALORIES_BURNED =
            PERMISSION_PREFIX + "WRITE_TOTAL_CALORIES_BURNED"
        internal const val WRITE_VO2_MAX = PERMISSION_PREFIX + "WRITE_VO2_MAX"
        internal const val WRITE_WHEELCHAIR_PUSHES = PERMISSION_PREFIX + "WRITE_WHEELCHAIR_PUSHES"
        internal const val WRITE_POWER = PERMISSION_PREFIX + "WRITE_POWER"
        internal const val WRITE_SPEED = PERMISSION_PREFIX + "WRITE_SPEED"

        // Write permissions for BODY_MEASUREMENTS.
        internal const val WRITE_BASAL_METABOLIC_RATE =
            PERMISSION_PREFIX + "WRITE_BASAL_METABOLIC_RATE"
        internal const val WRITE_BODY_FAT = PERMISSION_PREFIX + "WRITE_BODY_FAT"
        internal const val WRITE_BODY_WATER_MASS = PERMISSION_PREFIX + "WRITE_BODY_WATER_MASS"
        internal const val WRITE_BONE_MASS = PERMISSION_PREFIX + "WRITE_BONE_MASS"
        internal const val WRITE_HEIGHT = PERMISSION_PREFIX + "WRITE_HEIGHT"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val WRITE_HIP_CIRCUMFERENCE = PERMISSION_PREFIX + "WRITE_HIP_CIRCUMFERENCE"
        internal const val WRITE_LEAN_BODY_MASS = PERMISSION_PREFIX + "WRITE_LEAN_BODY_MASS"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val WRITE_WAIST_CIRCUMFERENCE =
            PERMISSION_PREFIX + "WRITE_WAIST_CIRCUMFERENCE"
        internal const val WRITE_WEIGHT = PERMISSION_PREFIX + "WRITE_WEIGHT"

        // Write permissions for CYCLE_TRACKING.
        internal const val WRITE_CERVICAL_MUCUS = PERMISSION_PREFIX + "WRITE_CERVICAL_MUCUS"

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal const val WRITE_INTERMENSTRUAL_BLEEDING =
            PERMISSION_PREFIX + "WRITE_INTERMENSTRUAL_BLEEDING"
        internal const val WRITE_MENSTRUATION = PERMISSION_PREFIX + "WRITE_MENSTRUATION"
        internal const val WRITE_OVULATION_TEST = PERMISSION_PREFIX + "WRITE_OVULATION_TEST"
        internal const val WRITE_SEXUAL_ACTIVITY = PERMISSION_PREFIX + "WRITE_SEXUAL_ACTIVITY"

        // Write permissions for NUTRITION.
        internal const val WRITE_HYDRATION = PERMISSION_PREFIX + "WRITE_HYDRATION"
        internal const val WRITE_NUTRITION = PERMISSION_PREFIX + "WRITE_NUTRITION"

        // Write permissions for SLEEP.
        internal const val WRITE_SLEEP = PERMISSION_PREFIX + "WRITE_SLEEP"

        // Write permissions for VITALS.
        internal const val WRITE_BASAL_BODY_TEMPERATURE =
            PERMISSION_PREFIX + "WRITE_BASAL_BODY_TEMPERATURE"
        internal const val WRITE_BLOOD_GLUCOSE = PERMISSION_PREFIX + "WRITE_BLOOD_GLUCOSE"
        internal const val WRITE_BLOOD_PRESSURE = PERMISSION_PREFIX + "WRITE_BLOOD_PRESSURE"
        internal const val WRITE_BODY_TEMPERATURE = PERMISSION_PREFIX + "WRITE_BODY_TEMPERATURE"
        internal const val WRITE_HEART_RATE = PERMISSION_PREFIX + "WRITE_HEART_RATE"
        internal const val WRITE_HEART_RATE_VARIABILITY =
            PERMISSION_PREFIX + "WRITE_HEART_RATE_VARIABILITY"
        internal const val WRITE_OXYGEN_SATURATION = PERMISSION_PREFIX + "WRITE_OXYGEN_SATURATION"
        internal const val WRITE_RESPIRATORY_RATE = PERMISSION_PREFIX + "WRITE_RESPIRATORY_RATE"
        internal const val WRITE_RESTING_HEART_RATE = PERMISSION_PREFIX + "WRITE_RESTING_HEART_RATE"

        internal const val READ_PERMISSION_PREFIX = PERMISSION_PREFIX + "READ_"
        internal const val WRITE_PERMISSION_PREFIX = PERMISSION_PREFIX + "WRITE_"

        internal val RECORD_TYPE_TO_PERMISSION =
            mapOf<KClass<out Record>, String>(
                ActiveCaloriesBurnedRecord::class to
                    READ_ACTIVE_CALORIES_BURNED.substringAfter(READ_PERMISSION_PREFIX),
                BasalBodyTemperatureRecord::class to
                    READ_BASAL_BODY_TEMPERATURE.substringAfter(READ_PERMISSION_PREFIX),
                BasalMetabolicRateRecord::class to
                    READ_BASAL_METABOLIC_RATE.substringAfter(READ_PERMISSION_PREFIX),
                BloodGlucoseRecord::class to
                    READ_BLOOD_GLUCOSE.substringAfter(READ_PERMISSION_PREFIX),
                BloodPressureRecord::class to
                    READ_BLOOD_PRESSURE.substringAfter(READ_PERMISSION_PREFIX),
                BodyFatRecord::class to READ_BODY_FAT.substringAfter(READ_PERMISSION_PREFIX),
                BodyTemperatureRecord::class to
                    READ_BODY_TEMPERATURE.substringAfter(READ_PERMISSION_PREFIX),
                BodyWaterMassRecord::class to
                    READ_BODY_WATER_MASS.substringAfter(READ_PERMISSION_PREFIX),
                BoneMassRecord::class to READ_BONE_MASS.substringAfter(READ_PERMISSION_PREFIX),
                CervicalMucusRecord::class to
                    READ_CERVICAL_MUCUS.substringAfter(READ_PERMISSION_PREFIX),
                CyclingPedalingCadenceRecord::class to
                    READ_EXERCISE.substringAfter(READ_PERMISSION_PREFIX),
                DistanceRecord::class to READ_DISTANCE.substringAfter(READ_PERMISSION_PREFIX),
                ElevationGainedRecord::class to
                    READ_ELEVATION_GAINED.substringAfter(READ_PERMISSION_PREFIX),
                ExerciseSessionRecord::class to
                    READ_EXERCISE.substringAfter(READ_PERMISSION_PREFIX),
                FloorsClimbedRecord::class to
                    READ_FLOORS_CLIMBED.substringAfter(READ_PERMISSION_PREFIX),
                HeartRateRecord::class to READ_HEART_RATE.substringAfter(READ_PERMISSION_PREFIX),
                HeartRateVariabilityRmssdRecord::class to
                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
                HeightRecord::class to READ_HEIGHT.substringAfter(READ_PERMISSION_PREFIX),
                HydrationRecord::class to READ_HYDRATION.substringAfter(READ_PERMISSION_PREFIX),
                IntermenstrualBleedingRecord::class to
                    READ_INTERMENSTRUAL_BLEEDING.substringAfter(READ_PERMISSION_PREFIX),
                LeanBodyMassRecord::class to
                    READ_LEAN_BODY_MASS.substringAfter(READ_PERMISSION_PREFIX),
                MenstruationFlowRecord::class to
                    READ_MENSTRUATION.substringAfter(READ_PERMISSION_PREFIX),
                MenstruationPeriodRecord::class to
                    READ_MENSTRUATION.substringAfter(READ_PERMISSION_PREFIX),
                NutritionRecord::class to READ_NUTRITION.substringAfter(READ_PERMISSION_PREFIX),
                OvulationTestRecord::class to
                    READ_OVULATION_TEST.substringAfter(READ_PERMISSION_PREFIX),
                OxygenSaturationRecord::class to
                    READ_OXYGEN_SATURATION.substringAfter(READ_PERMISSION_PREFIX),
                PowerRecord::class to READ_POWER.substringAfter(READ_PERMISSION_PREFIX),
                RespiratoryRateRecord::class to
                    READ_RESPIRATORY_RATE.substringAfter(READ_PERMISSION_PREFIX),
                RestingHeartRateRecord::class to
                    READ_RESTING_HEART_RATE.substringAfter(READ_PERMISSION_PREFIX),
                SexualActivityRecord::class to
                    READ_SEXUAL_ACTIVITY.substringAfter(READ_PERMISSION_PREFIX),
                SleepSessionRecord::class to READ_SLEEP.substringAfter(READ_PERMISSION_PREFIX),
                SleepStageRecord::class to READ_SLEEP.substringAfter(READ_PERMISSION_PREFIX),
                SpeedRecord::class to READ_SPEED.substringAfter(READ_PERMISSION_PREFIX),
                StepsCadenceRecord::class to READ_STEPS.substringAfter(READ_PERMISSION_PREFIX),
                StepsRecord::class to READ_STEPS.substringAfter(READ_PERMISSION_PREFIX),
                TotalCaloriesBurnedRecord::class to
                    READ_TOTAL_CALORIES_BURNED.substringAfter(READ_PERMISSION_PREFIX),
                Vo2MaxRecord::class to READ_VO2_MAX.substringAfter(READ_PERMISSION_PREFIX),
                WeightRecord::class to READ_WEIGHT.substringAfter(READ_PERMISSION_PREFIX),
                WheelchairPushesRecord::class to
                    READ_WHEELCHAIR_PUSHES.substringAfter(READ_PERMISSION_PREFIX),
            )
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is HealthPermission) return false

        if (recordType != other.recordType) return false
        if (accessType != other.accessType) return false

        return true
    }

    override fun hashCode(): Int {
        var result = recordType.hashCode()
        result = 31 * result + accessType
        return result
    }
}