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.SexualActivityRecordInternal;
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 an occurrence of sexual activity. Each record is a single occurrence. ProtectionUsed
33  * field is optional.
34  */
35 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_SEXUAL_ACTIVITY)
36 public final class SexualActivityRecord extends InstantRecord {
37 
38     private final int mProtectionUsed;
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 protectionUsed ProtectionUsed of this activity
45      * @param skipValidation Boolean flag to skip validation of record values.
46      */
SexualActivityRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @SexualActivityProtectionUsed.SexualActivityProtectionUsedTypes int protectionUsed, boolean skipValidation)47     private SexualActivityRecord(
48             @NonNull Metadata metadata,
49             @NonNull Instant time,
50             @NonNull ZoneOffset zoneOffset,
51             @SexualActivityProtectionUsed.SexualActivityProtectionUsedTypes int protectionUsed,
52             boolean skipValidation) {
53         super(metadata, time, zoneOffset, skipValidation);
54         Objects.requireNonNull(metadata);
55         Objects.requireNonNull(time);
56         Objects.requireNonNull(zoneOffset);
57         validateIntDefValue(
58                 protectionUsed,
59                 SexualActivityProtectionUsed.VALID_TYPES,
60                 SexualActivityProtectionUsed.class.getSimpleName());
61         mProtectionUsed = protectionUsed;
62     }
63 
64     /**
65      * @return protectionUsed
66      */
67     @SexualActivityProtectionUsed.SexualActivityProtectionUsedTypes
getProtectionUsed()68     public int getProtectionUsed() {
69         return mProtectionUsed;
70     }
71 
72     /** Identifier for sexual activity protection used */
73     public static final class SexualActivityProtectionUsed {
74         public static final int PROTECTION_USED_UNKNOWN = 0;
75         public static final int PROTECTION_USED_PROTECTED = 1;
76         public static final int PROTECTION_USED_UNPROTECTED = 2;
77 
78         /**
79          * Valid set of values for this IntDef. Update this set when add new type or deprecate
80          * existing type.
81          *
82          * @hide
83          */
84         public static final Set<Integer> VALID_TYPES =
85                 Set.of(
86                         PROTECTION_USED_UNKNOWN,
87                         PROTECTION_USED_PROTECTED,
88                         PROTECTION_USED_UNPROTECTED);
89 
SexualActivityProtectionUsed()90         SexualActivityProtectionUsed() {}
91 
92         /** @hide */
93         @IntDef({PROTECTION_USED_UNKNOWN, PROTECTION_USED_PROTECTED, PROTECTION_USED_UNPROTECTED})
94         @Retention(RetentionPolicy.SOURCE)
95         public @interface SexualActivityProtectionUsedTypes {}
96     }
97 
98     /**
99      * Indicates whether some other object is "equal to" this one.
100      *
101      * @param o the reference object with which to compare.
102      * @return {@code true} if this object is the same as the obj
103      */
104     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
105     @Override
equals(Object o)106     public boolean equals(Object o) {
107         if (this == o) return true;
108         if (!super.equals(o)) return false;
109         SexualActivityRecord that = (SexualActivityRecord) o;
110         return getProtectionUsed() == that.getProtectionUsed();
111     }
112 
113     /** Returns a hash code value for the object. */
114     @Override
hashCode()115     public int hashCode() {
116         return Objects.hash(super.hashCode(), getProtectionUsed());
117     }
118 
119     /** Builder class for {@link SexualActivityRecord} */
120     public static final class Builder {
121         private final Metadata mMetadata;
122         private final Instant mTime;
123         private ZoneOffset mZoneOffset;
124         private final int mProtectionUsed;
125 
126         /**
127          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
128          * @param time Start time of this activity
129          * @param protectionUsed Whether protection was used during sexual activity. Optional field,
130          *     null if unknown. Allowed values: Protection.
131          */
Builder( @onNull Metadata metadata, @NonNull Instant time, @SexualActivityProtectionUsed.SexualActivityProtectionUsedTypes int protectionUsed)132         public Builder(
133                 @NonNull Metadata metadata,
134                 @NonNull Instant time,
135                 @SexualActivityProtectionUsed.SexualActivityProtectionUsedTypes
136                         int protectionUsed) {
137             Objects.requireNonNull(metadata);
138             Objects.requireNonNull(time);
139             mMetadata = metadata;
140             mTime = time;
141             mProtectionUsed = protectionUsed;
142             mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time);
143         }
144 
145         /** Sets the zone offset of the user when the activity happened */
146         @NonNull
setZoneOffset(@onNull ZoneOffset zoneOffset)147         public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) {
148             Objects.requireNonNull(zoneOffset);
149             mZoneOffset = zoneOffset;
150             return this;
151         }
152 
153         /** Sets the zone offset of this record to system default. */
154         @NonNull
clearZoneOffset()155         public Builder clearZoneOffset() {
156             mZoneOffset = RecordUtils.getDefaultZoneOffset();
157             return this;
158         }
159 
160         /**
161          * @return Object of {@link SexualActivityRecord} without validating the values.
162          * @hide
163          */
164         @NonNull
buildWithoutValidation()165         public SexualActivityRecord buildWithoutValidation() {
166             return new SexualActivityRecord(mMetadata, mTime, mZoneOffset, mProtectionUsed, true);
167         }
168 
169         /**
170          * @return Object of {@link SexualActivityRecord}
171          */
172         @NonNull
build()173         public SexualActivityRecord build() {
174             return new SexualActivityRecord(mMetadata, mTime, mZoneOffset, mProtectionUsed, false);
175         }
176     }
177 
178     /** @hide */
179     @Override
toRecordInternal()180     public SexualActivityRecordInternal toRecordInternal() {
181         SexualActivityRecordInternal recordInternal =
182                 (SexualActivityRecordInternal)
183                         new SexualActivityRecordInternal()
184                                 .setUuid(getMetadata().getId())
185                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
186                                 .setLastModifiedTime(
187                                         getMetadata().getLastModifiedTime().toEpochMilli())
188                                 .setClientRecordId(getMetadata().getClientRecordId())
189                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
190                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
191                                 .setModel(getMetadata().getDevice().getModel())
192                                 .setDeviceType(getMetadata().getDevice().getType())
193                                 .setRecordingMethod(getMetadata().getRecordingMethod());
194         recordInternal.setTime(getTime().toEpochMilli());
195         recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds());
196         recordInternal.setProtectionUsed(mProtectionUsed);
197         return recordInternal;
198     }
199 }
200