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