/*
* 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.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import java.time.Duration
import java.time.Instant
/**
* Helper class to facilitate creating [DataPoint]s. In general, this should not be needed outside
* of tests.
*/
// TODO(b/177504986): Remove all @Keep annotations once we figure out why this class gets stripped
// away by proguard.
internal object DataPoints {
private const val TAG = "DataPoints"
/**
* Creates a new [IntervalDataPoint] of type [DataType.STEPS] with the given [steps].
*
* @param steps number of steps taken between [startDurationFromBoot] and [endDurationFromBoot],
* Range from 0 to 1000000.
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun steps(
@IntRange(from = 0, to = 1000000) steps: Long,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Long> {
if (steps !in 0..1000000) {
Log.w(TAG, "steps value $steps is out of range")
}
return IntervalDataPoint(
dataType = DataType.STEPS,
value = steps,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [SampleDataPoint] of type [DataType.STEPS_PER_MINUTE] with the given
* [stepsPerMinute].
*
* @param stepsPerMinute step rate at [timeDurationFromBoot], Range from 0 to 1000000
* @param timeDurationFromBoot the point in time [stepsPerMinute] is accurate
*/
@JvmStatic
public fun stepsPerMinute(
@IntRange(from = 0, to = 1000000) stepsPerMinute: Long,
timeDurationFromBoot: Duration
): SampleDataPoint<Long> {
if (stepsPerMinute !in 0..1000000) {
Log.w(TAG, "stepsPerMinute value $stepsPerMinute is out of range")
}
return SampleDataPoint(
dataType = DataType.STEPS_PER_MINUTE,
value = stepsPerMinute,
timeDurationFromBoot = timeDurationFromBoot
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.DISTANCE] with the given [meters].
*
* @param meters distance traveled between [startDurationFromBoot] and [endDurationFromBoot]
* , Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun distance(
@FloatRange(from = 0.0, to = 1000000.0) meters: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (meters !in 0.0..1000000.0) {
Log.w(TAG, "distance value $meters is out of range")
}
return IntervalDataPoint(
dataType = DataType.DISTANCE,
value = meters,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [CumulativeDataPoint] for [DataType.DISTANCE_TOTAL] with the given [meters].
*
* @param meters distance accumulated between [startTime] and [endTime], Range from
* 0.0 to 1000000.0
* @param startTime the point in time this data point begins
* @param endTime the point in time this data point ends
*/
@JvmStatic
public fun distanceTotal(
@FloatRange(from = 0.0, to = 1000000.0) meters: Double,
startTime: Instant,
endTime: Instant
): CumulativeDataPoint<Double> {
if (meters !in 0.0..1000000.0) {
Log.w(TAG, "distanceTotal value $meters is out of range")
}
return CumulativeDataPoint(
dataType = DataType.DISTANCE_TOTAL,
total = meters,
start = startTime,
end = endTime
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.ELEVATION_GAIN] with the given [meters].
*
* @param meters meters gained between [startDurationFromBoot] and [endDurationFromBoot],
* Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun elevationGain(
@FloatRange(from = 0.0, to = 1000000.0) meters: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (meters !in 0.0..1000000.0) {
Log.w(TAG, "elevationGain value $meters is out of range")
}
return IntervalDataPoint(
dataType = DataType.ELEVATION_GAIN,
value = meters,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Create a new [IntervalDataPoint] of type [DataType.ELEVATION_LOSS] with the given [meters].
*
* @param meters meters lost between [startDurationFromBoot] and [endDurationFromBoot],
* Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun elevationLoss(
@FloatRange(from = 0.0, to = 1000000.0) meters: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (meters !in 0.0..1000000.0) {
Log.w(TAG, "elevationLoss value $meters is out of range")
}
return IntervalDataPoint(
dataType = DataType.ELEVATION_LOSS,
value = meters,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [SampleDataPoint] of type [DataType.ABSOLUTE_ELEVATION] with the given
* [meters].
*
* @param meters absolute elevation in meters at [timeDurationFromBoot], Range
* from -1000000.0 to 1000000.0
* @param timeDurationFromBoot the point in time [stepsPerMinute] is accurate
*/
@JvmStatic
public fun absoluteElevation(
@FloatRange(from = -1000000.0, to = 1000000.0) meters: Double,
timeDurationFromBoot: Duration,
): SampleDataPoint<Double> {
if (meters !in -1000000.0..1000000.0) {
Log.w(TAG, "absoluteElevation value $meters is out of range")
}
return SampleDataPoint(
dataType = DataType.ABSOLUTE_ELEVATION,
value = meters,
timeDurationFromBoot = timeDurationFromBoot,
)
}
/**
* Creates a new [StatisticalDataPoint] of type [DataType.ABSOLUTE_ELEVATION_STATS] with the
* given elevations (in meters).
*
* @param minAbsoluteElevationMeters lowest observed elevation in this interval,
* Range from -1000000.0 to 1000000.0
* @param maxAbsoluteElevationMeters highest observed elevation in this interval,
* Range from -1000000.0 to 1000000.0
* @param averageAbsoluteElevationMeters average observed elevation in this interval,
* Range from -1000000.0 to 1000000.0
* @param startTime the point in time this data point begins
* @param endTime the point in time this data point ends
*/
@JvmStatic
public fun absoluteElevationStats(
@FloatRange(from = -1000000.0, to = 1000000.0) minAbsoluteElevationMeters: Double,
@FloatRange(from = -1000000.0, to = 1000000.0) maxAbsoluteElevationMeters: Double,
@FloatRange(from = -1000000.0, to = 1000000.0) averageAbsoluteElevationMeters: Double,
startTime: Instant,
endTime: Instant
): StatisticalDataPoint<Double> {
if (minAbsoluteElevationMeters !in -1000000.0..1000000.0) {
Log.w(TAG, "absoluteElevationStats: minAbsoluteElevationMeters value " +
"$minAbsoluteElevationMeters is out of range")
}
if (maxAbsoluteElevationMeters !in -1000000.0..1000000.0) {
Log.w(TAG, "absoluteElevationStats: maxAbsoluteElevationMeters value " +
"$maxAbsoluteElevationMeters is out of range")
}
if (averageAbsoluteElevationMeters !in -1000000.0..1000000.0) {
Log.w(TAG, "absoluteElevationStats: averageAbsoluteElevationMeters value " +
"$averageAbsoluteElevationMeters is out of range")
}
return StatisticalDataPoint(
dataType = DataType.ABSOLUTE_ELEVATION_STATS,
min = minAbsoluteElevationMeters,
max = maxAbsoluteElevationMeters,
average = averageAbsoluteElevationMeters,
start = startTime,
end = endTime,
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.FLOORS] with the given [floors].
*
* @param floors floors ascended between [startDurationFromBoot] and [endDurationFromBoot],
* Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun floors(
@FloatRange(from = 0.0, to = 1000000.0) floors: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (floors !in 0.0..1000000.0) {
Log.w(TAG, "floors value $floors is out of range")
}
return IntervalDataPoint(
dataType = DataType.FLOORS,
value = floors,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.CALORIES] with the given [kilocalories].
*
* @param kilocalories total calories burned (BMR + Active) between [startDurationFromBoot] and
* [endDurationFromBoot], Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun calories(
@FloatRange(from = 0.0, to = 1000000.0) kilocalories: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (kilocalories !in 0.0..1000000.0) {
Log.w(TAG, "calories value $kilocalories is out of range")
}
return IntervalDataPoint(
dataType = DataType.CALORIES,
value = kilocalories,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [CumulativeDataPoint] of type [DataType.CALORIES_TOTAL] with the given
* [kilocalories] that represents an accumulation over a longer period of time.
*
* @param kilocalories total calories burned (BMR + Active) between [startTime] and
* [endTime], Range from 0.0 to 1000000.0
* @param startTime the point in time this data point begins
* @param endTime the point in time this data point ends
*/
@JvmStatic
public fun caloriesTotal(
@FloatRange(from = 0.0, to = 1000000.0) kilocalories: Double,
startTime: Instant,
endTime: Instant
): CumulativeDataPoint<Double> {
if (kilocalories !in 0.0..1000000.0) {
Log.w(TAG, "caloriesTotal value $kilocalories is out of range")
}
return CumulativeDataPoint(
dataType = DataType.CALORIES_TOTAL,
total = kilocalories,
start = startTime,
end = endTime
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.SWIMMING_STROKES] with the given
* [strokes].
*
* @param strokes total swimming strokes between [startDurationFromBoot] and
* [endDurationFromBoot], Range from 0 to 1000000
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun swimmingStrokes(
@IntRange(from = 0, to = 1000000) strokes: Long,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration
): IntervalDataPoint<Long> {
if (strokes !in 0..1000000) {
Log.w(TAG, "swimmingStrokes value $strokes is out of range")
}
return IntervalDataPoint(
dataType = DataType.SWIMMING_STROKES,
value = strokes,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.GOLF_SHOT_COUNT] with the given [shots].
*
* @param shots golf shots made between [startDurationFromBoot] and [endDurationFromBoot],
* Range from 0 to 1000000
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun golfShotCount(
@IntRange(from = 0, to = 1000000) shots: Long,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Long> {
if (shots !in 0..1000000) {
Log.w(TAG, "golfShotCount value $shots is out of range")
}
return IntervalDataPoint(
dataType = DataType.GOLF_SHOT_COUNT,
value = shots,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot,
)
}
/**
* Creates a new [SampleDataPoint] of type [DataType.LOCATION] with the given [latitude],
* [longitude], and optionally [altitude], [bearing], and [accuracy].
*
* @param latitude latitude at [timeDurationFromBoot], Range from -90.0 to 90.0
* @param longitude longitude at [timeDurationFromBoot], Range from -180.0 to 180.0
* @param timeDurationFromBoot the point in time this data was recorded
* @param altitude optional altitude or [LocationData.ALTITUDE_UNAVAILABLE] at
* [timeDurationFromBoot]
* @param bearing optional bearing or [LocationData.BEARING_UNAVAILABLE] at
* [timeDurationFromBoot], Range from 0.0 (inclusive) to 360.0 (exclusive).
* Value [LocationData.ALTITUDE_UNAVAILABLE] represents altitude is not available
* @param accuracy optional [LocationAccuracy] describing this data or `null`
*/
@JvmStatic
@JvmOverloads
public fun location(
@FloatRange(from = -90.0, to = 90.0) latitude: Double,
@FloatRange(from = -180.0, to = 180.0) longitude: Double,
timeDurationFromBoot: Duration,
altitude: Double = LocationData.ALTITUDE_UNAVAILABLE,
bearing: Double = LocationData.BEARING_UNAVAILABLE,
accuracy: LocationAccuracy? = null
): SampleDataPoint<LocationData> {
if (latitude !in -90.0..90.0) {
Log.w(TAG, "location: latitude value $latitude is out of range")
}
if (longitude !in -180.0..180.0) {
Log.w(TAG, "location: longitude value $longitude is out of range")
}
if (bearing < -1.0 && bearing >= 360.0) {
Log.w(TAG, "location: bearing value $bearing is out of range")
}
return SampleDataPoint(
dataType = DataType.LOCATION,
value = LocationData(latitude, longitude, altitude, bearing),
timeDurationFromBoot = timeDurationFromBoot,
accuracy = accuracy
)
}
/**
* Creates a new [SampleDataPoint] of type [DataType.SPEED] with the given [metersPerSecond].
*
* @param metersPerSecond speed in meters per second at [timeDurationFromBoot],
* Range from 0.0 to 1000000.0
* @param timeDurationFromBoot the point in time [metersPerSecond] was recorded
*/
@JvmStatic
public fun speed(
@FloatRange(from = 0.0, to = 1000000.0) metersPerSecond: Double,
timeDurationFromBoot: Duration,
): SampleDataPoint<Double> {
if (metersPerSecond !in 0.0..1000000.0) {
Log.w(TAG, "speed value $metersPerSecond is out of range")
}
return SampleDataPoint(
dataType = DataType.SPEED,
value = metersPerSecond,
timeDurationFromBoot = timeDurationFromBoot,
)
}
/**
* Creates a new [SampleDataPoint] of type [DataType.PACE] with the given
* [durationPerKilometer].
*
* @param durationPerKilometer pace in terms of time per kilometer at [timeDurationFromBoot]
* @param timeDurationFromBoot the point in time [durationPerKilometer] was recorded
*/
@JvmStatic
public fun pace(
durationPerKilometer: Duration,
timeDurationFromBoot: Duration
): SampleDataPoint<Double> =
SampleDataPoint(
dataType = DataType.PACE,
value = (durationPerKilometer.toMillis()).toDouble(),
timeDurationFromBoot = timeDurationFromBoot
)
/**
* Creates a new [SampleDataPoint] of type [DataType.HEART_RATE_BPM] with the given [bpm] and
* [accuracy].
*
* @param bpm heart rate given in beats per minute, Range from 0.0 to 300.0
* @param timeDurationFromBoot the point in time this data was recorded
* @param accuracy optional [HeartRateAccuracy] describing this data or `null`
*/
@JvmStatic
@JvmOverloads
public fun heartRate(
@FloatRange(from = 0.0, to = 300.0) bpm: Double,
timeDurationFromBoot: Duration,
accuracy: HeartRateAccuracy? = null
): SampleDataPoint<Double> {
if (bpm !in 0.0..300.0) {
Log.w(TAG, "heartRate value $bpm is out of range")
}
return SampleDataPoint(
dataType = DataType.HEART_RATE_BPM,
value = bpm,
timeDurationFromBoot = timeDurationFromBoot,
accuracy = accuracy
)
}
/**
* Creates a new [StatisticalDataPoint] of type [DataType.HEART_RATE_BPM] with the given
* min/max/average beats per minute.
*
* @param minBpm lowest observed heart rate given in beats per minute in this interval,
* Range from 0.0 to 300.0
* @param maxBpm highest observed heart rate given in beats per minute in this interval,
* Range from 0.0 to 300.0
* @param averageBpm average observed heart rate given in beats per minute in this interval,
* Range from 0.0 to 300.0
* @param startTime the point in time this data point begins
* @param endTime the point in time this data point ends
*/
@JvmStatic
public fun heartRateStats(
@FloatRange(from = 0.0, to = 300.0) minBpm: Double,
@FloatRange(from = 0.0, to = 300.0) maxBpm: Double,
@FloatRange(from = 0.0, to = 300.0) averageBpm: Double,
startTime: Instant,
endTime: Instant,
): StatisticalDataPoint<Double> {
if (minBpm !in 0.0..300.0) {
Log.w(TAG, "heartRateStats: minBpm value $minBpm is out of range")
}
if (maxBpm !in 0.0..300.0) {
Log.w(TAG, "heartRateStats: maxBpm value $maxBpm is out of range")
}
if (averageBpm !in 0.0..300.0) {
Log.w(TAG, "heartRateStats: averageBpm value $averageBpm is out of range")
}
return StatisticalDataPoint(
dataType = DataType.HEART_RATE_BPM_STATS,
min = minBpm,
max = maxBpm,
average = averageBpm,
start = startTime,
end = endTime
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.STEPS_DAILY] with the given [dailySteps].
*
* @param dailySteps number of steps taken today, between [startDurationFromBoot] and
* [endDurationFromBoot], Range from 0 to 1000000
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun dailySteps(
@IntRange(from = 0, to = 1000000) dailySteps: Long,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration
): IntervalDataPoint<Long> {
if (dailySteps !in 0..1000000) {
Log.w(TAG, "dailySteps value $dailySteps is out of range")
}
return IntervalDataPoint(
dataType = DataType.STEPS_DAILY,
value = dailySteps,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.FLOORS_DAILY] with the given [floors].
*
* @param floors number of floors ascended today, between [startDurationFromBoot] and
* [endDurationFromBoot], Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun dailyFloors(
@FloatRange(from = 0.0, to = 1000000.0) floors: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration
): IntervalDataPoint<Double> {
if (floors !in 0.0..1000000.0) {
Log.w(TAG, "dailyFloors value $floors is out of range")
}
return IntervalDataPoint(
dataType = DataType.FLOORS_DAILY,
value = floors,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.CALORIES_DAILY] with the given
* [calories].
*
* @param calories number of calories burned today including both active and passive / BMR,
* between [startDurationFromBoot] and [endDurationFromBoot], Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun dailyCalories(
@FloatRange(from = 0.0, to = 1000000.0) calories: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration
): IntervalDataPoint<Double> {
if (calories in 0.0..1000000.0) {
Log.w(TAG, "dailyCalories value $calories is out of range")
}
return IntervalDataPoint(
dataType = DataType.CALORIES_DAILY,
value = calories,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot
)
}
/**
* Creates a new [IntervalDataPoint] of type [DataType.DISTANCE_DAILY] with the given [meters].
*
* @param meters number of meters traveled today through active/passive exercise between
* [startDurationFromBoot] and [endDurationFromBoot], Range from 0.0 to 1000000.0
* @param startDurationFromBoot the point in time this data point begins
* @param endDurationFromBoot the point in time this data point ends
*/
@JvmStatic
public fun dailyDistance(
@FloatRange(from = 0.0, to = 1000000.0) meters: Double,
startDurationFromBoot: Duration,
endDurationFromBoot: Duration,
): IntervalDataPoint<Double> {
if (meters !in 0.0..1000000.0) {
Log.w(TAG, "dailyDistance value $meters is out of range")
}
return IntervalDataPoint(
dataType = DataType.DISTANCE_DAILY,
value = meters,
startDurationFromBoot = startDurationFromBoot,
endDurationFromBoot = endDurationFromBoot
)
}
}