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