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_BASAL_METABOLIC_RATE;
19 
20 import android.annotation.NonNull;
21 import android.health.connect.HealthConnectManager;
22 import android.health.connect.datatypes.units.Energy;
23 import android.health.connect.datatypes.units.Power;
24 import android.health.connect.datatypes.validation.ValidationUtils;
25 import android.health.connect.internal.datatypes.BasalMetabolicRateRecordInternal;
26 
27 import java.time.Instant;
28 import java.time.ZoneOffset;
29 import java.util.Objects;
30 
31 /**
32  * Captures the BMR of a user. Each record represents the energy a user would burn if at rest all
33  * day, based on their height and weight.
34  */
35 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_BASAL_METABOLIC_RATE)
36 public final class BasalMetabolicRateRecord extends InstantRecord {
37     private final Power mBasalMetabolicRate;
38 
39     /**
40      * @param metadata Metadata to be associated with the record. See {@link Metadata}.
41      * @param time Start time of this activity
42      * @param zoneOffset Zone offset of the user when the activity started
43      * @param basalMetabolicRate BasalMetabolicRate of this activity
44      * @param skipValidation Boolean flag to skip validation of record values.
45      */
BasalMetabolicRateRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @NonNull Power basalMetabolicRate, boolean skipValidation)46     private BasalMetabolicRateRecord(
47             @NonNull Metadata metadata,
48             @NonNull Instant time,
49             @NonNull ZoneOffset zoneOffset,
50             @NonNull Power basalMetabolicRate,
51             boolean skipValidation) {
52         super(metadata, time, zoneOffset, skipValidation);
53         Objects.requireNonNull(metadata);
54         Objects.requireNonNull(time);
55         Objects.requireNonNull(zoneOffset);
56         Objects.requireNonNull(basalMetabolicRate);
57         if (!skipValidation) {
58             ValidationUtils.requireInRange(
59                     basalMetabolicRate.getInWatts(), 0.0, 484.259, "basalMetabolicRate");
60         }
61         mBasalMetabolicRate = basalMetabolicRate;
62     }
63 
64     /**
65      * @return basalMetabolicRate
66      */
67     @NonNull
getBasalMetabolicRate()68     public Power getBasalMetabolicRate() {
69         return mBasalMetabolicRate;
70     }
71 
72     /**
73      * Indicates whether some other object is "equal to" this one.
74      *
75      * @param o the reference object with which to compare.
76      * @return {@code true} if this object is the same as the obj
77      */
78     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
79     @Override
equals(Object o)80     public boolean equals(Object o) {
81         if (this == o) return true;
82         if (!super.equals(o)) return false;
83         BasalMetabolicRateRecord that = (BasalMetabolicRateRecord) o;
84         return getBasalMetabolicRate().equals(that.getBasalMetabolicRate());
85     }
86 
87     /** Returns a hash code value for the object. */
88     @Override
hashCode()89     public int hashCode() {
90         return Objects.hash(super.hashCode(), getBasalMetabolicRate());
91     }
92 
93     /** Builder class for {@link BasalMetabolicRateRecord} */
94     public static final class Builder {
95         private final Metadata mMetadata;
96         private final Instant mTime;
97         private ZoneOffset mZoneOffset;
98         private final Power mBasalMetabolicRate;
99 
100         /**
101          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
102          * @param time Start time of this activity
103          * @param basalMetabolicRate Basal metabolic rate, in {@link Power} unit. Required field.
104          *     Valid range: 0-10000 kcal/day.
105          */
Builder( @onNull Metadata metadata, @NonNull Instant time, @NonNull Power basalMetabolicRate)106         public Builder(
107                 @NonNull Metadata metadata,
108                 @NonNull Instant time,
109                 @NonNull Power basalMetabolicRate) {
110             Objects.requireNonNull(metadata);
111             Objects.requireNonNull(time);
112             Objects.requireNonNull(basalMetabolicRate);
113             mMetadata = metadata;
114             mTime = time;
115             mBasalMetabolicRate = basalMetabolicRate;
116             mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time);
117         }
118 
119         /** Sets the zone offset of the user when the activity happened */
120         @NonNull
setZoneOffset(@onNull ZoneOffset zoneOffset)121         public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) {
122             Objects.requireNonNull(zoneOffset);
123             mZoneOffset = zoneOffset;
124             return this;
125         }
126 
127         /** Sets the zone offset of this record to system default. */
128         @NonNull
clearZoneOffset()129         public Builder clearZoneOffset() {
130             mZoneOffset = RecordUtils.getDefaultZoneOffset();
131             return this;
132         }
133 
134         /**
135          * @return Object of {@link BasalMetabolicRateRecord} without validating the values.
136          * @hide
137          */
138         @NonNull
buildWithoutValidation()139         public BasalMetabolicRateRecord buildWithoutValidation() {
140             return new BasalMetabolicRateRecord(
141                     mMetadata, mTime, mZoneOffset, mBasalMetabolicRate, true);
142         }
143 
144         /**
145          * @return Object of {@link BasalMetabolicRateRecord}
146          */
147         @NonNull
build()148         public BasalMetabolicRateRecord build() {
149             return new BasalMetabolicRateRecord(
150                     mMetadata, mTime, mZoneOffset, mBasalMetabolicRate, false);
151         }
152     }
153 
154     /**
155      * Metric identifier get total basal calories burnt using aggregate APIs in {@link
156      * HealthConnectManager}
157      */
158     @NonNull
159     public static final AggregationType<Energy> BASAL_CALORIES_TOTAL =
160             new AggregationType<>(
161                     AggregationType.AggregationTypeIdentifier.BMR_RECORD_BASAL_CALORIES_TOTAL,
162                     AggregationType.SUM,
163                     RECORD_TYPE_BASAL_METABOLIC_RATE,
164                     Energy.class);
165 
166     /** @hide */
167     @Override
toRecordInternal()168     public BasalMetabolicRateRecordInternal toRecordInternal() {
169         BasalMetabolicRateRecordInternal recordInternal =
170                 (BasalMetabolicRateRecordInternal)
171                         new BasalMetabolicRateRecordInternal()
172                                 .setUuid(getMetadata().getId())
173                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
174                                 .setLastModifiedTime(
175                                         getMetadata().getLastModifiedTime().toEpochMilli())
176                                 .setClientRecordId(getMetadata().getClientRecordId())
177                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
178                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
179                                 .setModel(getMetadata().getDevice().getModel())
180                                 .setDeviceType(getMetadata().getDevice().getType())
181                                 .setRecordingMethod(getMetadata().getRecordingMethod());
182         recordInternal.setTime(getTime().toEpochMilli());
183         recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds());
184         recordInternal.setBasalMetabolicRate(mBasalMetabolicRate.getInWatts());
185         return recordInternal;
186     }
187 }
188