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.validation.ValidationUtils.validateIntDefValue; 19 20 import android.annotation.FloatRange; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.health.connect.datatypes.validation.ValidationUtils; 24 import android.health.connect.internal.datatypes.Vo2MaxRecordInternal; 25 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.time.Instant; 29 import java.time.ZoneOffset; 30 import java.util.Objects; 31 import java.util.Set; 32 33 /** Capture user's VO2 max score and optionally the measurement method. */ 34 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_VO2_MAX) 35 public final class Vo2MaxRecord extends InstantRecord { 36 37 private final int mMeasurementMethod; 38 private final double mVo2MillilitersPerMinuteKilogram; 39 40 /** 41 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 42 * @param time Start time of this activity 43 * @param zoneOffset Zone offset of the user when the activity started 44 * @param measurementMethod MeasurementMethod of this activity 45 * @param vo2MillilitersPerMinuteKilogram Vo2MillilitersPerMinuteKilogram of this activity 46 * @param skipValidation Boolean flag to skip validation of record values. 47 */ Vo2MaxRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @Vo2MaxMeasurementMethod.Vo2MaxMeasurementMethodTypes int measurementMethod, double vo2MillilitersPerMinuteKilogram, boolean skipValidation)48 private Vo2MaxRecord( 49 @NonNull Metadata metadata, 50 @NonNull Instant time, 51 @NonNull ZoneOffset zoneOffset, 52 @Vo2MaxMeasurementMethod.Vo2MaxMeasurementMethodTypes int measurementMethod, 53 double vo2MillilitersPerMinuteKilogram, 54 boolean skipValidation) { 55 super(metadata, time, zoneOffset, skipValidation); 56 Objects.requireNonNull(metadata); 57 Objects.requireNonNull(time); 58 Objects.requireNonNull(zoneOffset); 59 validateIntDefValue( 60 measurementMethod, 61 Vo2MaxMeasurementMethod.VALID_TYPES, 62 Vo2MaxMeasurementMethod.class.getSimpleName()); 63 if (!skipValidation) { 64 ValidationUtils.requireInRange( 65 vo2MillilitersPerMinuteKilogram, 0.0, 100.0, "vo2MillilitersPerMinuteKilogram"); 66 } 67 mMeasurementMethod = measurementMethod; 68 mVo2MillilitersPerMinuteKilogram = vo2MillilitersPerMinuteKilogram; 69 } 70 71 /** 72 * @return measurementMethod 73 */ 74 @Vo2MaxMeasurementMethod.Vo2MaxMeasurementMethodTypes getMeasurementMethod()75 public int getMeasurementMethod() { 76 return mMeasurementMethod; 77 } 78 79 /** 80 * @return vo2MillilitersPerMinuteKilogram 81 */ getVo2MillilitersPerMinuteKilogram()82 public double getVo2MillilitersPerMinuteKilogram() { 83 return mVo2MillilitersPerMinuteKilogram; 84 } 85 86 /** Identifier for V02 max measurement method */ 87 public static final class Vo2MaxMeasurementMethod { 88 public static final int MEASUREMENT_METHOD_OTHER = 0; 89 public static final int MEASUREMENT_METHOD_METABOLIC_CART = 1; 90 public static final int MEASUREMENT_METHOD_HEART_RATE_RATIO = 2; 91 public static final int MEASUREMENT_METHOD_COOPER_TEST = 3; 92 public static final int MEASUREMENT_METHOD_MULTISTAGE_FITNESS_TEST = 4; 93 public static final int MEASUREMENT_METHOD_ROCKPORT_FITNESS_TEST = 5; 94 95 /** 96 * Valid set of values for this IntDef. Update this set when add new type or deprecate 97 * existing type. 98 * 99 * @hide 100 */ 101 public static final Set<Integer> VALID_TYPES = 102 Set.of( 103 MEASUREMENT_METHOD_OTHER, 104 MEASUREMENT_METHOD_METABOLIC_CART, 105 MEASUREMENT_METHOD_HEART_RATE_RATIO, 106 MEASUREMENT_METHOD_COOPER_TEST, 107 MEASUREMENT_METHOD_MULTISTAGE_FITNESS_TEST, 108 MEASUREMENT_METHOD_ROCKPORT_FITNESS_TEST); 109 Vo2MaxMeasurementMethod()110 Vo2MaxMeasurementMethod() {} 111 112 /** @hide */ 113 @IntDef({ 114 MEASUREMENT_METHOD_OTHER, 115 MEASUREMENT_METHOD_METABOLIC_CART, 116 MEASUREMENT_METHOD_HEART_RATE_RATIO, 117 MEASUREMENT_METHOD_COOPER_TEST, 118 MEASUREMENT_METHOD_MULTISTAGE_FITNESS_TEST, 119 MEASUREMENT_METHOD_ROCKPORT_FITNESS_TEST 120 }) 121 @Retention(RetentionPolicy.SOURCE) 122 public @interface Vo2MaxMeasurementMethodTypes {} 123 } 124 125 /** 126 * Indicates whether some other object is "equal to" this one. 127 * 128 * @param o the reference object with which to compare. 129 * @return {@code true} if this object is the same as the obj 130 */ 131 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 132 @Override equals(Object o)133 public boolean equals(Object o) { 134 if (this == o) return true; 135 if (!super.equals(o)) return false; 136 Vo2MaxRecord that = (Vo2MaxRecord) o; 137 return getMeasurementMethod() == that.getMeasurementMethod() 138 && Double.compare( 139 that.getVo2MillilitersPerMinuteKilogram(), 140 getVo2MillilitersPerMinuteKilogram()) 141 == 0; 142 } 143 144 /** Returns a hash code value for the object. */ 145 @Override hashCode()146 public int hashCode() { 147 return Objects.hash( 148 super.hashCode(), getMeasurementMethod(), getVo2MillilitersPerMinuteKilogram()); 149 } 150 151 /** Builder class for {@link Vo2MaxRecord} */ 152 public static final class Builder { 153 private final Metadata mMetadata; 154 private final Instant mTime; 155 private ZoneOffset mZoneOffset; 156 private final int mMeasurementMethod; 157 private final double mVo2MillilitersPerMinuteKilogram; 158 159 /** 160 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 161 * @param time Start time of this activity 162 * @param measurementMethod VO2 max measurement method. Optional field. Allowed values: 163 * {@link Vo2MaxMeasurementMethod}. 164 * @param vo2MillilitersPerMinuteKilogram Maximal aerobic capacity (VO2 max) in milliliters. 165 * Required field. Valid range: 0-100. 166 */ Builder( @onNull Metadata metadata, @NonNull Instant time, @Vo2MaxMeasurementMethod.Vo2MaxMeasurementMethodTypes int measurementMethod, @FloatRange(from = 0, to = 100) double vo2MillilitersPerMinuteKilogram)167 public Builder( 168 @NonNull Metadata metadata, 169 @NonNull Instant time, 170 @Vo2MaxMeasurementMethod.Vo2MaxMeasurementMethodTypes int measurementMethod, 171 @FloatRange(from = 0, to = 100) double vo2MillilitersPerMinuteKilogram) { 172 Objects.requireNonNull(metadata); 173 Objects.requireNonNull(time); 174 mMetadata = metadata; 175 mTime = time; 176 mMeasurementMethod = measurementMethod; 177 mVo2MillilitersPerMinuteKilogram = vo2MillilitersPerMinuteKilogram; 178 mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time); 179 } 180 181 /** Sets the zone offset of the user when the activity happened */ 182 @NonNull setZoneOffset(@onNull ZoneOffset zoneOffset)183 public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) { 184 Objects.requireNonNull(zoneOffset); 185 mZoneOffset = zoneOffset; 186 return this; 187 } 188 189 /** Sets the zone offset of this record to system default. */ 190 @NonNull clearZoneOffset()191 public Builder clearZoneOffset() { 192 mZoneOffset = RecordUtils.getDefaultZoneOffset(); 193 return this; 194 } 195 196 /** 197 * @return Object of {@link Vo2MaxRecord} without validating the values. 198 * @hide 199 */ 200 @NonNull buildWithoutValidation()201 public Vo2MaxRecord buildWithoutValidation() { 202 return new Vo2MaxRecord( 203 mMetadata, 204 mTime, 205 mZoneOffset, 206 mMeasurementMethod, 207 mVo2MillilitersPerMinuteKilogram, 208 true); 209 } 210 211 /** 212 * @return Object of {@link Vo2MaxRecord} 213 */ 214 @NonNull build()215 public Vo2MaxRecord build() { 216 return new Vo2MaxRecord( 217 mMetadata, 218 mTime, 219 mZoneOffset, 220 mMeasurementMethod, 221 mVo2MillilitersPerMinuteKilogram, 222 false); 223 } 224 } 225 226 /** @hide */ 227 @Override toRecordInternal()228 public Vo2MaxRecordInternal toRecordInternal() { 229 Vo2MaxRecordInternal recordInternal = 230 (Vo2MaxRecordInternal) 231 new Vo2MaxRecordInternal() 232 .setUuid(getMetadata().getId()) 233 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 234 .setLastModifiedTime( 235 getMetadata().getLastModifiedTime().toEpochMilli()) 236 .setClientRecordId(getMetadata().getClientRecordId()) 237 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 238 .setManufacturer(getMetadata().getDevice().getManufacturer()) 239 .setModel(getMetadata().getDevice().getModel()) 240 .setDeviceType(getMetadata().getDevice().getType()) 241 .setRecordingMethod(getMetadata().getRecordingMethod()); 242 recordInternal.setTime(getTime().toEpochMilli()); 243 recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds()); 244 recordInternal.setMeasurementMethod(mMeasurementMethod); 245 recordInternal.setVo2MillilitersPerMinuteKilogram(mVo2MillilitersPerMinuteKilogram); 246 return recordInternal; 247 } 248 } 249