DataPoints.kt

/*
 * Copyright (C) 2021 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.services.client.data

import android.content.Intent
import android.os.Bundle
import androidx.annotation.Keep
import java.time.Duration
import java.util.ArrayList

/** Helper class to facilitate working with [DataPoint] s. */
// TODO(b/177504986): Remove all @Keep annotations once we figure out why this class gets stripped
// away by proguard.
@Keep
public object DataPoints {
    /**
     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
     * this index represents the latitude.
     */
    public const val LOCATION_DATA_POINT_LATITUDE_INDEX: Int = 0

    /**
     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
     * this index represents the longitude.
     */
    public const val LOCATION_DATA_POINT_LONGITUDE_INDEX: Int = 1

    /**
     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
     * this index represents the altitude. This value will default to [Double.MAX_VALUE] if it is
     * not available.
     */
    public const val LOCATION_DATA_POINT_ALTITUDE_INDEX: Int = 2

    /**
     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
     * this index represents the bearing. This value will default to [Double.MAX_VALUE] if it is not
     * available.
     */
    public const val LOCATION_DATA_POINT_BEARING_INDEX: Int = 3

    /** Name of intent extra containing the data points set on pending intent. */
    private const val EXTRA_DATA_POINTS: String = "hs.data_points_list"

    /** Name of intent extra containing whether permissions are granted or not. */
    private const val EXTRA_PERMISSIONS_GRANTED: String = "hs.data_points_has_permissions"

    /** Retrieves the [DataPoint] s that are contained in the given [Intent], if any. */
    @JvmStatic
    @Keep
    public fun getDataPoints(intent: Intent): List<DataPoint> =
        intent.getParcelableArrayListExtra(EXTRA_DATA_POINTS) ?: listOf()

    /** Puts the given [DataPoint] s in the given [Intent]. */
    @JvmStatic
    public fun putDataPoints(intent: Intent, dataPoints: Collection<DataPoint>) {
        val copy = ArrayList(dataPoints)
        intent.putParcelableArrayListExtra(EXTRA_DATA_POINTS, copy)
    }

    /** Sets whether [DataPoint] permissions are `granted` in the given [Intent]. */
    @JvmStatic
    public fun putPermissionsGranted(intent: Intent, granted: Boolean) {
        intent.putExtra(EXTRA_PERMISSIONS_GRANTED, granted)
    }

    /** Retrieves whether permissions are granted in this [Intent]. */
    @JvmStatic
    public fun getPermissionsGranted(intent: Intent): Boolean =
        intent.getBooleanExtra(EXTRA_PERMISSIONS_GRANTED, true)

    /** Creates a new [DataPoint] of type [DataType.STEPS] with the given `steps`. */
    @JvmStatic
    @JvmOverloads
    public fun steps(
        steps: Long,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createInterval(
            DataType.STEPS,
            Value.ofLong(steps),
            startDurationFromBoot,
            endDurationFromBoot,
            metadata ?: Bundle()
        )

    /**
     * Creates a new [DataPoint] of type [DataType.STEPS_PER_MINUTE] with the given
     * `stepsPerMinute`.
     */
    @JvmStatic
    public fun stepsPerMinute(stepsPerMinute: Long, startDurationFromBoot: Duration): DataPoint =
        DataPoint.createSample(
            DataType.STEPS_PER_MINUTE,
            Value.ofLong(stepsPerMinute),
            startDurationFromBoot
        )

    /** Creates a new [DataPoint] of type [DataType.DISTANCE] with the given `meters`. */
    @JvmStatic
    @JvmOverloads
    public fun distance(
        meters: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createInterval(
            DataType.DISTANCE,
            Value.ofDouble(meters),
            startDurationFromBoot,
            endDurationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.ELEVATION_GAIN] with the given `meters`. */
    @JvmStatic
    @JvmOverloads
    public fun elevationGain(
        meters: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createInterval(
            DataType.ELEVATION_GAIN,
            Value.ofDouble(meters),
            startDurationFromBoot,
            endDurationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.ABSOLUTE_ELEVATION] with the given `meters`. */
    @JvmStatic
    @JvmOverloads
    public fun absoluteElevation(
        meters: Double,
        durationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createSample(
            DataType.ABSOLUTE_ELEVATION,
            Value.ofDouble(meters),
            durationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.FLOORS] with the given `floors`. */
    @JvmStatic
    @JvmOverloads
    public fun floors(
        floors: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createInterval(
            DataType.FLOORS,
            Value.ofDouble(floors),
            startDurationFromBoot,
            endDurationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.TOTAL_CALORIES] with the given `kcalories`. */
    @JvmStatic
    @JvmOverloads
    public fun calories(
        kcalories: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createInterval(
            DataType.TOTAL_CALORIES,
            Value.ofDouble(kcalories),
            startDurationFromBoot,
            endDurationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.SWIMMING_STROKES] with the given `strokes`. */
    @JvmStatic
    public fun swimmingStrokes(
        strokes: Long,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration
    ): DataPoint =
        DataPoint.createInterval(
            DataType.SWIMMING_STROKES,
            Value.ofLong(strokes),
            startDurationFromBoot,
            endDurationFromBoot
        )

    /**
     * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude`, `longitude`,
     * `altitude`, `bearing`, and `accuracy`.
     */
    @JvmStatic
    @JvmOverloads
    public fun location(
        latitude: Double,
        longitude: Double,
        altitude: Double = Double.MAX_VALUE,
        bearing: Double = Double.MAX_VALUE,
        durationFromBoot: Duration,
        accuracy: LocationAccuracy? = null
    ): DataPoint =
        DataPoint.createSample(
            DataType.LOCATION,
            Value.ofDoubleArray(latitude, longitude, altitude, bearing),
            durationFromBoot,
            accuracy = accuracy
        )

    /** Creates a new [DataPoint] of type [DataType.SPEED] with the given `metersPerSecond`. */
    @JvmStatic
    @JvmOverloads
    public fun speed(
        metersPerSecond: Double,
        durationFromBoot: Duration,
        metadata: Bundle? = null
    ): DataPoint =
        DataPoint.createSample(
            DataType.SPEED,
            Value.ofDouble(metersPerSecond),
            durationFromBoot,
            metadata ?: Bundle()
        )

    /** Creates a new [DataPoint] of type [DataType.PACE] with the given `millisPerKm`. */
    @JvmStatic
    public fun pace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
        DataPoint.createSample(DataType.PACE, Value.ofDouble(millisPerKm), durationFromBoot)

    /**
     * Creates a new [DataPoint] of type [DataType.HEART_RATE_BPM] with the given `bpm` and
     * `accuracy`.
     */
    @JvmStatic
    @JvmOverloads
    public fun heartRate(
        bpm: Double,
        durationFromBoot: Duration,
        accuracy: HrAccuracy? = null
    ): DataPoint =
        DataPoint.createSample(
            DataType.HEART_RATE_BPM,
            Value.ofDouble(bpm),
            durationFromBoot,
            accuracy = accuracy
        )

    /** Creates a new [DataPoint] of type [DataType.DAILY_STEPS] with the given `steps`. */
    @JvmStatic
    public fun dailySteps(
        steps: Long,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration
    ): DataPoint =
        DataPoint.createInterval(
            DataType.DAILY_STEPS,
            Value.ofLong(steps),
            startDurationFromBoot,
            endDurationFromBoot
        )

    /** Creates a new [DataPoint] of type [DataType.DAILY_FLOORS] with the given `floors`. */
    @JvmStatic
    public fun dailyFloors(
        floors: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration
    ): DataPoint =
        DataPoint.createInterval(
            DataType.DAILY_FLOORS,
            Value.ofDouble(floors),
            startDurationFromBoot,
            endDurationFromBoot
        )

    /** Creates a new [DataPoint] of type [DataType.DAILY_CALORIES] with the given `calories`. */
    @JvmStatic
    public fun dailyCalories(
        calories: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration
    ): DataPoint =
        DataPoint.createInterval(
            DataType.DAILY_CALORIES,
            Value.ofDouble(calories),
            startDurationFromBoot,
            endDurationFromBoot
        )

    /** Creates a new [DataPoint] of type [DataType.DAILY_DISTANCE] with the given `distance`. */
    @JvmStatic
    public fun dailyDistance(
        distance: Double,
        startDurationFromBoot: Duration,
        endDurationFromBoot: Duration
    ): DataPoint =
        DataPoint.createInterval(
            DataType.DAILY_DISTANCE,
            Value.ofDouble(distance),
            startDurationFromBoot,
            endDurationFromBoot
        )
}