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_ACTIVE_CALORIES_BURNED;
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.validation.ValidationUtils;
24 import android.health.connect.internal.datatypes.ActiveCaloriesBurnedRecordInternal;
25 
26 import java.time.Instant;
27 import java.time.ZoneOffset;
28 import java.util.Objects;
29 
30 /**
31  * Captures the estimated active energy burned by the user (in kilocalories), excluding basal
32  * metabolic rate (BMR).
33  */
34 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_ACTIVE_CALORIES_BURNED)
35 public final class ActiveCaloriesBurnedRecord extends IntervalRecord {
36     /** Builder class for {@link ActiveCaloriesBurnedRecord} */
37     public static final class Builder {
38         private final Metadata mMetadata;
39         private final Instant mStartTime;
40         private final Instant mEndTime;
41         private ZoneOffset mStartZoneOffset;
42         private ZoneOffset mEndZoneOffset;
43         private final Energy mEnergy;
44 
45         /**
46          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
47          * @param startTime Start time of this activity
48          * @param endTime End time of this activity
49          * @param energy Energy in {@link Energy} unit. Required field. Valid range: 0-1000000 kcal.
50          */
Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, @NonNull Energy energy)51         public Builder(
52                 @NonNull Metadata metadata,
53                 @NonNull Instant startTime,
54                 @NonNull Instant endTime,
55                 @NonNull Energy energy) {
56             Objects.requireNonNull(metadata);
57             Objects.requireNonNull(startTime);
58             Objects.requireNonNull(endTime);
59             Objects.requireNonNull(energy);
60             mMetadata = metadata;
61             mStartTime = startTime;
62             mEndTime = endTime;
63             mEnergy = energy;
64             mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime);
65             mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime);
66         }
67 
68         /** Sets the zone offset of the user when the activity started */
69         @NonNull
setStartZoneOffset(@onNull ZoneOffset startZoneOffset)70         public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) {
71             Objects.requireNonNull(startZoneOffset);
72 
73             mStartZoneOffset = startZoneOffset;
74             return this;
75         }
76 
77         /** Sets the zone offset of the user when the activity ended */
78         @NonNull
setEndZoneOffset(@onNull ZoneOffset endZoneOffset)79         public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) {
80             Objects.requireNonNull(endZoneOffset);
81 
82             mEndZoneOffset = endZoneOffset;
83             return this;
84         }
85 
86         /** Sets the start zone offset of this record to system default. */
87         @NonNull
clearStartZoneOffset()88         public Builder clearStartZoneOffset() {
89             mStartZoneOffset = RecordUtils.getDefaultZoneOffset();
90             return this;
91         }
92 
93         /** Sets the start zone offset of this record to system default. */
94         @NonNull
clearEndZoneOffset()95         public Builder clearEndZoneOffset() {
96             mEndZoneOffset = RecordUtils.getDefaultZoneOffset();
97             return this;
98         }
99 
100         /**
101          * @return Object of {@link ActiveCaloriesBurnedRecord} without validating the values.
102          * @hide
103          */
104         @NonNull
buildWithoutValidation()105         public ActiveCaloriesBurnedRecord buildWithoutValidation() {
106             return new ActiveCaloriesBurnedRecord(
107                     mMetadata,
108                     mStartTime,
109                     mStartZoneOffset,
110                     mEndTime,
111                     mEndZoneOffset,
112                     mEnergy,
113                     true);
114         }
115 
116         /**
117          * @return Object of {@link ActiveCaloriesBurnedRecord}
118          */
119         @NonNull
build()120         public ActiveCaloriesBurnedRecord build() {
121             return new ActiveCaloriesBurnedRecord(
122                     mMetadata,
123                     mStartTime,
124                     mStartZoneOffset,
125                     mEndTime,
126                     mEndZoneOffset,
127                     mEnergy,
128                     false);
129         }
130     }
131 
132     /**
133      * Metric identifier to get total active calories burnt using aggregate APIs in {@link
134      * HealthConnectManager}
135      */
136     @NonNull
137     public static final AggregationType<Energy> ACTIVE_CALORIES_TOTAL =
138             new AggregationType<>(
139                     AggregationType.AggregationTypeIdentifier
140                             .ACTIVE_CALORIES_BURNED_RECORD_ACTIVE_CALORIES_TOTAL,
141                     AggregationType.SUM,
142                     RECORD_TYPE_ACTIVE_CALORIES_BURNED,
143                     Energy.class);
144 
145     private final Energy mEnergy;
146 
147     /**
148      * @param metadata Metadata to be associated with the record. See {@link Metadata}.
149      * @param startTime Start time of this activity
150      * @param startZoneOffset Zone offset of the user when the activity started
151      * @param endTime End time of this activity
152      * @param endZoneOffset Zone offset of the user when the activity finished
153      * @param energy Energy of this activity
154      * @param skipValidation Boolean flag to skip validation of record values.
155      */
ActiveCaloriesBurnedRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @NonNull Energy energy, boolean skipValidation)156     private ActiveCaloriesBurnedRecord(
157             @NonNull Metadata metadata,
158             @NonNull Instant startTime,
159             @NonNull ZoneOffset startZoneOffset,
160             @NonNull Instant endTime,
161             @NonNull ZoneOffset endZoneOffset,
162             @NonNull Energy energy,
163             boolean skipValidation) {
164         super(
165                 metadata,
166                 startTime,
167                 startZoneOffset,
168                 endTime,
169                 endZoneOffset,
170                 skipValidation,
171                 /* enforceFutureTimeRestrictions= */ true);
172         Objects.requireNonNull(energy);
173         if (!skipValidation) {
174             ValidationUtils.requireInRange(energy.getInCalories(), 0.0, 1000000000.0, "energy");
175         }
176         mEnergy = energy;
177     }
178 
179     /**
180      * @return energy in {@link Energy} unit.
181      */
182     @NonNull
getEnergy()183     public Energy getEnergy() {
184         return mEnergy;
185     }
186 
187     /**
188      * Indicates whether some other object is "equal to" this one.
189      *
190      * @param o the reference object with which to compare.
191      * @return {@code true} if this object is the same as the obj
192      */
193     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
194     @Override
equals(Object o)195     public boolean equals(Object o) {
196         if (this == o) return true;
197         if (!super.equals(o)) return false;
198         ActiveCaloriesBurnedRecord that = (ActiveCaloriesBurnedRecord) o;
199         return getEnergy().equals(that.getEnergy());
200     }
201 
202     /**
203      * @return a hash code value for this object.
204      */
205     @Override
hashCode()206     public int hashCode() {
207         return Objects.hash(super.hashCode(), getEnergy());
208     }
209 
210     /** @hide */
211     @Override
toRecordInternal()212     public ActiveCaloriesBurnedRecordInternal toRecordInternal() {
213         ActiveCaloriesBurnedRecordInternal recordInternal =
214                 (ActiveCaloriesBurnedRecordInternal)
215                         new ActiveCaloriesBurnedRecordInternal()
216                                 .setUuid(getMetadata().getId())
217                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
218                                 .setLastModifiedTime(
219                                         getMetadata().getLastModifiedTime().toEpochMilli())
220                                 .setClientRecordId(getMetadata().getClientRecordId())
221                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
222                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
223                                 .setModel(getMetadata().getDevice().getModel())
224                                 .setDeviceType(getMetadata().getDevice().getType())
225                                 .setRecordingMethod(getMetadata().getRecordingMethod());
226         recordInternal.setStartTime(getStartTime().toEpochMilli());
227         recordInternal.setEndTime(getEndTime().toEpochMilli());
228         recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds());
229         recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds());
230         recordInternal.setEnergy(mEnergy.getInCalories());
231         return recordInternal;
232     }
233 }
234