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 package android.health.connect.datatypes;
17 
18 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_FLOORS_CLIMBED;
19 
20 import android.annotation.FloatRange;
21 import android.annotation.NonNull;
22 import android.health.connect.HealthConnectManager;
23 import android.health.connect.datatypes.validation.ValidationUtils;
24 import android.health.connect.internal.datatypes.FloorsClimbedRecordInternal;
25 
26 import java.time.Instant;
27 import java.time.ZoneOffset;
28 import java.util.Objects;
29 
30 /** Captures the number of floors climbed by the user between the start and end time. */
31 @Identifier(recordIdentifier = RECORD_TYPE_FLOORS_CLIMBED)
32 public final class FloorsClimbedRecord extends IntervalRecord {
33     /**
34      * Metric identifier to get total floors climbed using aggregate APIs in {@link
35      * HealthConnectManager}
36      */
37     @NonNull
38     public static final AggregationType<Double> FLOORS_CLIMBED_TOTAL =
39             new AggregationType<>(
40                     AggregationType.AggregationTypeIdentifier
41                             .FLOORS_CLIMBED_RECORD_FLOORS_CLIMBED_TOTAL,
42                     AggregationType.SUM,
43                     RECORD_TYPE_FLOORS_CLIMBED,
44                     Double.class);
45 
46     private final double mFloors;
47 
48     /**
49      * @param metadata Metadata to be associated with the record. See {@link Metadata}.
50      * @param startTime Start time of this activity
51      * @param startZoneOffset Zone offset of the user when the activity started
52      * @param endTime End time of this activity
53      * @param endZoneOffset Zone offset of the user when the activity finished
54      * @param floors Number of floors of this activity. Valid range: 0-1000000.
55      * @param skipValidation Boolean flag to skip validation of record values.
56      */
FloorsClimbedRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @FloatRange(from = 0f, to = 1000000f) double floors, boolean skipValidation)57     private FloorsClimbedRecord(
58             @NonNull Metadata metadata,
59             @NonNull Instant startTime,
60             @NonNull ZoneOffset startZoneOffset,
61             @NonNull Instant endTime,
62             @NonNull ZoneOffset endZoneOffset,
63             @FloatRange(from = 0f, to = 1000000f) double floors,
64             boolean skipValidation) {
65         super(
66                 metadata,
67                 startTime,
68                 startZoneOffset,
69                 endTime,
70                 endZoneOffset,
71                 skipValidation,
72                 /* enforceFutureTimeRestrictions= */ true);
73         if (!skipValidation) {
74             ValidationUtils.requireInRange(floors, 0.0, 1000000.0, "floors");
75         }
76         mFloors = floors;
77     }
78 
79     /**
80      * @return number of floors climbed.
81      */
82     @FloatRange(from = 0f, to = 1000000f)
getFloors()83     public double getFloors() {
84         return mFloors;
85     }
86 
87     /**
88      * Indicates whether some other object is "equal to" this one.
89      *
90      * @param o the reference object with which to compare.
91      * @return {@code true} if this object is the same as the obj
92      */
93     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
94     @Override
equals(Object o)95     public boolean equals(Object o) {
96         if (this == o) return true;
97         if (!super.equals(o)) return false;
98         FloorsClimbedRecord that = (FloorsClimbedRecord) o;
99         return getFloors() == that.getFloors();
100     }
101 
102     /**
103      * @return a hash code value for this object.
104      */
105     @Override
hashCode()106     public int hashCode() {
107         return Objects.hash(super.hashCode(), getFloors());
108     }
109 
110     /** Builder class for {@link FloorsClimbedRecord} */
111     public static final class Builder {
112         private final Metadata mMetadata;
113         private final Instant mStartTime;
114         private final Instant mEndTime;
115         private ZoneOffset mStartZoneOffset;
116         private ZoneOffset mEndZoneOffset;
117         private final double mFloors;
118 
119         /**
120          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
121          * @param startTime Start time of this activity
122          * @param endTime End time of this activity
123          * @param floors Number of floors. Required field. Valid range: 0-1000000.
124          */
Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, double floors)125         public Builder(
126                 @NonNull Metadata metadata,
127                 @NonNull Instant startTime,
128                 @NonNull Instant endTime,
129                 double floors) {
130             Objects.requireNonNull(metadata);
131             Objects.requireNonNull(startTime);
132             Objects.requireNonNull(endTime);
133             mMetadata = metadata;
134             mStartTime = startTime;
135             mEndTime = endTime;
136             mFloors = floors;
137             mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime);
138             mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime);
139         }
140 
141         /** Sets the zone offset of the user when the activity started */
142         @NonNull
setStartZoneOffset(@onNull ZoneOffset startZoneOffset)143         public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) {
144             Objects.requireNonNull(startZoneOffset);
145 
146             mStartZoneOffset = startZoneOffset;
147             return this;
148         }
149 
150         /** Sets the zone offset of the user when the activity ended */
151         @NonNull
setEndZoneOffset(@onNull ZoneOffset endZoneOffset)152         public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) {
153             Objects.requireNonNull(endZoneOffset);
154 
155             mEndZoneOffset = endZoneOffset;
156             return this;
157         }
158 
159         /** Sets the start zone offset of this record to system default. */
160         @NonNull
clearStartZoneOffset()161         public Builder clearStartZoneOffset() {
162             mStartZoneOffset = RecordUtils.getDefaultZoneOffset();
163             return this;
164         }
165 
166         /** Sets the start zone offset of this record to system default. */
167         @NonNull
clearEndZoneOffset()168         public Builder clearEndZoneOffset() {
169             mEndZoneOffset = RecordUtils.getDefaultZoneOffset();
170             return this;
171         }
172 
173         /**
174          * @return Object of {@link FloorsClimbedRecord} without validating the values.
175          * @hide
176          */
177         @NonNull
buildWithoutValidation()178         public FloorsClimbedRecord buildWithoutValidation() {
179             return new FloorsClimbedRecord(
180                     mMetadata,
181                     mStartTime,
182                     mStartZoneOffset,
183                     mEndTime,
184                     mEndZoneOffset,
185                     mFloors,
186                     true);
187         }
188 
189         /**
190          * @return Object of {@link FloorsClimbedRecord}
191          */
192         @NonNull
build()193         public FloorsClimbedRecord build() {
194             return new FloorsClimbedRecord(
195                     mMetadata,
196                     mStartTime,
197                     mStartZoneOffset,
198                     mEndTime,
199                     mEndZoneOffset,
200                     mFloors,
201                     false);
202         }
203     }
204 
205     /** @hide */
206     @Override
toRecordInternal()207     public FloorsClimbedRecordInternal toRecordInternal() {
208         FloorsClimbedRecordInternal recordInternal =
209                 (FloorsClimbedRecordInternal)
210                         new FloorsClimbedRecordInternal()
211                                 .setUuid(getMetadata().getId())
212                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
213                                 .setLastModifiedTime(
214                                         getMetadata().getLastModifiedTime().toEpochMilli())
215                                 .setClientRecordId(getMetadata().getClientRecordId())
216                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
217                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
218                                 .setModel(getMetadata().getDevice().getModel())
219                                 .setDeviceType(getMetadata().getDevice().getType())
220                                 .setRecordingMethod(getMetadata().getRecordingMethod());
221         recordInternal.setStartTime(getStartTime().toEpochMilli());
222         recordInternal.setEndTime(getEndTime().toEpochMilli());
223         recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds());
224         recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds());
225         recordInternal.setFloors(mFloors);
226         return recordInternal;
227     }
228 }
229