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_HYDRATION; 19 20 import android.annotation.NonNull; 21 import android.health.connect.HealthConnectManager; 22 import android.health.connect.datatypes.units.Volume; 23 import android.health.connect.datatypes.validation.ValidationUtils; 24 import android.health.connect.internal.datatypes.HydrationRecordInternal; 25 26 import java.time.Instant; 27 import java.time.ZoneOffset; 28 import java.util.Objects; 29 30 /** Captures the amount of liquids user had in a single drink. */ 31 @Identifier(recordIdentifier = RECORD_TYPE_HYDRATION) 32 public final class HydrationRecord extends IntervalRecord { 33 /** 34 * Metric identifier to get hydration total volume using aggregate APIs in {@link 35 * HealthConnectManager} 36 */ 37 @NonNull 38 public static final AggregationType<Volume> VOLUME_TOTAL = 39 new AggregationType<>( 40 AggregationType.AggregationTypeIdentifier.HYDRATION_RECORD_VOLUME_TOTAL, 41 AggregationType.SUM, 42 RECORD_TYPE_HYDRATION, 43 Volume.class); 44 45 private final Volume mVolume; 46 47 /** 48 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 49 * @param startTime Start time of this activity 50 * @param startZoneOffset Zone offset of the user when the activity started 51 * @param endTime End time of this activity 52 * @param endZoneOffset Zone offset of the user when the activity finished 53 * @param volume Volume of this activity 54 * @param skipValidation Boolean flag to skip validation of record values. 55 */ HydrationRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @NonNull Volume volume, boolean skipValidation)56 private HydrationRecord( 57 @NonNull Metadata metadata, 58 @NonNull Instant startTime, 59 @NonNull ZoneOffset startZoneOffset, 60 @NonNull Instant endTime, 61 @NonNull ZoneOffset endZoneOffset, 62 @NonNull Volume volume, 63 boolean skipValidation) { 64 super( 65 metadata, 66 startTime, 67 startZoneOffset, 68 endTime, 69 endZoneOffset, 70 skipValidation, 71 /* enforceFutureTimeRestrictions= */ true); 72 Objects.requireNonNull(volume); 73 if (!skipValidation) { 74 ValidationUtils.requireInRange(volume.getInLiters(), 0.0, 100.0, "volume"); 75 } 76 mVolume = volume; 77 } 78 79 /** 80 * @return hydration volume 81 */ 82 @NonNull getVolume()83 public Volume getVolume() { 84 return mVolume; 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 HydrationRecord that = (HydrationRecord) o; 99 return Objects.equals(getVolume(), that.getVolume()); 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(), getVolume()); 108 } 109 110 /** Builder class for {@link HydrationRecord} */ 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 Volume mVolume; 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 volume Volume of the liquids in {@link Volume} unit. Required field. Valid range: 124 * 0-100 liters. 125 */ Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, @NonNull Volume volume)126 public Builder( 127 @NonNull Metadata metadata, 128 @NonNull Instant startTime, 129 @NonNull Instant endTime, 130 @NonNull Volume volume) { 131 Objects.requireNonNull(metadata); 132 Objects.requireNonNull(startTime); 133 Objects.requireNonNull(endTime); 134 Objects.requireNonNull(volume); 135 mMetadata = metadata; 136 mStartTime = startTime; 137 mEndTime = endTime; 138 mVolume = volume; 139 mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime); 140 mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime); 141 } 142 143 /** Sets the zone offset of the user when the activity started */ 144 @NonNull setStartZoneOffset(@onNull ZoneOffset startZoneOffset)145 public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) { 146 Objects.requireNonNull(startZoneOffset); 147 148 mStartZoneOffset = startZoneOffset; 149 return this; 150 } 151 152 /** Sets the zone offset of the user when the activity ended */ 153 @NonNull setEndZoneOffset(@onNull ZoneOffset endZoneOffset)154 public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) { 155 Objects.requireNonNull(endZoneOffset); 156 157 mEndZoneOffset = endZoneOffset; 158 return this; 159 } 160 161 /** Sets the start zone offset of this record to system default. */ 162 @NonNull clearStartZoneOffset()163 public Builder clearStartZoneOffset() { 164 mStartZoneOffset = RecordUtils.getDefaultZoneOffset(); 165 return this; 166 } 167 168 /** Sets the start zone offset of this record to system default. */ 169 @NonNull clearEndZoneOffset()170 public Builder clearEndZoneOffset() { 171 mEndZoneOffset = RecordUtils.getDefaultZoneOffset(); 172 return this; 173 } 174 175 /** 176 * @return Object of {@link HydrationRecord} without validating the values. 177 * @hide 178 */ 179 @NonNull buildWithoutValidation()180 public HydrationRecord buildWithoutValidation() { 181 return new HydrationRecord( 182 mMetadata, 183 mStartTime, 184 mStartZoneOffset, 185 mEndTime, 186 mEndZoneOffset, 187 mVolume, 188 true); 189 } 190 191 /** 192 * @return Object of {@link HydrationRecord} 193 */ 194 @NonNull build()195 public HydrationRecord build() { 196 return new HydrationRecord( 197 mMetadata, 198 mStartTime, 199 mStartZoneOffset, 200 mEndTime, 201 mEndZoneOffset, 202 mVolume, 203 false); 204 } 205 } 206 207 /** @hide */ 208 @Override toRecordInternal()209 public HydrationRecordInternal toRecordInternal() { 210 HydrationRecordInternal recordInternal = 211 (HydrationRecordInternal) 212 new HydrationRecordInternal() 213 .setUuid(getMetadata().getId()) 214 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 215 .setLastModifiedTime( 216 getMetadata().getLastModifiedTime().toEpochMilli()) 217 .setClientRecordId(getMetadata().getClientRecordId()) 218 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 219 .setManufacturer(getMetadata().getDevice().getManufacturer()) 220 .setModel(getMetadata().getDevice().getModel()) 221 .setDeviceType(getMetadata().getDevice().getType()) 222 .setRecordingMethod(getMetadata().getRecordingMethod()); 223 recordInternal.setStartTime(getStartTime().toEpochMilli()); 224 recordInternal.setEndTime(getEndTime().toEpochMilli()); 225 recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds()); 226 recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds()); 227 recordInternal.setVolume(mVolume.getInLiters()); 228 return recordInternal; 229 } 230 } 231