1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.health.connect.datatypes;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.health.connect.datatypes.units.Length;
22 import android.health.connect.datatypes.validation.ValidationUtils;
23 import android.health.connect.internal.datatypes.ExerciseLapInternal;
24 
25 import java.time.Instant;
26 import java.util.Objects;
27 
28 /**
29  * Captures the time of a lap within exercise session. Part of {@link ExerciseSessionRecord}.
30  *
31  * <p>Each record contains the start and end time and optional {@link Length} of the lap (e.g. pool
32  * length while swimming or a track lap while running). There may or may not be direct correlation
33  * with {@link ExerciseSegment} start and end times, e.g. {@link ExerciseSessionRecord} of type
34  * running without any segments can be divided as laps of different lengths.
35  */
36 public final class ExerciseLap implements TimeInterval.TimeIntervalHolder {
37     private static final int MAX_LAP_LENGTH_METRES = 1000000;
38 
39     private final TimeInterval mInterval;
40     private final Length mLength;
41 
42     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
ExerciseLap( @onNull TimeInterval interval, @Nullable Length length, boolean skipValidation)43     private ExerciseLap(
44             @NonNull TimeInterval interval, @Nullable Length length, boolean skipValidation) {
45         Objects.requireNonNull(interval);
46         if (!skipValidation) {
47             ValidationUtils.requireInRangeIfExists(
48                     length,
49                     Length.fromMeters(0.0),
50                     Length.fromMeters(MAX_LAP_LENGTH_METRES),
51                     "length");
52         }
53         mInterval = interval;
54         mLength = length;
55     }
56 
57     /*
58      * Returns Length of the lap.
59      */
60     @Nullable
getLength()61     public Length getLength() {
62         return mLength;
63     }
64 
65     /*
66      * Returns start time of the lap.
67      */
68     @NonNull
getStartTime()69     public Instant getStartTime() {
70         return mInterval.getStartTime();
71     }
72 
73     /*
74      * Returns end time of the lap.
75      */
76     @NonNull
getEndTime()77     public Instant getEndTime() {
78         return mInterval.getEndTime();
79     }
80 
81     /** @hide */
82     @Override
getInterval()83     public TimeInterval getInterval() {
84         return mInterval;
85     }
86 
87     /** @hide */
getType()88     public int getType() {
89         return 0;
90     }
91 
92     @Override
equals(Object o)93     public boolean equals(Object o) {
94         if (this == o) return true;
95         if (!(o instanceof ExerciseLap)) return false;
96         ExerciseLap that = (ExerciseLap) o;
97         return Objects.equals(mInterval, that.mInterval)
98                 && Objects.equals(getLength(), that.getLength());
99     }
100 
101     @Override
hashCode()102     public int hashCode() {
103         return Objects.hash(mInterval, getLength());
104     }
105 
106     /** @hide */
107     @NonNull
toExerciseLapInternal()108     public ExerciseLapInternal toExerciseLapInternal() {
109         ExerciseLapInternal internalLap =
110                 new ExerciseLapInternal()
111                         .setStarTime(getStartTime().toEpochMilli())
112                         .setEndTime(getEndTime().toEpochMilli());
113         if (getLength() != null) {
114             internalLap.setLength(getLength().getInMeters());
115         }
116 
117         return internalLap;
118     }
119 
120     /** Builder class for {@link ExerciseLap} */
121     public static final class Builder {
122         private final TimeInterval mInterval;
123         private Length mLength;
124 
125         @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
Builder(@onNull Instant startTime, @NonNull Instant endTime)126         public Builder(@NonNull Instant startTime, @NonNull Instant endTime) {
127             mInterval = new TimeInterval(startTime, endTime);
128         }
129 
130         /**
131          * Sets the length of this lap
132          *
133          * @param length Length of the lap, in {@link Length} unit. Optional field. Valid range:
134          *     0-1000000 meters.
135          */
136         @NonNull
setLength(@onNull Length length)137         public ExerciseLap.Builder setLength(@NonNull Length length) {
138             Objects.requireNonNull(length);
139             mLength = length;
140             return this;
141         }
142 
143         /**
144          * @return Object of {@link ExerciseLap} without validating the values.
145          * @hide
146          */
147         @NonNull
buildWithoutValidation()148         public ExerciseLap buildWithoutValidation() {
149             return new ExerciseLap(mInterval, mLength, true);
150         }
151 
152         /** Builds {@link ExerciseLap} instance. */
153         @NonNull
build()154         public ExerciseLap build() {
155             return new ExerciseLap(mInterval, mLength, false);
156         }
157     }
158 }
159