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 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 107 @Override equals(Object o)108 public boolean equals(Object o) { 109 if (this == o) return true; 110 if (!super.equals(o)) return false; 111 HeightRecord that = (HeightRecord) o; 112 return getHeight().equals(that.getHeight()); 113 } 114 115 /** Returns a hash code value for the object. */ 116 @Override hashCode()117 public int hashCode() { 118 return Objects.hash(super.hashCode(), getHeight()); 119 } 120 121 /** Builder class for {@link HeightRecord} */ 122 public static final class Builder { 123 private final Metadata mMetadata; 124 private final Instant mTime; 125 private ZoneOffset mZoneOffset; 126 private final Length mHeight; 127 128 /** 129 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 130 * @param time Start time of this activity 131 * @param height Height in {@link Length} unit. Required field. Valid range: 0-3 meters. 132 */ Builder(@onNull Metadata metadata, @NonNull Instant time, @NonNull Length height)133 public Builder(@NonNull Metadata metadata, @NonNull Instant time, @NonNull Length height) { 134 Objects.requireNonNull(metadata); 135 Objects.requireNonNull(time); 136 Objects.requireNonNull(height); 137 mMetadata = metadata; 138 mTime = time; 139 mHeight = height; 140 mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time); 141 } 142 143 /** Sets the zone offset of the user when the activity happened */ 144 @NonNull setZoneOffset(@onNull ZoneOffset zoneOffset)145 public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) { 146 Objects.requireNonNull(zoneOffset); 147 mZoneOffset = zoneOffset; 148 return this; 149 } 150 151 /** Sets the zone offset of this record to system default. */ 152 @NonNull clearZoneOffset()153 public Builder clearZoneOffset() { 154 mZoneOffset = RecordUtils.getDefaultZoneOffset(); 155 return this; 156 } 157 158 /** 159 * @return Object of {@link HeightRecord} without validating the values. 160 * @hide 161 */ 162 @NonNull buildWithoutValidation()163 public HeightRecord buildWithoutValidation() { 164 return new HeightRecord(mMetadata, mTime, mZoneOffset, mHeight, true); 165 } 166 167 /** 168 * @return Object of {@link HeightRecord} 169 */ 170 @NonNull build()171 public HeightRecord build() { 172 return new HeightRecord(mMetadata, mTime, mZoneOffset, mHeight, false); 173 } 174 } 175 176 /** @hide */ 177 @Override toRecordInternal()178 public HeightRecordInternal toRecordInternal() { 179 HeightRecordInternal recordInternal = 180 (HeightRecordInternal) 181 new HeightRecordInternal() 182 .setUuid(getMetadata().getId()) 183 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 184 .setLastModifiedTime( 185 getMetadata().getLastModifiedTime().toEpochMilli()) 186 .setClientRecordId(getMetadata().getClientRecordId()) 187 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 188 .setManufacturer(getMetadata().getDevice().getManufacturer()) 189 .setModel(getMetadata().getDevice().getModel()) 190 .setDeviceType(getMetadata().getDevice().getType()) 191 .setRecordingMethod(getMetadata().getRecordingMethod()); 192 recordInternal.setTime(getTime().toEpochMilli()); 193 recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds()); 194 recordInternal.setHeight(mHeight.getInMeters()); 195 return recordInternal; 196 } 197 } 198