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_BLOOD_PRESSURE; 19 import static android.health.connect.datatypes.validation.ValidationUtils.validateIntDefValue; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.health.connect.HealthConnectManager; 24 import android.health.connect.datatypes.units.Pressure; 25 import android.health.connect.datatypes.validation.ValidationUtils; 26 import android.health.connect.internal.datatypes.BloodPressureRecordInternal; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.time.Instant; 31 import java.time.ZoneOffset; 32 import java.util.Objects; 33 import java.util.Set; 34 35 /** 36 * Captures the blood pressure of a user. Each record represents a single instantaneous blood 37 * pressure reading. 38 */ 39 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_BLOOD_PRESSURE) 40 public final class BloodPressureRecord extends InstantRecord { 41 private static final double SYSTOLIC_MIN_VALUE = 20.0; 42 private static final double SYSTOLIC_MAX_VALUE = 300.0; 43 private static final double DIASTOLIC_MIN_VALUE = 10.0; 44 private static final double DIASTOLIC_MAX_VALUE = 300.0; 45 46 /** 47 * Metric identifier to get average diastolic pressure using aggregate APIs in {@link 48 * HealthConnectManager} 49 */ 50 @NonNull 51 public static final AggregationType<Pressure> DIASTOLIC_AVG = 52 new AggregationType<>( 53 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_DIASTOLIC_AVG, 54 AggregationType.AVG, 55 RECORD_TYPE_BLOOD_PRESSURE, 56 Pressure.class); 57 58 /** 59 * Metric identifier to get maximum diastolic pressure using aggregate APIs in {@link 60 * HealthConnectManager} 61 */ 62 @NonNull 63 public static final AggregationType<Pressure> DIASTOLIC_MAX = 64 new AggregationType<>( 65 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_DIASTOLIC_MAX, 66 AggregationType.MAX, 67 RECORD_TYPE_BLOOD_PRESSURE, 68 Pressure.class); 69 70 /** 71 * Metric identifier to get minimum diastolic pressure using aggregate APIs in {@link 72 * HealthConnectManager} 73 */ 74 @NonNull 75 public static final AggregationType<Pressure> DIASTOLIC_MIN = 76 new AggregationType<>( 77 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_DIASTOLIC_MIN, 78 AggregationType.MIN, 79 RECORD_TYPE_BLOOD_PRESSURE, 80 Pressure.class); 81 82 /** 83 * Metric identifier to get average systolic pressure using aggregate APIs in {@link 84 * HealthConnectManager} 85 */ 86 @NonNull 87 public static final AggregationType<Pressure> SYSTOLIC_AVG = 88 new AggregationType<>( 89 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_SYSTOLIC_AVG, 90 AggregationType.AVG, 91 RECORD_TYPE_BLOOD_PRESSURE, 92 Pressure.class); 93 94 /** 95 * Metric identifier to get maximum systolic pressure using aggregate APIs in {@link 96 * HealthConnectManager} 97 */ 98 @NonNull 99 public static final AggregationType<Pressure> SYSTOLIC_MAX = 100 new AggregationType<>( 101 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_SYSTOLIC_MAX, 102 AggregationType.MAX, 103 RECORD_TYPE_BLOOD_PRESSURE, 104 Pressure.class); 105 106 /** 107 * Metric identifier to get minimum systolic pressure using aggregate APIs in {@link 108 * HealthConnectManager} 109 */ 110 @NonNull 111 public static final AggregationType<Pressure> SYSTOLIC_MIN = 112 new AggregationType<>( 113 AggregationType.AggregationTypeIdentifier.BLOOD_PRESSURE_RECORD_SYSTOLIC_MIN, 114 AggregationType.MIN, 115 RECORD_TYPE_BLOOD_PRESSURE, 116 Pressure.class); 117 118 private final int mMeasurementLocation; 119 private final Pressure mSystolic; 120 private final Pressure mDiastolic; 121 private final int mBodyPosition; 122 123 /** 124 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 125 * @param time Start time of this activity 126 * @param zoneOffset Zone offset of the user when the activity started 127 * @param measurementLocation MeasurementLocation of this activity 128 * @param systolic Systolic of this activity 129 * @param diastolic Diastolic of this activity 130 * @param bodyPosition BodyPosition of this activity 131 * @param skipValidation Boolean flag to skip validation of record values. 132 */ BloodPressureRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @BloodPressureMeasurementLocation.BloodPressureMeasurementLocations int measurementLocation, @NonNull Pressure systolic, @NonNull Pressure diastolic, @BodyPosition.BodyPositionType int bodyPosition, boolean skipValidation)133 private BloodPressureRecord( 134 @NonNull Metadata metadata, 135 @NonNull Instant time, 136 @NonNull ZoneOffset zoneOffset, 137 @BloodPressureMeasurementLocation.BloodPressureMeasurementLocations 138 int measurementLocation, 139 @NonNull Pressure systolic, 140 @NonNull Pressure diastolic, 141 @BodyPosition.BodyPositionType int bodyPosition, 142 boolean skipValidation) { 143 super(metadata, time, zoneOffset, skipValidation); 144 Objects.requireNonNull(metadata); 145 Objects.requireNonNull(time); 146 Objects.requireNonNull(zoneOffset); 147 Objects.requireNonNull(systolic); 148 Objects.requireNonNull(diastolic); 149 validateIntDefValue( 150 measurementLocation, 151 BloodPressureMeasurementLocation.VALID_TYPES, 152 BloodPressureMeasurementLocation.class.getSimpleName()); 153 if (!skipValidation) { 154 ValidationUtils.requireInRange( 155 systolic.getInMillimetersOfMercury(), 156 SYSTOLIC_MIN_VALUE, 157 SYSTOLIC_MAX_VALUE, 158 "systolic"); 159 ValidationUtils.requireInRange( 160 diastolic.getInMillimetersOfMercury(), 161 DIASTOLIC_MIN_VALUE, 162 DIASTOLIC_MAX_VALUE, 163 "diastolic"); 164 } 165 validateIntDefValue( 166 bodyPosition, BodyPosition.VALID_TYPES, BodyPosition.class.getSimpleName()); 167 mMeasurementLocation = measurementLocation; 168 mSystolic = systolic; 169 mDiastolic = diastolic; 170 mBodyPosition = bodyPosition; 171 } 172 173 /** 174 * @return measurementLocation 175 */ 176 @BloodPressureMeasurementLocation.BloodPressureMeasurementLocations getMeasurementLocation()177 public int getMeasurementLocation() { 178 return mMeasurementLocation; 179 } 180 181 /** 182 * @return systolic 183 */ 184 @NonNull getSystolic()185 public Pressure getSystolic() { 186 return mSystolic; 187 } 188 189 /** 190 * @return diastolic 191 */ 192 @NonNull getDiastolic()193 public Pressure getDiastolic() { 194 return mDiastolic; 195 } 196 197 /** 198 * @return bodyPosition 199 */ 200 @BodyPosition.BodyPositionType getBodyPosition()201 public int getBodyPosition() { 202 return mBodyPosition; 203 } 204 205 /** Identifier for Blood Pressure Measurement Location */ 206 public static final class BloodPressureMeasurementLocation { 207 208 public static final int BLOOD_PRESSURE_MEASUREMENT_LOCATION_UNKNOWN = 0; 209 /** Blood pressure measurement location constant for the left wrist. */ 210 public static final int BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_WRIST = 1; 211 /** Blood pressure measurement location constant for the right wrist. */ 212 public static final int BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_WRIST = 2; 213 /** Blood pressure measurement location constant for the left upper arm. */ 214 public static final int BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_UPPER_ARM = 3; 215 /** Blood pressure measurement location constant for the right upper arm. */ 216 public static final int BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_UPPER_ARM = 4; 217 218 /** 219 * Valid set of values for this IntDef. Update this set when add new type or deprecate 220 * existing type. 221 * 222 * @hide 223 */ 224 public static final Set<Integer> VALID_TYPES = 225 Set.of( 226 BLOOD_PRESSURE_MEASUREMENT_LOCATION_UNKNOWN, 227 BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_WRIST, 228 BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_WRIST, 229 BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_UPPER_ARM, 230 BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_UPPER_ARM); 231 BloodPressureMeasurementLocation()232 private BloodPressureMeasurementLocation() {} 233 234 /** @hide */ 235 @IntDef({ 236 BLOOD_PRESSURE_MEASUREMENT_LOCATION_UNKNOWN, 237 BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_WRIST, 238 BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_WRIST, 239 BLOOD_PRESSURE_MEASUREMENT_LOCATION_LEFT_UPPER_ARM, 240 BLOOD_PRESSURE_MEASUREMENT_LOCATION_RIGHT_UPPER_ARM 241 }) 242 @Retention(RetentionPolicy.SOURCE) 243 public @interface BloodPressureMeasurementLocations {} 244 } 245 246 /** Identifier for body position */ 247 public static final class BodyPosition { 248 249 /** Body position unknown / not identified. */ 250 public static final int BODY_POSITION_UNKNOWN = 0; 251 /** Body position constant representing standing up. */ 252 public static final int BODY_POSITION_STANDING_UP = 1; 253 /** Body position constant representing sitting down. */ 254 public static final int BODY_POSITION_SITTING_DOWN = 2; 255 /** Body position constant representing lying down. */ 256 public static final int BODY_POSITION_LYING_DOWN = 3; 257 /** Body position constant representing semi-recumbent (partially reclining) pose. */ 258 public static final int BODY_POSITION_RECLINING = 4; 259 260 /** 261 * Valid set of values for this IntDef. Update this set when add new type or deprecate 262 * existing type. 263 * 264 * @hide 265 */ 266 public static final Set<Integer> VALID_TYPES = 267 Set.of( 268 BODY_POSITION_UNKNOWN, 269 BODY_POSITION_STANDING_UP, 270 BODY_POSITION_SITTING_DOWN, 271 BODY_POSITION_LYING_DOWN, 272 BODY_POSITION_RECLINING); 273 BodyPosition()274 private BodyPosition() {} 275 276 /** @hide */ 277 @IntDef({ 278 BODY_POSITION_UNKNOWN, 279 BODY_POSITION_STANDING_UP, 280 BODY_POSITION_SITTING_DOWN, 281 BODY_POSITION_LYING_DOWN, 282 BODY_POSITION_RECLINING 283 }) 284 @Retention(RetentionPolicy.SOURCE) 285 public @interface BodyPositionType {} 286 } 287 288 /** 289 * Indicates whether some other object is "equal to" this one. 290 * 291 * @param o the reference object with which to compare. 292 * @return {@code true} if this object is the same as the obj 293 */ 294 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 295 @Override equals(Object o)296 public boolean equals(Object o) { 297 if (this == o) return true; 298 if (!super.equals(o)) return false; 299 BloodPressureRecord that = (BloodPressureRecord) o; 300 return getMeasurementLocation() == that.getMeasurementLocation() 301 && getBodyPosition() == that.getBodyPosition() 302 && getSystolic().equals(that.getSystolic()) 303 && getDiastolic().equals(that.getDiastolic()); 304 } 305 306 /** Returns a hash code value for the object. */ 307 @Override hashCode()308 public int hashCode() { 309 return Objects.hash( 310 super.hashCode(), 311 getMeasurementLocation(), 312 getSystolic(), 313 getDiastolic(), 314 getBodyPosition()); 315 } 316 317 /** Builder class for {@link BloodPressureRecord} */ 318 public static final class Builder { 319 private final Metadata mMetadata; 320 private final Instant mTime; 321 private ZoneOffset mZoneOffset; 322 private final int mMeasurementLocation; 323 private final Pressure mSystolic; 324 private final Pressure mDiastolic; 325 private final int mBodyPosition; 326 327 /** 328 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 329 * @param time Start time of this activity 330 * @param measurementLocation The arm and part of the arm where the measurement was taken. 331 * Optional field. Allowed values: {@link BodyTemperatureMeasurementLocation}. 332 * @param systolic Systolic blood pressure measurement, in {@link Pressure} unit. Required 333 * field. Valid range: 20-200 mmHg. 334 * @param diastolic Diastolic blood pressure measurement, in {@link Pressure} unit. Required 335 * field. Valid range: 10-180 mmHg. 336 * @param bodyPosition The user's body position when the measurement was taken. Optional 337 * field. Allowed values: {@link BodyPosition}. 338 */ Builder( @onNull Metadata metadata, @NonNull Instant time, @BloodPressureMeasurementLocation.BloodPressureMeasurementLocations int measurementLocation, @NonNull Pressure systolic, @NonNull Pressure diastolic, @BodyPosition.BodyPositionType int bodyPosition)339 public Builder( 340 @NonNull Metadata metadata, 341 @NonNull Instant time, 342 @BloodPressureMeasurementLocation.BloodPressureMeasurementLocations 343 int measurementLocation, 344 @NonNull Pressure systolic, 345 @NonNull Pressure diastolic, 346 @BodyPosition.BodyPositionType int bodyPosition) { 347 Objects.requireNonNull(metadata); 348 Objects.requireNonNull(time); 349 Objects.requireNonNull(systolic); 350 Objects.requireNonNull(diastolic); 351 validateIntDefValue( 352 measurementLocation, 353 BloodPressureMeasurementLocation.VALID_TYPES, 354 BloodPressureMeasurementLocation.class.getSimpleName()); 355 mMetadata = metadata; 356 mTime = time; 357 mMeasurementLocation = measurementLocation; 358 mSystolic = systolic; 359 mDiastolic = diastolic; 360 mBodyPosition = bodyPosition; 361 mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time); 362 } 363 364 /** Sets the zone offset of the user when the activity happened */ 365 @NonNull setZoneOffset(@onNull ZoneOffset zoneOffset)366 public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) { 367 Objects.requireNonNull(zoneOffset); 368 mZoneOffset = zoneOffset; 369 return this; 370 } 371 372 /** Sets the zone offset of this record to system default. */ 373 @NonNull clearZoneOffset()374 public Builder clearZoneOffset() { 375 mZoneOffset = RecordUtils.getDefaultZoneOffset(); 376 return this; 377 } 378 379 /** 380 * @return Object of {@link BloodPressureRecord} without validating the values. 381 * @hide 382 */ 383 @NonNull buildWithoutValidation()384 public BloodPressureRecord buildWithoutValidation() { 385 return new BloodPressureRecord( 386 mMetadata, 387 mTime, 388 mZoneOffset, 389 mMeasurementLocation, 390 mSystolic, 391 mDiastolic, 392 mBodyPosition, 393 true); 394 } 395 396 /** 397 * @return Object of {@link BloodPressureRecord} 398 */ 399 @NonNull build()400 public BloodPressureRecord build() { 401 return new BloodPressureRecord( 402 mMetadata, 403 mTime, 404 mZoneOffset, 405 mMeasurementLocation, 406 mSystolic, 407 mDiastolic, 408 mBodyPosition, 409 false); 410 } 411 } 412 413 /** @hide */ 414 @Override toRecordInternal()415 public BloodPressureRecordInternal toRecordInternal() { 416 BloodPressureRecordInternal recordInternal = 417 (BloodPressureRecordInternal) 418 new BloodPressureRecordInternal() 419 .setUuid(getMetadata().getId()) 420 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 421 .setLastModifiedTime( 422 getMetadata().getLastModifiedTime().toEpochMilli()) 423 .setClientRecordId(getMetadata().getClientRecordId()) 424 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 425 .setManufacturer(getMetadata().getDevice().getManufacturer()) 426 .setModel(getMetadata().getDevice().getModel()) 427 .setDeviceType(getMetadata().getDevice().getType()) 428 .setRecordingMethod(getMetadata().getRecordingMethod()); 429 recordInternal.setTime(getTime().toEpochMilli()); 430 recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds()); 431 recordInternal.setMeasurementLocation(mMeasurementLocation); 432 recordInternal.setSystolic(mSystolic.getInMillimetersOfMercury()); 433 recordInternal.setDiastolic(mDiastolic.getInMillimetersOfMercury()); 434 recordInternal.setBodyPosition(mBodyPosition); 435 return recordInternal; 436 } 437 } 438