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.validation.ValidationUtils.validateIntDefValue;
19 
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.health.connect.internal.datatypes.MenstruationFlowRecordInternal;
23 
24 import java.lang.annotation.Retention;
25 import java.lang.annotation.RetentionPolicy;
26 import java.time.Instant;
27 import java.time.ZoneOffset;
28 import java.util.Objects;
29 import java.util.Set;
30 
31 /**
32  * Captures a description of how heavy a user's menstrual flow was (spotting, light, medium, or
33  * heavy). Each record represents a description of how heavy the user's menstrual bleeding was.
34  */
35 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_MENSTRUATION_FLOW)
36 public final class MenstruationFlowRecord extends InstantRecord {
37 
38     private final int mFlow;
39 
40     /**
41      * @param metadata Metadata to be associated with the record. See {@link Metadata}.
42      * @param time Start time of this activity
43      * @param zoneOffset Zone offset of the user when the activity started
44      * @param flow Flow of this activity
45      * @param skipValidation Boolean flag to skip validation of record values.
46      */
MenstruationFlowRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @MenstruationFlowType.MenstruationFlowTypes int flow, boolean skipValidation)47     private MenstruationFlowRecord(
48             @NonNull Metadata metadata,
49             @NonNull Instant time,
50             @NonNull ZoneOffset zoneOffset,
51             @MenstruationFlowType.MenstruationFlowTypes int flow,
52             boolean skipValidation) {
53         super(metadata, time, zoneOffset, skipValidation);
54         Objects.requireNonNull(metadata);
55         Objects.requireNonNull(time);
56         Objects.requireNonNull(zoneOffset);
57         validateIntDefValue(
58                 flow, MenstruationFlowType.VALID_TYPES, MenstruationFlowType.class.getSimpleName());
59         mFlow = flow;
60     }
61 
62     /**
63      * @return menstruation flow
64      */
getFlow()65     public int getFlow() {
66         return mFlow;
67     }
68 
69     /** Identifier for Menstruation Flow */
70     public static final class MenstruationFlowType {
71         public static final int FLOW_UNKNOWN = 0;
72         public static final int FLOW_LIGHT = 1;
73         public static final int FLOW_MEDIUM = 2;
74         public static final int FLOW_HEAVY = 3;
75 
76         /**
77          * Valid set of values for this IntDef. Update this set when add new type or deprecate
78          * existing type.
79          *
80          * @hide
81          */
82         public static final Set<Integer> VALID_TYPES =
83                 Set.of(FLOW_UNKNOWN, FLOW_LIGHT, FLOW_MEDIUM, FLOW_HEAVY);
84 
MenstruationFlowType()85         MenstruationFlowType() {}
86 
87         /** @hide */
88         @IntDef({FLOW_UNKNOWN, FLOW_LIGHT, FLOW_MEDIUM, FLOW_HEAVY})
89         @Retention(RetentionPolicy.SOURCE)
90         public @interface MenstruationFlowTypes {}
91     }
92 
93     /**
94      * Indicates whether some other object is "equal to" this one.
95      *
96      * @param o the reference object with which to compare.
97      * @return {@code true} if this object is the same as the obj
98      */
99     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
100     @Override
equals(Object o)101     public boolean equals(Object o) {
102         if (this == o) return true;
103         if (!super.equals(o)) return false;
104         MenstruationFlowRecord that = (MenstruationFlowRecord) o;
105         return getFlow() == that.getFlow();
106     }
107 
108     /** Returns a hash code value for the object. */
109     @Override
hashCode()110     public int hashCode() {
111         return Objects.hash(super.hashCode(), getFlow());
112     }
113 
114     /** Builder class for {@link MenstruationFlowRecord} */
115     public static final class Builder {
116         private final Metadata mMetadata;
117         private final Instant mTime;
118         private ZoneOffset mZoneOffset;
119         private final int mFlow;
120 
121         /**
122          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
123          * @param time Start time of this activity
124          * @param flow How heavy the user's menstrual flow was. Optional field. Allowed values:
125          *     {@link MenstruationFlowType}.
126          */
Builder( @onNull Metadata metadata, @NonNull Instant time, @MenstruationFlowType.MenstruationFlowTypes int flow)127         public Builder(
128                 @NonNull Metadata metadata,
129                 @NonNull Instant time,
130                 @MenstruationFlowType.MenstruationFlowTypes int flow) {
131             Objects.requireNonNull(metadata);
132             Objects.requireNonNull(time);
133             mMetadata = metadata;
134             mTime = time;
135             mFlow = flow;
136             mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time);
137         }
138 
139         /** Sets the zone offset of the user when the activity happened */
140         @NonNull
setZoneOffset(@onNull ZoneOffset zoneOffset)141         public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) {
142             Objects.requireNonNull(zoneOffset);
143             mZoneOffset = zoneOffset;
144             return this;
145         }
146 
147         /** Sets the zone offset of this record to system default. */
148         @NonNull
clearZoneOffset()149         public Builder clearZoneOffset() {
150             mZoneOffset = RecordUtils.getDefaultZoneOffset();
151             return this;
152         }
153 
154         /**
155          * @return Object of {@link MenstruationFlowRecord} without validating the values.
156          * @hide
157          */
158         @NonNull
buildWithoutValidation()159         public MenstruationFlowRecord buildWithoutValidation() {
160             return new MenstruationFlowRecord(mMetadata, mTime, mZoneOffset, mFlow, true);
161         }
162 
163         /**
164          * @return Object of {@link MenstruationFlowRecord}
165          */
166         @NonNull
build()167         public MenstruationFlowRecord build() {
168             return new MenstruationFlowRecord(mMetadata, mTime, mZoneOffset, mFlow, false);
169         }
170     }
171 
172     /** @hide */
173     @Override
toRecordInternal()174     public MenstruationFlowRecordInternal toRecordInternal() {
175         MenstruationFlowRecordInternal recordInternal =
176                 (MenstruationFlowRecordInternal)
177                         new MenstruationFlowRecordInternal()
178                                 .setUuid(getMetadata().getId())
179                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
180                                 .setLastModifiedTime(
181                                         getMetadata().getLastModifiedTime().toEpochMilli())
182                                 .setClientRecordId(getMetadata().getClientRecordId())
183                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
184                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
185                                 .setModel(getMetadata().getDevice().getModel())
186                                 .setDeviceType(getMetadata().getDevice().getType())
187                                 .setRecordingMethod(getMetadata().getRecordingMethod());
188         recordInternal.setTime(getTime().toEpochMilli());
189         recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds());
190         recordInternal.setFlow(mFlow);
191         return recordInternal;
192     }
193 }
194