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_HEIGHT; 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.HeightRecordInternal; 25 26 import java.time.Instant; 27 import java.time.ZoneOffset; 28 import java.util.Objects; 29 30 /** Captures the user's height. */ 31 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_HEIGHT) 32 public final class HeightRecord extends InstantRecord { 33 34 private final Length mHeight; 35 36 /** 37 * Metric identifier to get average height using aggregate APIs in {@link HealthConnectManager} 38 */ 39 @android.annotation.NonNull 40 public static final AggregationType<Length> HEIGHT_AVG = 41 new AggregationType<>( 42 AggregationType.AggregationTypeIdentifier.HEIGHT_RECORD_HEIGHT_AVG, 43 AggregationType.AVG, 44 RECORD_TYPE_HEIGHT, 45 Length.class); 46 47 /** 48 * Metric identifier to get minimum height using aggregate APIs in {@link HealthConnectManager} 49 */ 50 @android.annotation.NonNull 51 public static final AggregationType<Length> HEIGHT_MIN = 52 new AggregationType<>( 53 AggregationType.AggregationTypeIdentifier.HEIGHT_RECORD_HEIGHT_MIN, 54 AggregationType.MIN, 55 RECORD_TYPE_HEIGHT, 56 Length.class); 57 58 /** 59 * Metric identifier to get maximum height using aggregate APIs in {@link HealthConnectManager} 60 */ 61 @android.annotation.NonNull 62 public static final AggregationType<Length> HEIGHT_MAX = 63 new AggregationType<>( 64 AggregationType.AggregationTypeIdentifier.HEIGHT_RECORD_HEIGHT_MAX, 65 AggregationType.MAX, 66 RECORD_TYPE_HEIGHT, 67 Length.class); 68 69 /** 70 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 71 * @param time Start time of this activity 72 * @param zoneOffset Zone offset of the user when the activity started 73 * @param height Height of this activity 74 * @param skipValidation Boolean flag to skip validation of record values. 75 */ HeightRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @NonNull Length height, boolean skipValidation)76 private HeightRecord( 77 @NonNull Metadata metadata, 78 @NonNull Instant time, 79 @NonNull ZoneOffset zoneOffset, 80 @NonNull Length height, 81 boolean skipValidation) { 82 super(metadata, time, zoneOffset, skipValidation); 83 Objects.requireNonNull(metadata); 84 Objects.requireNonNull(time); 85 Objects.requireNonNull(zoneOffset); 86 Objects.requireNonNull(height); 87 if (!skipValidation) { 88 ValidationUtils.requireInRange(height.getInMeters(), 0.0, 3.0, "height"); 89 } 90 mHeight = height; 91 } 92 /** 93 * @return height in {@link Length} unit. 94 */ 95 @NonNull getHeight()96 public Length getHeight() { 97 return mHeight; 98 } 99 100 /** 101 * Indicates whether some other object is "equal to" this one. 102 * 103 * @param o the reference object with which to compare. 104 * @return {@code true} if this object is the same as the obj 105 */ 106 @Override equals(Object o)107 public boolean equals(Object o) { 108 if (this == o) return true; 109 if (!super.equals(o)) return false; 110 HeightRecord that = (HeightRecord) o; 111 return getHeight().equals(that.getHeight()); 112 } 113 114 /** Returns a hash code value for the object. */ 115 @Override hashCode()116 public int hashCode() { 117 return Objects.hash(super.hashCode(), getHeight()); 118 } 119 120 /** Builder class for {@link HeightRecord} */ 121 public static final class Builder { 122 private final Metadata mMetadata; 123 private final Instant mTime; 124 private ZoneOffset mZoneOffset; 125 private final Length mHeight; 126 127 /** 128 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 129 * @param time Start time of this activity 130 * @param height Height in {@link Length} unit. Required field. Valid range: 0-3 meters. 131 */ Builder(@onNull Metadata metadata, @NonNull Instant time, @NonNull Length height)132 public Builder(@NonNull Metadata metadata, @NonNull Instant time, @NonNull Length height) { 133 Objects.requireNonNull(metadata); 134 Objects.requireNonNull(time); 135 Objects.requireNonNull(height); 136 mMetadata = metadata; 137 mTime = time; 138 mHeight = height; 139 mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time); 140 } 141 142 /** Sets the zone offset of the user when the activity happened */ 143 @NonNull setZoneOffset(@onNull ZoneOffset zoneOffset)144 public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) { 145 Objects.requireNonNull(zoneOffset); 146 mZoneOffset = zoneOffset; 147 return this; 148 } 149 150 /** Sets the zone offset of this record to system default. */ 151 @NonNull clearZoneOffset()152 public Builder clearZoneOffset() { 153 mZoneOffset = RecordUtils.getDefaultZoneOffset(); 154 return this; 155 } 156 157 /** 158 * @return Object of {@link HeightRecord} without validating the values. 159 * @hide 160 */ 161 @NonNull buildWithoutValidation()162 public HeightRecord buildWithoutValidation() { 163 return new HeightRecord(mMetadata, mTime, mZoneOffset, mHeight, true); 164 } 165 166 /** 167 * @return Object of {@link HeightRecord} 168 */ 169 @NonNull build()170 public HeightRecord build() { 171 return new HeightRecord(mMetadata, mTime, mZoneOffset, mHeight, false); 172 } 173 } 174 175 /** @hide */ 176 @Override toRecordInternal()177 public HeightRecordInternal toRecordInternal() { 178 HeightRecordInternal recordInternal = 179 (HeightRecordInternal) 180 new HeightRecordInternal() 181 .setUuid(getMetadata().getId()) 182 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 183 .setLastModifiedTime( 184 getMetadata().getLastModifiedTime().toEpochMilli()) 185 .setClientRecordId(getMetadata().getClientRecordId()) 186 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 187 .setManufacturer(getMetadata().getDevice().getManufacturer()) 188 .setModel(getMetadata().getDevice().getModel()) 189 .setDeviceType(getMetadata().getDevice().getType()) 190 .setRecordingMethod(getMetadata().getRecordingMethod()); 191 recordInternal.setTime(getTime().toEpochMilli()); 192 recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds()); 193 recordInternal.setHeight(mHeight.getInMeters()); 194 return recordInternal; 195 } 196 } 197