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