Add segments and laps to the exercise session record
Add ExerciseSegment and ExerciseLap classes as parts of
the of exercise session record. Update session types.
Design doc: go/hc-platform-session-datatype
Bug: 262550571
Test: atest CtsHealthConnectDeviceTestCases
Change-Id: I115a2958984350b942e8f29d08e112549567e15a
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 067fa9b..5b02af7 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -479,6 +479,18 @@
method @NonNull public android.healthconnect.datatypes.ElevationGainedRecord.Builder setStartZoneOffset(@NonNull java.time.ZoneOffset);
}
+ public final class ExerciseLap {
+ method @NonNull public java.time.Instant getEndTime();
+ method @Nullable public android.healthconnect.datatypes.units.Length getLength();
+ method @NonNull public java.time.Instant getStartTime();
+ }
+
+ public static final class ExerciseLap.Builder {
+ ctor public ExerciseLap.Builder(@NonNull java.time.Instant, @NonNull java.time.Instant);
+ method @NonNull public android.healthconnect.datatypes.ExerciseLap build();
+ method @NonNull public android.healthconnect.datatypes.ExerciseLap.Builder setLength(@NonNull android.healthconnect.datatypes.units.Length);
+ }
+
public final class ExerciseRoute {
ctor public ExerciseRoute(@NonNull java.util.List<android.healthconnect.datatypes.ExerciseRoute.Location>);
method @NonNull public java.util.List<android.healthconnect.datatypes.ExerciseRoute.Location> getRouteLocations();
@@ -501,10 +513,96 @@
method @NonNull public android.healthconnect.datatypes.ExerciseRoute.Location.Builder setVerticalAccuracy(@NonNull android.healthconnect.datatypes.units.Length);
}
+ public final class ExerciseSegment {
+ method @NonNull public java.time.Instant getEndTime();
+ method @IntRange(from=0) public int getRepetitionsCount();
+ method public int getSegmentType();
+ method @NonNull public java.time.Instant getStartTime();
+ }
+
+ public static final class ExerciseSegment.Builder {
+ ctor public ExerciseSegment.Builder(@NonNull java.time.Instant, @NonNull java.time.Instant, int);
+ method @NonNull public android.healthconnect.datatypes.ExerciseSegment build();
+ method @NonNull public android.healthconnect.datatypes.ExerciseSegment.Builder setRepetitionsCount(@IntRange(from=0) int);
+ }
+
+ public final class ExerciseSegmentType {
+ field public static final int EXERCISE_SEGMENT_TYPE_ARM_CURL = 26; // 0x1a
+ field public static final int EXERCISE_SEGMENT_TYPE_BACK_EXTENSION = 27; // 0x1b
+ field public static final int EXERCISE_SEGMENT_TYPE_BALL_SLAM = 28; // 0x1c
+ field public static final int EXERCISE_SEGMENT_TYPE_BARBELL_SHOULDER_PRESS = 1; // 0x1
+ field public static final int EXERCISE_SEGMENT_TYPE_BENCH_PRESS = 29; // 0x1d
+ field public static final int EXERCISE_SEGMENT_TYPE_BENCH_SIT_UP = 2; // 0x2
+ field public static final int EXERCISE_SEGMENT_TYPE_BIKING = 3; // 0x3
+ field public static final int EXERCISE_SEGMENT_TYPE_BIKING_STATIONARY = 4; // 0x4
+ field public static final int EXERCISE_SEGMENT_TYPE_BURPEE = 30; // 0x1e
+ field public static final int EXERCISE_SEGMENT_TYPE_CRUNCH = 31; // 0x1f
+ field public static final int EXERCISE_SEGMENT_TYPE_DEADLIFT = 32; // 0x20
+ field public static final int EXERCISE_SEGMENT_TYPE_DOUBLE_ARM_TRICEPS_EXTENSION = 33; // 0x21
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_LEFT_ARM = 5; // 0x5
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_RIGHT_ARM = 6; // 0x6
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_FRONT_RAISE = 7; // 0x7
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_LATERAL_RAISE = 8; // 0x8
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_ROW = 34; // 0x22
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM = 9; // 0x9
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM = 10; // 0xa
+ field public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_TWO_ARM = 11; // 0xb
+ field public static final int EXERCISE_SEGMENT_TYPE_ELLIPTICAL = 12; // 0xc
+ field public static final int EXERCISE_SEGMENT_TYPE_FORWARD_TWIST = 13; // 0xd
+ field public static final int EXERCISE_SEGMENT_TYPE_FRONT_RAISE = 35; // 0x23
+ field public static final int EXERCISE_SEGMENT_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING = 62; // 0x3e
+ field public static final int EXERCISE_SEGMENT_TYPE_HIP_THRUST = 36; // 0x24
+ field public static final int EXERCISE_SEGMENT_TYPE_HULA_HOOP = 37; // 0x25
+ field public static final int EXERCISE_SEGMENT_TYPE_JUMPING_JACK = 38; // 0x26
+ field public static final int EXERCISE_SEGMENT_TYPE_JUMP_ROPE = 39; // 0x27
+ field public static final int EXERCISE_SEGMENT_TYPE_KETTLEBELL_SWING = 40; // 0x28
+ field public static final int EXERCISE_SEGMENT_TYPE_LATERAL_RAISE = 41; // 0x29
+ field public static final int EXERCISE_SEGMENT_TYPE_LAT_PULL_DOWN = 42; // 0x2a
+ field public static final int EXERCISE_SEGMENT_TYPE_LEG_CURL = 43; // 0x2b
+ field public static final int EXERCISE_SEGMENT_TYPE_LEG_EXTENSION = 44; // 0x2c
+ field public static final int EXERCISE_SEGMENT_TYPE_LEG_PRESS = 45; // 0x2d
+ field public static final int EXERCISE_SEGMENT_TYPE_LEG_RAISE = 46; // 0x2e
+ field public static final int EXERCISE_SEGMENT_TYPE_LUNGE = 47; // 0x2f
+ field public static final int EXERCISE_SEGMENT_TYPE_MOUNTAIN_CLIMBER = 48; // 0x30
+ field public static final int EXERCISE_SEGMENT_TYPE_OTHER_WORKOUT = 64; // 0x40
+ field public static final int EXERCISE_SEGMENT_TYPE_PAUSE = 67; // 0x43
+ field public static final int EXERCISE_SEGMENT_TYPE_PILATES = 14; // 0xe
+ field public static final int EXERCISE_SEGMENT_TYPE_PLANK = 49; // 0x31
+ field public static final int EXERCISE_SEGMENT_TYPE_PULL_UP = 50; // 0x32
+ field public static final int EXERCISE_SEGMENT_TYPE_PUNCH = 51; // 0x33
+ field public static final int EXERCISE_SEGMENT_TYPE_REST = 66; // 0x42
+ field public static final int EXERCISE_SEGMENT_TYPE_ROWING_MACHINE = 15; // 0xf
+ field public static final int EXERCISE_SEGMENT_TYPE_RUNNING = 16; // 0x10
+ field public static final int EXERCISE_SEGMENT_TYPE_RUNNING_TREADMILL = 17; // 0x11
+ field public static final int EXERCISE_SEGMENT_TYPE_SHOULDER_PRESS = 52; // 0x34
+ field public static final int EXERCISE_SEGMENT_TYPE_SINGLE_ARM_TRICEPS_EXTENSION = 53; // 0x35
+ field public static final int EXERCISE_SEGMENT_TYPE_SIT_UP = 54; // 0x36
+ field public static final int EXERCISE_SEGMENT_TYPE_SQUAT = 55; // 0x37
+ field public static final int EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING = 18; // 0x12
+ field public static final int EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING_MACHINE = 19; // 0x13
+ field public static final int EXERCISE_SEGMENT_TYPE_STRETCHING = 20; // 0x14
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BACKSTROKE = 57; // 0x39
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BREASTSTROKE = 58; // 0x3a
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BUTTERFLY = 59; // 0x3b
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_FREESTYLE = 56; // 0x38
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_MIXED = 60; // 0x3c
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_OPEN_WATER = 21; // 0x15
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_OTHER = 61; // 0x3d
+ field public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_POOL = 22; // 0x16
+ field public static final int EXERCISE_SEGMENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int EXERCISE_SEGMENT_TYPE_UPPER_TWIST = 23; // 0x17
+ field public static final int EXERCISE_SEGMENT_TYPE_WALKING = 24; // 0x18
+ field public static final int EXERCISE_SEGMENT_TYPE_WEIGHTLIFTING = 63; // 0x3f
+ field public static final int EXERCISE_SEGMENT_TYPE_WHEELCHAIR = 25; // 0x19
+ field public static final int EXERCISE_SEGMENT_TYPE_YOGA = 65; // 0x41
+ }
+
public final class ExerciseSessionRecord extends android.healthconnect.datatypes.IntervalRecord {
method public int getExerciseType();
+ method @NonNull public java.util.List<android.healthconnect.datatypes.ExerciseLap> getLaps();
method @Nullable public CharSequence getNotes();
method @Nullable public android.healthconnect.datatypes.ExerciseRoute getRoute();
+ method @NonNull public java.util.List<android.healthconnect.datatypes.ExerciseSegment> getSegments();
method @Nullable public CharSequence getTitle();
method @NonNull public boolean hasRoute();
}
@@ -513,8 +611,10 @@
ctor public ExerciseSessionRecord.Builder(@NonNull android.healthconnect.datatypes.Metadata, @NonNull java.time.Instant, @NonNull java.time.Instant, int);
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord build();
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setEndZoneOffset(@NonNull java.time.ZoneOffset);
+ method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setLaps(@NonNull java.util.List<android.healthconnect.datatypes.ExerciseLap>);
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setNotes(@Nullable CharSequence);
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setRoute(@Nullable android.healthconnect.datatypes.ExerciseRoute);
+ method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setSegments(@NonNull java.util.List<android.healthconnect.datatypes.ExerciseSegment>);
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setStartZoneOffset(@NonNull java.time.ZoneOffset);
method @NonNull public android.healthconnect.datatypes.ExerciseSessionRecord.Builder setTitle(@Nullable CharSequence);
}
@@ -530,6 +630,7 @@
field public static final int EXERCISE_SESSION_TYPE_CALISTHENICS = 8; // 0x8
field public static final int EXERCISE_SESSION_TYPE_CRICKET = 9; // 0x9
field public static final int EXERCISE_SESSION_TYPE_DANCING = 10; // 0xa
+ field public static final int EXERCISE_SESSION_TYPE_ELLIPTICAL = 60; // 0x3c
field public static final int EXERCISE_SESSION_TYPE_EXERCISE_CLASS = 11; // 0xb
field public static final int EXERCISE_SESSION_TYPE_FENCING = 12; // 0xc
field public static final int EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN = 13; // 0xd
@@ -552,6 +653,7 @@
field public static final int EXERCISE_SESSION_TYPE_ROCK_CLIMBING = 29; // 0x1d
field public static final int EXERCISE_SESSION_TYPE_ROLLER_HOCKEY = 30; // 0x1e
field public static final int EXERCISE_SESSION_TYPE_ROWING = 31; // 0x1f
+ field public static final int EXERCISE_SESSION_TYPE_ROWING_MACHINE = 61; // 0x3d
field public static final int EXERCISE_SESSION_TYPE_RUGBY = 32; // 0x20
field public static final int EXERCISE_SESSION_TYPE_RUNNING = 33; // 0x21
field public static final int EXERCISE_SESSION_TYPE_RUNNING_TREADMILL = 34; // 0x22
@@ -565,6 +667,7 @@
field public static final int EXERCISE_SESSION_TYPE_SOFTBALL = 42; // 0x2a
field public static final int EXERCISE_SESSION_TYPE_SQUASH = 43; // 0x2b
field public static final int EXERCISE_SESSION_TYPE_STAIR_CLIMBING = 44; // 0x2c
+ field public static final int EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE = 59; // 0x3b
field public static final int EXERCISE_SESSION_TYPE_STRENGTH_TRAINING = 45; // 0x2d
field public static final int EXERCISE_SESSION_TYPE_STRETCHING = 46; // 0x2e
field public static final int EXERCISE_SESSION_TYPE_SURFING = 47; // 0x2f
diff --git a/framework/java/android/healthconnect/datatypes/ExerciseLap.java b/framework/java/android/healthconnect/datatypes/ExerciseLap.java
new file mode 100644
index 0000000..01622f0
--- /dev/null
+++ b/framework/java/android/healthconnect/datatypes/ExerciseLap.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.datatypes;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.healthconnect.datatypes.units.Length;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * Captures the time of a lap within exercise session. Part of {@link ExerciseSessionRecord}.
+ *
+ * <p>Each record contains the start and end time and optional {@link Length} of the lap (e.g. pool
+ * length while swimming or a track lap while running). There may or may not be direct correlation
+ * with {@link ExerciseSegment} start and end times, e.g. {@link ExerciseSessionRecord} of type
+ * running without any segments can be divided as laps of different lengths.
+ */
+public final class ExerciseLap {
+ private static final int MAX_LAP_LENGTH_METRES = 10000000;
+
+ private final TimeInterval mInterval;
+ private final Length mLength;
+
+ private ExerciseLap(@NonNull TimeInterval interval, @Nullable Length length) {
+ Objects.requireNonNull(interval);
+ mInterval = interval;
+ mLength = length;
+ }
+
+ /*
+ * Returns Length of the lap.
+ */
+ @Nullable
+ public Length getLength() {
+ return mLength;
+ }
+
+ /*
+ * Returns start time of the lap.
+ */
+ @NonNull
+ public Instant getStartTime() {
+ return mInterval.getStartTime();
+ }
+
+ /*
+ * Returns end time of the lap.
+ */
+ @NonNull
+ public Instant getEndTime() {
+ return mInterval.getEndTime();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ExerciseLap)) return false;
+ ExerciseLap that = (ExerciseLap) o;
+ return Objects.equals(mInterval, that.mInterval)
+ && Objects.equals(getLength(), that.getLength());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mInterval, getLength());
+ }
+
+ /** Builder class for {@link ExerciseLap} */
+ public static final class Builder {
+ private final TimeInterval mInterval;
+ private Length mLength;
+
+ public Builder(@NonNull Instant startTime, @NonNull Instant endTime) {
+ mInterval = new TimeInterval(startTime, endTime);
+ }
+
+ /**
+ * Sets the length of this lap
+ *
+ * @param length Length of the lap, in {@link Length} unit. Optional field. Valid range:
+ * 0-1000000 meters.
+ */
+ @NonNull
+ public ExerciseLap.Builder setLength(@NonNull Length length) {
+ Objects.requireNonNull(length);
+ if (length.getInMeters() < 0 || length.getInMeters() > MAX_LAP_LENGTH_METRES) {
+ throw new IllegalArgumentException("Length must be between 0-1000000 metres");
+ }
+ mLength = length;
+ return this;
+ }
+
+ /** Builds {@link ExerciseLap} instance. */
+ @NonNull
+ public ExerciseLap build() {
+ return new ExerciseLap(mInterval, mLength);
+ }
+ }
+}
diff --git a/framework/java/android/healthconnect/datatypes/ExerciseSegment.java b/framework/java/android/healthconnect/datatypes/ExerciseSegment.java
new file mode 100644
index 0000000..7f3cfcc
--- /dev/null
+++ b/framework/java/android/healthconnect/datatypes/ExerciseSegment.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.datatypes;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * Represents particular exercise within exercise session (see {@link ExerciseSessionRecord}).
+ *
+ * <p>Each record contains start and end time of the exercise, exercise type and optional number of
+ * repetitions.
+ */
+public final class ExerciseSegment {
+ private final TimeInterval mInterval;
+
+ @ExerciseSegmentType.ExerciseSegmentTypes private final int mSegmentType;
+
+ private final int mRepetitionsCount;
+
+ private ExerciseSegment(
+ @NonNull TimeInterval interval,
+ @ExerciseSegmentType.ExerciseSegmentTypes int segmentType,
+ @IntRange(from = 0) int repetitionsCount) {
+ Objects.requireNonNull(interval);
+ mInterval = interval;
+
+ mSegmentType = segmentType;
+
+ if (repetitionsCount < 0) {
+ throw new IllegalArgumentException("Repetitions count must be non-negative.");
+ }
+ mRepetitionsCount = repetitionsCount;
+ }
+
+ /*
+ * Returns type of the segment, one of {@link @ExerciseSegmentType.ExerciseSegmentTypes}.
+ */
+ @ExerciseSegmentType.ExerciseSegmentTypes
+ public int getSegmentType() {
+ return mSegmentType;
+ }
+
+ /*
+ * Returns number of repetitions in the current segment. Positive value.
+ */
+ @IntRange(from = 0)
+ public int getRepetitionsCount() {
+ return mRepetitionsCount;
+ }
+
+ /*
+ * Returns start time of the segment.
+ */
+ @NonNull
+ public Instant getStartTime() {
+ return mInterval.getStartTime();
+ }
+
+ /*
+ * Returns end time of the segment.
+ */
+ @NonNull
+ public Instant getEndTime() {
+ return mInterval.getEndTime();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ExerciseSegment)) return false;
+ ExerciseSegment that = (ExerciseSegment) o;
+ return mSegmentType == that.mSegmentType && mRepetitionsCount == that.mRepetitionsCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSegmentType, mRepetitionsCount);
+ }
+
+ /** Builder class for {@link ExerciseSegment} */
+ public static final class Builder {
+ private final TimeInterval mInterval;
+
+ @ExerciseSegmentType.ExerciseSegmentTypes private final int mSegmentType;
+
+ private int mRepetitionsCount = 0;
+
+ public Builder(
+ @NonNull Instant startTime,
+ @NonNull Instant endTime,
+ @ExerciseSegmentType.ExerciseSegmentTypes int segmentType) {
+ Objects.requireNonNull(startTime);
+ Objects.requireNonNull(endTime);
+ mInterval = new TimeInterval(startTime, endTime);
+ mSegmentType = segmentType;
+ }
+
+ /**
+ * Sets the number of repetitions to the current segment. Returns builder instance with
+ * repetitions count set.
+ */
+ @NonNull
+ public Builder setRepetitionsCount(@IntRange(from = 0) int repetitionsCount) {
+ if (repetitionsCount < 0) {
+ throw new IllegalArgumentException("Number of repetitions must be non negative.");
+ }
+ mRepetitionsCount = repetitionsCount;
+ return this;
+ }
+
+ /**
+ * Sets the number repetitions to the current segment. Returns {@link ExerciseSegment}
+ * instance.
+ */
+ @NonNull
+ public ExerciseSegment build() {
+ return new ExerciseSegment(mInterval, mSegmentType, mRepetitionsCount);
+ }
+ }
+}
diff --git a/framework/java/android/healthconnect/datatypes/ExerciseSegmentType.java b/framework/java/android/healthconnect/datatypes/ExerciseSegmentType.java
new file mode 100644
index 0000000..eb32102
--- /dev/null
+++ b/framework/java/android/healthconnect/datatypes/ExerciseSegmentType.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.datatypes;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Identifier for exercise types, as returned by {@link ExerciseSegment#getSegmentType()}. */
+public final class ExerciseSegmentType {
+
+ /** Use this type if the type of the exercise segment is not known. */
+ public static final int EXERCISE_SEGMENT_TYPE_UNKNOWN = 0;
+
+ /** Use this type for barbel shoulder press. */
+ public static final int EXERCISE_SEGMENT_TYPE_BARBELL_SHOULDER_PRESS = 1;
+
+ /** Use this type for bench sit up. */
+ public static final int EXERCISE_SEGMENT_TYPE_BENCH_SIT_UP = 2;
+
+ /** Use this type for biking. */
+ public static final int EXERCISE_SEGMENT_TYPE_BIKING = 3;
+
+ /** Use this type for stationary biking. */
+ public static final int EXERCISE_SEGMENT_TYPE_BIKING_STATIONARY = 4;
+
+ /** Use this type for left arm dumbbell curl. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_LEFT_ARM = 5;
+
+ /** Use this type for right arm dumbbell curl. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_RIGHT_ARM = 6;
+
+ /** Use this type for right arm dumbbell front raise. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_FRONT_RAISE = 7;
+
+ /** Use this type for dumbbell lateral raises. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_LATERAL_RAISE = 8;
+
+ /** Use this type for left arm triceps extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM = 9;
+
+ /** Use this type for right arm triceps extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM = 10;
+
+ /** Use this type for two arms triceps extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_TWO_ARM = 11;
+
+ /** Use this type for elliptical workout. */
+ public static final int EXERCISE_SEGMENT_TYPE_ELLIPTICAL = 12;
+
+ /** Use this type for forward twists. */
+ public static final int EXERCISE_SEGMENT_TYPE_FORWARD_TWIST = 13;
+
+ /** Use this type for pilates. */
+ public static final int EXERCISE_SEGMENT_TYPE_PILATES = 14;
+
+ /** Use this type for rowing machine workout. */
+ public static final int EXERCISE_SEGMENT_TYPE_ROWING_MACHINE = 15;
+
+ /** Use this type for running. */
+ public static final int EXERCISE_SEGMENT_TYPE_RUNNING = 16;
+
+ /** Use this type for treadmill running. */
+ public static final int EXERCISE_SEGMENT_TYPE_RUNNING_TREADMILL = 17;
+
+ /** Use this type for stair climbing. */
+ public static final int EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING = 18;
+
+ /** Use this type for stair climbing machine. */
+ public static final int EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING_MACHINE = 19;
+
+ /** Use this type for stretching. */
+ public static final int EXERCISE_SEGMENT_TYPE_STRETCHING = 20;
+
+ /** Use this type for swimming in open water. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_OPEN_WATER = 21;
+
+ /** Use this type for swimming in the pool. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_POOL = 22;
+
+ /** Use this type for upper twists. */
+ public static final int EXERCISE_SEGMENT_TYPE_UPPER_TWIST = 23;
+
+ /** Use this type for walking. */
+ public static final int EXERCISE_SEGMENT_TYPE_WALKING = 24;
+
+ /** Use this type for wheelchair. */
+ public static final int EXERCISE_SEGMENT_TYPE_WHEELCHAIR = 25;
+
+ /** Use this type for arm curls. */
+ public static final int EXERCISE_SEGMENT_TYPE_ARM_CURL = 26;
+
+ /** Use this type for back extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_BACK_EXTENSION = 27;
+
+ /** Use this type for ball slams. */
+ public static final int EXERCISE_SEGMENT_TYPE_BALL_SLAM = 28;
+
+ /** Use this type for bench presses. */
+ public static final int EXERCISE_SEGMENT_TYPE_BENCH_PRESS = 29;
+
+ /** Use this type for burpees. */
+ public static final int EXERCISE_SEGMENT_TYPE_BURPEE = 30;
+
+ /** Use this type for crunches. */
+ public static final int EXERCISE_SEGMENT_TYPE_CRUNCH = 31;
+
+ /** Use this type for deadlifts. */
+ public static final int EXERCISE_SEGMENT_TYPE_DEADLIFT = 32;
+
+ /** Use this type for double arms triceps extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_DOUBLE_ARM_TRICEPS_EXTENSION = 33;
+
+ /** Use this type for dumbbells rows. */
+ public static final int EXERCISE_SEGMENT_TYPE_DUMBBELL_ROW = 34;
+
+ /** Use this type for front raises. */
+ public static final int EXERCISE_SEGMENT_TYPE_FRONT_RAISE = 35;
+
+ /** Use this type for hip thrusts. */
+ public static final int EXERCISE_SEGMENT_TYPE_HIP_THRUST = 36;
+
+ /** Use this type for hula-hoops. */
+ public static final int EXERCISE_SEGMENT_TYPE_HULA_HOOP = 37;
+
+ /** Use this type for jumping jacks. */
+ public static final int EXERCISE_SEGMENT_TYPE_JUMPING_JACK = 38;
+
+ /** Use this type for jump rope. */
+ public static final int EXERCISE_SEGMENT_TYPE_JUMP_ROPE = 39;
+
+ /** Use this type for kettlebell swings. */
+ public static final int EXERCISE_SEGMENT_TYPE_KETTLEBELL_SWING = 40;
+
+ /** Use this type for lateral raises. */
+ public static final int EXERCISE_SEGMENT_TYPE_LATERAL_RAISE = 41;
+
+ /** Use this type for lat pull-downs. */
+ public static final int EXERCISE_SEGMENT_TYPE_LAT_PULL_DOWN = 42;
+
+ /** Use this type for leg curls. */
+ public static final int EXERCISE_SEGMENT_TYPE_LEG_CURL = 43;
+
+ /** Use this type for leg extensions. */
+ public static final int EXERCISE_SEGMENT_TYPE_LEG_EXTENSION = 44;
+
+ /** Use this type for leg presses. */
+ public static final int EXERCISE_SEGMENT_TYPE_LEG_PRESS = 45;
+
+ /** Use this type for leg raises. */
+ public static final int EXERCISE_SEGMENT_TYPE_LEG_RAISE = 46;
+
+ /** Use this type for lunges. */
+ public static final int EXERCISE_SEGMENT_TYPE_LUNGE = 47;
+
+ /** Use this type for mountain climber. */
+ public static final int EXERCISE_SEGMENT_TYPE_MOUNTAIN_CLIMBER = 48;
+
+ /** Use this type for plank. */
+ public static final int EXERCISE_SEGMENT_TYPE_PLANK = 49;
+
+ /** Use this type for pull-ups. */
+ public static final int EXERCISE_SEGMENT_TYPE_PULL_UP = 50;
+
+ /** Use this type for punches. */
+ public static final int EXERCISE_SEGMENT_TYPE_PUNCH = 51;
+
+ /** Use this type for shoulder press. */
+ public static final int EXERCISE_SEGMENT_TYPE_SHOULDER_PRESS = 52;
+
+ /** Use this type for single arm triceps extension. */
+ public static final int EXERCISE_SEGMENT_TYPE_SINGLE_ARM_TRICEPS_EXTENSION = 53;
+
+ /** Use this type for sit-ups. */
+ public static final int EXERCISE_SEGMENT_TYPE_SIT_UP = 54;
+
+ /** Use this type for squats. */
+ public static final int EXERCISE_SEGMENT_TYPE_SQUAT = 55;
+
+ /** Use this type for freestyle swimming. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_FREESTYLE = 56;
+
+ /** Use this type for backstroke swimming. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BACKSTROKE = 57;
+
+ /** Use this type for breaststroke swimming. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BREASTSTROKE = 58;
+
+ /** Use this type for butterfly swimming. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_BUTTERFLY = 59;
+
+ /** Use this type for mixed swimming. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_MIXED = 60;
+
+ /** Use this type if other swimming styles are not suitable. */
+ public static final int EXERCISE_SEGMENT_TYPE_SWIMMING_OTHER = 61;
+
+ /** Use this type for high intensity training. */
+ public static final int EXERCISE_SEGMENT_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING = 62;
+
+ /** Use this type for weightlifting. */
+ public static final int EXERCISE_SEGMENT_TYPE_WEIGHTLIFTING = 63;
+
+ /** Use this type for other workout. */
+ public static final int EXERCISE_SEGMENT_TYPE_OTHER_WORKOUT = 64;
+
+ /** Use this type for yoga. */
+ public static final int EXERCISE_SEGMENT_TYPE_YOGA = 65;
+
+ /** Use this type for the rest. */
+ public static final int EXERCISE_SEGMENT_TYPE_REST = 66;
+
+ /** Use this type for the pause. */
+ public static final int EXERCISE_SEGMENT_TYPE_PAUSE = 67;
+
+ private ExerciseSegmentType() {}
+
+ /** @hide */
+ @IntDef({
+ EXERCISE_SEGMENT_TYPE_UNKNOWN,
+ EXERCISE_SEGMENT_TYPE_BARBELL_SHOULDER_PRESS,
+ EXERCISE_SEGMENT_TYPE_BENCH_SIT_UP,
+ EXERCISE_SEGMENT_TYPE_BIKING,
+ EXERCISE_SEGMENT_TYPE_BIKING_STATIONARY,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_LEFT_ARM,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_CURL_RIGHT_ARM,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_FRONT_RAISE,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_LATERAL_RAISE,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_TRICEPS_EXTENSION_TWO_ARM,
+ EXERCISE_SEGMENT_TYPE_FORWARD_TWIST,
+ EXERCISE_SEGMENT_TYPE_ELLIPTICAL,
+ EXERCISE_SEGMENT_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING,
+ EXERCISE_SEGMENT_TYPE_PILATES,
+ EXERCISE_SEGMENT_TYPE_ROWING_MACHINE,
+ EXERCISE_SEGMENT_TYPE_RUNNING,
+ EXERCISE_SEGMENT_TYPE_RUNNING_TREADMILL,
+ EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING,
+ EXERCISE_SEGMENT_TYPE_STAIR_CLIMBING_MACHINE,
+ EXERCISE_SEGMENT_TYPE_STRETCHING,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_OPEN_WATER,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_POOL,
+ EXERCISE_SEGMENT_TYPE_UPPER_TWIST,
+ EXERCISE_SEGMENT_TYPE_WALKING,
+ EXERCISE_SEGMENT_TYPE_WEIGHTLIFTING,
+ EXERCISE_SEGMENT_TYPE_WHEELCHAIR,
+ EXERCISE_SEGMENT_TYPE_OTHER_WORKOUT,
+ EXERCISE_SEGMENT_TYPE_YOGA,
+ EXERCISE_SEGMENT_TYPE_ARM_CURL,
+ EXERCISE_SEGMENT_TYPE_BACK_EXTENSION,
+ EXERCISE_SEGMENT_TYPE_BALL_SLAM,
+ EXERCISE_SEGMENT_TYPE_BENCH_PRESS,
+ EXERCISE_SEGMENT_TYPE_BURPEE,
+ EXERCISE_SEGMENT_TYPE_CRUNCH,
+ EXERCISE_SEGMENT_TYPE_DEADLIFT,
+ EXERCISE_SEGMENT_TYPE_DOUBLE_ARM_TRICEPS_EXTENSION,
+ EXERCISE_SEGMENT_TYPE_DUMBBELL_ROW,
+ EXERCISE_SEGMENT_TYPE_FRONT_RAISE,
+ EXERCISE_SEGMENT_TYPE_HIP_THRUST,
+ EXERCISE_SEGMENT_TYPE_HULA_HOOP,
+ EXERCISE_SEGMENT_TYPE_JUMPING_JACK,
+ EXERCISE_SEGMENT_TYPE_JUMP_ROPE,
+ EXERCISE_SEGMENT_TYPE_KETTLEBELL_SWING,
+ EXERCISE_SEGMENT_TYPE_LATERAL_RAISE,
+ EXERCISE_SEGMENT_TYPE_LAT_PULL_DOWN,
+ EXERCISE_SEGMENT_TYPE_LEG_CURL,
+ EXERCISE_SEGMENT_TYPE_LEG_EXTENSION,
+ EXERCISE_SEGMENT_TYPE_LEG_PRESS,
+ EXERCISE_SEGMENT_TYPE_LEG_RAISE,
+ EXERCISE_SEGMENT_TYPE_LUNGE,
+ EXERCISE_SEGMENT_TYPE_MOUNTAIN_CLIMBER,
+ EXERCISE_SEGMENT_TYPE_PLANK,
+ EXERCISE_SEGMENT_TYPE_PULL_UP,
+ EXERCISE_SEGMENT_TYPE_PUNCH,
+ EXERCISE_SEGMENT_TYPE_SHOULDER_PRESS,
+ EXERCISE_SEGMENT_TYPE_SINGLE_ARM_TRICEPS_EXTENSION,
+ EXERCISE_SEGMENT_TYPE_SIT_UP,
+ EXERCISE_SEGMENT_TYPE_SQUAT,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_FREESTYLE,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_BACKSTROKE,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_BREASTSTROKE,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_BUTTERFLY,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_MIXED,
+ EXERCISE_SEGMENT_TYPE_SWIMMING_OTHER,
+ EXERCISE_SEGMENT_TYPE_REST,
+ EXERCISE_SEGMENT_TYPE_PAUSE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExerciseSegmentTypes {}
+}
diff --git a/framework/java/android/healthconnect/datatypes/ExerciseSessionRecord.java b/framework/java/android/healthconnect/datatypes/ExerciseSessionRecord.java
index b09f02a..b1ff9a1 100644
--- a/framework/java/android/healthconnect/datatypes/ExerciseSessionRecord.java
+++ b/framework/java/android/healthconnect/datatypes/ExerciseSessionRecord.java
@@ -21,14 +21,18 @@
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
- * Captures any other exercise a user does for which there is not a custom record type. This can be
- * common fitness exercise like running or different sports.
+ * Captures exercise or a sequence of exercises. This can be a playing game like football or a
+ * sequence of fitness exercises.
*
- * <p>Each record needs a start time and end time. Records don't need to be back-to-back or directly
- * after each other, there can be gaps in between.
+ * <p>Each record needs a start time, end time and session type. In addition, each record has two
+ * optional independent lists of time intervals: {@link ExerciseSegment} represents particular
+ * exercise within session, {@link ExerciseLap} represents a lap time within session.
*/
@Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_EXERCISE_SESSION)
public final class ExerciseSessionRecord extends IntervalRecord {
@@ -38,6 +42,9 @@
private final CharSequence mTitle;
private final ExerciseRoute mRoute;
+ private final List<ExerciseSegment> mSegments;
+ private final List<ExerciseLap> mLaps;
+
/**
* @param metadata Metadata to be associated with the record. See {@link Metadata}.
* @param startTime Start time of this activity
@@ -58,12 +65,16 @@
@Nullable CharSequence notes,
@NonNull @ExerciseSessionType.ExerciseSessionTypes int exerciseType,
@Nullable CharSequence title,
- @Nullable ExerciseRoute route) {
+ @Nullable ExerciseRoute route,
+ @NonNull List<ExerciseSegment> segments,
+ @NonNull List<ExerciseLap> laps) {
super(metadata, startTime, startZoneOffset, endTime, endZoneOffset);
mNotes = notes;
mExerciseType = exerciseType;
mTitle = title;
mRoute = route;
+ mSegments = Collections.unmodifiableList(segments);
+ mLaps = Collections.unmodifiableList(laps);
}
/** Returns exerciseType of this session. */
@@ -90,6 +101,23 @@
return mRoute;
}
+ /**
+ * Returns segments of this session. Returns empty list if the session doesn't have exercise
+ * segments.
+ */
+ @NonNull
+ public List<ExerciseSegment> getSegments() {
+ return mSegments;
+ }
+
+ /**
+ * Returns laps of this session. Returns empty list if the session doesn't have exercise laps.
+ */
+ @NonNull
+ public List<ExerciseLap> getLaps() {
+ return mLaps;
+ }
+
/** Returns if this session has route. */
@NonNull
public boolean hasRoute() {
@@ -105,13 +133,19 @@
return getExerciseType() == that.getExerciseType()
&& RecordUtils.isEqualNullableCharSequences(getNotes(), that.getNotes())
&& RecordUtils.isEqualNullableCharSequences(getTitle(), that.getTitle())
- && Objects.equals(getRoute(), that.getRoute());
+ && Objects.equals(getRoute(), that.getRoute())
+ && Objects.equals(getSegments(), that.getSegments());
}
@Override
public int hashCode() {
return Objects.hash(
- super.hashCode(), getExerciseType(), getNotes(), getTitle(), getRoute());
+ super.hashCode(),
+ getExerciseType(),
+ getNotes(),
+ getTitle(),
+ getRoute(),
+ getSegments());
}
/** Builder class for {@link ExerciseSessionRecord} */
@@ -125,6 +159,8 @@
private CharSequence mNotes;
private CharSequence mTitle;
private ExerciseRoute mRoute;
+ private List<ExerciseSegment> mSegments;
+ private List<ExerciseLap> mLaps;
/**
* @param metadata Metadata to be associated with the record. See {@link Metadata}.
@@ -147,6 +183,8 @@
mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(Instant.now());
mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(Instant.now());
mExerciseType = exerciseType;
+ mSegments = new ArrayList<>();
+ mLaps = new ArrayList<>();
}
/** Sets the zone offset of the user when the session started */
@@ -200,6 +238,32 @@
return this;
}
+ /**
+ * Sets segments for this session.
+ *
+ * @param laps list of {@link ExerciseLap} of this session
+ */
+ @NonNull
+ public Builder setLaps(@NonNull List<ExerciseLap> laps) {
+ Objects.requireNonNull(laps);
+ mLaps.clear();
+ mLaps.addAll(laps);
+ return this;
+ }
+
+ /**
+ * Sets segments for this session.
+ *
+ * @param segments list of {@link ExerciseSegment} of this session
+ */
+ @NonNull
+ public Builder setSegments(@NonNull List<ExerciseSegment> segments) {
+ Objects.requireNonNull(segments);
+ mSegments.clear();
+ mSegments.addAll(segments);
+ return this;
+ }
+
/** Returns {@link ExerciseSessionRecord} */
@NonNull
public ExerciseSessionRecord build() {
@@ -212,7 +276,9 @@
mNotes,
mExerciseType,
mTitle,
- mRoute);
+ mRoute,
+ mSegments,
+ mLaps);
}
}
}
diff --git a/framework/java/android/healthconnect/datatypes/ExerciseSessionType.java b/framework/java/android/healthconnect/datatypes/ExerciseSessionType.java
index 1b33981..7b7bd39 100644
--- a/framework/java/android/healthconnect/datatypes/ExerciseSessionType.java
+++ b/framework/java/android/healthconnect/datatypes/ExerciseSessionType.java
@@ -203,6 +203,15 @@
/** Use this type for the other workout session. */
public static final int EXERCISE_SESSION_TYPE_OTHER_WORKOUT = 58;
+ /** Use this type for the stair climbing machine session. */
+ public static final int EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE = 59;
+
+ /** Use this type for elliptical workout. */
+ public static final int EXERCISE_SESSION_TYPE_ELLIPTICAL = 60;
+
+ /** Use this type for rowing machine. */
+ public static final int EXERCISE_SESSION_TYPE_ROWING_MACHINE = 61;
+
private ExerciseSessionType() {}
/** @hide */
@@ -218,6 +227,7 @@
EXERCISE_SESSION_TYPE_CALISTHENICS,
EXERCISE_SESSION_TYPE_CRICKET,
EXERCISE_SESSION_TYPE_DANCING,
+ EXERCISE_SESSION_TYPE_ELLIPTICAL,
EXERCISE_SESSION_TYPE_EXERCISE_CLASS,
EXERCISE_SESSION_TYPE_FENCING,
EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN,
@@ -239,6 +249,7 @@
EXERCISE_SESSION_TYPE_ROCK_CLIMBING,
EXERCISE_SESSION_TYPE_ROLLER_HOCKEY,
EXERCISE_SESSION_TYPE_ROWING,
+ EXERCISE_SESSION_TYPE_ROWING_MACHINE,
EXERCISE_SESSION_TYPE_RUGBY,
EXERCISE_SESSION_TYPE_RUNNING,
EXERCISE_SESSION_TYPE_RUNNING_TREADMILL,
@@ -252,6 +263,7 @@
EXERCISE_SESSION_TYPE_SOFTBALL,
EXERCISE_SESSION_TYPE_SQUASH,
EXERCISE_SESSION_TYPE_STAIR_CLIMBING,
+ EXERCISE_SESSION_TYPE_STAIR_CLIMBING_MACHINE,
EXERCISE_SESSION_TYPE_STRENGTH_TRAINING,
EXERCISE_SESSION_TYPE_STRETCHING,
EXERCISE_SESSION_TYPE_SURFING,
@@ -265,7 +277,7 @@
EXERCISE_SESSION_TYPE_WEIGHTLIFTING,
EXERCISE_SESSION_TYPE_WHEELCHAIR,
EXERCISE_SESSION_TYPE_OTHER_WORKOUT,
- EXERCISE_SESSION_TYPE_YOGA
+ EXERCISE_SESSION_TYPE_YOGA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExerciseSessionTypes {}
diff --git a/framework/java/android/healthconnect/datatypes/TimeInterval.java b/framework/java/android/healthconnect/datatypes/TimeInterval.java
new file mode 100644
index 0000000..d577316
--- /dev/null
+++ b/framework/java/android/healthconnect/datatypes/TimeInterval.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.datatypes;
+
+import android.annotation.NonNull;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * Represents time interval.
+ *
+ * <p>Each object has start time and end time. End time must be after start time.
+ *
+ * @hide
+ */
+public final class TimeInterval {
+ private final Instant mStartTime;
+ private final Instant mEndTime;
+
+ public TimeInterval(@NonNull Instant startTime, @NonNull Instant endTime) {
+ Objects.requireNonNull(startTime);
+ Objects.requireNonNull(endTime);
+ if (!endTime.isAfter(startTime)) {
+ throw new IllegalArgumentException("End time must be after start time.");
+ }
+ mStartTime = startTime;
+ mEndTime = endTime;
+ }
+
+ /*
+ * Returns start time of the interval.
+ */
+ @NonNull
+ public Instant getStartTime() {
+ return mStartTime;
+ }
+
+ /*
+ * Returns end time of the interval.
+ */
+ @NonNull
+ public Instant getEndTime() {
+ return mEndTime;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TimeInterval)) return false;
+ TimeInterval that = (TimeInterval) o;
+ return getStartTime().equals(that.getStartTime()) && getEndTime().equals(that.getEndTime());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getStartTime(), getEndTime());
+ }
+}
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseLapTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseLapTest.java
new file mode 100644
index 0000000..b8ac2b4
--- /dev/null
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseLapTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.healthconnect.datatypes.ExerciseLap;
+import android.healthconnect.datatypes.units.Length;
+
+import org.junit.Test;
+
+import java.time.Instant;
+
+public class ExerciseLapTest {
+ private static final Instant START_TIME = Instant.ofEpochMilli((long) 1e1);
+ private static final Instant END_TIME = Instant.ofEpochMilli((long) 1e2);
+
+ @Test
+ public void testExerciseLap_buildLap_buildCorrectObject() {
+ ExerciseLap lap =
+ new ExerciseLap.Builder(START_TIME, END_TIME)
+ .setLength(Length.fromMeters(12))
+ .build();
+ assertThat(lap.getStartTime()).isEqualTo(START_TIME);
+ assertThat(lap.getEndTime()).isEqualTo(END_TIME);
+ assertThat(lap.getLength()).isEqualTo(Length.fromMeters(12));
+ }
+
+ @Test
+ public void testExerciseLap_buildWithoutLength_lengthIsNull() {
+ ExerciseLap lap = new ExerciseLap.Builder(START_TIME, END_TIME).build();
+ assertThat(lap.getLength()).isNull();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExerciseLap_endTimeEarlierThanStartTime_throwsException() {
+ new ExerciseLap.Builder(END_TIME, START_TIME).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExerciseLap_lengthIsNegative_throwsException() {
+ new ExerciseLap.Builder(START_TIME, END_TIME).setLength(Length.fromMeters(-10)).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExerciseLap_lengthIsHuge_throwsException() {
+ new ExerciseLap.Builder(START_TIME, END_TIME).setLength(Length.fromMeters(1e10)).build();
+ }
+}
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseSegmentTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseSegmentTest.java
new file mode 100644
index 0000000..52ce6e5
--- /dev/null
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseSegmentTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.healthconnect.datatypes.ExerciseSegment;
+import android.healthconnect.datatypes.ExerciseSegmentType;
+
+import org.junit.Test;
+
+import java.time.Instant;
+
+public class ExerciseSegmentTest {
+ private static final Instant START_TIME = Instant.ofEpochMilli((long) 1e1);
+ private static final Instant END_TIME = Instant.ofEpochMilli((long) 1e2);
+
+ @Test
+ public void testExerciseSegment_buildSegment_buildCorrectObject() {
+ ExerciseSegment segment =
+ new ExerciseSegment.Builder(
+ START_TIME,
+ END_TIME,
+ ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL)
+ .setRepetitionsCount(10)
+ .build();
+ assertThat(segment.getStartTime()).isEqualTo(START_TIME);
+ assertThat(segment.getEndTime()).isEqualTo(END_TIME);
+ assertThat(segment.getSegmentType())
+ .isEqualTo(ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL);
+ assertThat(segment.getRepetitionsCount()).isEqualTo(10);
+ }
+
+ @Test
+ public void testExerciseSegment_buildWithoutRepetitions_repetitionsIsZero() {
+ ExerciseSegment segment =
+ new ExerciseSegment.Builder(
+ START_TIME,
+ END_TIME,
+ ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL)
+ .build();
+ assertThat(segment.getRepetitionsCount()).isEqualTo(0);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExerciseSegment_endTimeEarlierThanStartTime_throwsException() {
+ new ExerciseSegment.Builder(
+ END_TIME, START_TIME, ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExerciseSegment_repetitionsIsNegative_throwsException() {
+ new ExerciseSegment.Builder(
+ START_TIME, END_TIME, ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL)
+ .setRepetitionsCount(-1)
+ .build();
+ }
+}
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
index 7fea8ec..81c7a98 100644
--- a/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
@@ -18,10 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import android.healthconnect.datatypes.ExerciseLap;
import android.healthconnect.datatypes.ExerciseRoute;
+import android.healthconnect.datatypes.ExerciseSegment;
+import android.healthconnect.datatypes.ExerciseSegmentType;
import android.healthconnect.datatypes.ExerciseSessionRecord;
import android.healthconnect.datatypes.ExerciseSessionType;
import android.healthconnect.datatypes.Metadata;
+import android.healthconnect.datatypes.units.Length;
import androidx.test.runner.AndroidJUnit4;
@@ -30,6 +34,7 @@
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class ExerciseSessionRecordTest {
@@ -51,6 +56,8 @@
assertThat(record.getRoute()).isNull();
assertThat(record.getNotes()).isNull();
assertThat(record.getTitle()).isNull();
+ assertThat(record.getSegments()).isEmpty();
+ assertThat(record.getLaps()).isEmpty();
}
@Test
@@ -78,6 +85,20 @@
ExerciseRoute route = TestUtils.buildExerciseRoute();
CharSequence notes = "rain";
CharSequence title = "Morning training";
+ List<ExerciseSegment> segmentList =
+ List.of(
+ new ExerciseSegment.Builder(
+ START_TIME,
+ END_TIME,
+ ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_ARM_CURL)
+ .setRepetitionsCount(10)
+ .build());
+
+ List<ExerciseLap> lapsList =
+ List.of(
+ new ExerciseLap.Builder(START_TIME, END_TIME)
+ .setLength(Length.fromMeters(10))
+ .build());
ExerciseSessionRecord record =
new ExerciseSessionRecord.Builder(
TestUtils.generateMetadata(),
@@ -89,6 +110,8 @@
.setStartZoneOffset(ZoneOffset.MIN)
.setNotes(notes)
.setTitle(title)
+ .setSegments(segmentList)
+ .setLaps(lapsList)
.build();
assertThat(record.hasRoute()).isTrue();
assertThat(record.getRoute()).isEqualTo(route);
@@ -97,6 +120,8 @@
assertThat(record.getExerciseType())
.isEqualTo(ExerciseSessionType.EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN);
assertThat(record.getNotes()).isEqualTo(notes);
+ assertThat(record.getSegments()).isEqualTo(segmentList);
+ assertThat(record.getLaps()).isEqualTo(lapsList);
assertThat(record.getTitle()).isEqualTo(title);
}
}
diff --git a/tests/unittests/src/android/healthconnect/ExerciseSegmentTypesTest.java b/tests/unittests/src/android/healthconnect/ExerciseSegmentTypesTest.java
new file mode 100644
index 0000000..c06405c
--- /dev/null
+++ b/tests/unittests/src/android/healthconnect/ExerciseSegmentTypesTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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 android.healthconnect;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.healthconnect.datatypes.ExerciseSegmentType;
+import android.util.ArraySet;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ExerciseSegmentTypesTest {
+
+ @Test
+ public void testExerciseSegmentTypeStrings_allValuesAreUnique() throws IllegalAccessException {
+ List<Integer> types = getAllDeclaredExerciseSegmentTypes();
+ Set<Integer> uniqueTypes = new ArraySet<>(types);
+ assertWithMessage("All values of segment types must be unique.")
+ .that(types.size())
+ .isEqualTo(uniqueTypes.size());
+ }
+
+ private List<Integer> getAllDeclaredExerciseSegmentTypes() throws IllegalAccessException {
+ List<Integer> types = new ArrayList<>();
+ for (Field field : ExerciseSegmentType.class.getDeclaredFields()) {
+ if (field.getType().equals(int.class) && Modifier.isPublic(field.getModifiers())) {
+ types.add((Integer) field.get(ExerciseSegmentType.class));
+ }
+ }
+ return types;
+ }
+}