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