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