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