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_MINDFULNESS_SESSION; 19 import static android.health.connect.datatypes.RecordUtils.isEqualNullableCharSequences; 20 import static android.health.connect.datatypes.validation.ValidationUtils.validateIntDefValue; 21 22 import static com.android.healthfitness.flags.Flags.FLAG_MINDFULNESS; 23 24 import android.annotation.FlaggedApi; 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.health.connect.internal.datatypes.MindfulnessSessionRecordInternal; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.time.Instant; 33 import java.time.ZoneOffset; 34 import java.util.Objects; 35 import java.util.Set; 36 37 /** 38 * Captures a mindfulness session. 39 * 40 * <p>For example: yoga, meditation, guided breathing, etc. 41 * 42 * <p>Each record needs a start time, end time and a mindfulness session type. In addition, each 43 * record has an optional title and notes. 44 */ 45 @FlaggedApi(FLAG_MINDFULNESS) 46 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_MINDFULNESS_SESSION) 47 public final class MindfulnessSessionRecord extends IntervalRecord { 48 49 /** 50 * Metric identifier to retrieve total mindfulness session duration using aggregate APIs in 51 * {@link android.health.connect.HealthConnectManager}. Calculated in milliseconds. 52 */ 53 @NonNull 54 public static final AggregationType<Long> MINDFULNESS_DURATION_TOTAL = 55 new AggregationType<>( 56 AggregationType.AggregationTypeIdentifier.MINDFULNESS_SESSION_DURATION_TOTAL, 57 AggregationType.SUM, 58 RECORD_TYPE_MINDFULNESS_SESSION, 59 Long.class); 60 61 /** Use this type if the mindfulness session type is unknown. */ 62 public static final int MINDFULNESS_SESSION_TYPE_UNKNOWN = 0; 63 64 /** Meditation mindfulness session. */ 65 public static final int MINDFULNESS_SESSION_TYPE_MEDITATION = 1; 66 67 /** Other mindfulness session. */ 68 public static final int MINDFULNESS_SESSION_TYPE_OTHER = 2; 69 70 /** Guided breathing mindfulness session. */ 71 public static final int MINDFULNESS_SESSION_TYPE_BREATHING = 3; 72 73 /** Music/soundscapes mindfulness session. */ 74 public static final int MINDFULNESS_SESSION_TYPE_MUSIC = 4; 75 76 /** Stretches/movement mindfulness session. */ 77 public static final int MINDFULNESS_SESSION_TYPE_MOVEMENT = 5; 78 79 /** Unguided mindfulness session. */ 80 public static final int MINDFULNESS_SESSION_TYPE_UNGUIDED = 6; 81 82 /** 83 * Valid set of values for this IntDef. Update this set when add new type or deprecate existing 84 * type. 85 */ 86 private static final Set<Integer> VALID_MINDFULNESS_SESSION_TYPES = 87 Set.of( 88 MINDFULNESS_SESSION_TYPE_UNKNOWN, 89 MINDFULNESS_SESSION_TYPE_MEDITATION, 90 MINDFULNESS_SESSION_TYPE_OTHER, 91 MINDFULNESS_SESSION_TYPE_BREATHING, 92 MINDFULNESS_SESSION_TYPE_MUSIC, 93 MINDFULNESS_SESSION_TYPE_MOVEMENT, 94 MINDFULNESS_SESSION_TYPE_UNGUIDED); 95 96 private final int mMindfulnessSessionType; 97 @Nullable private final CharSequence mTitle; 98 @Nullable private final CharSequence mNotes; 99 100 /** 101 * Builds {@link MindfulnessSessionRecord} instance 102 * 103 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 104 * @param startTime Start time of this activity 105 * @param startZoneOffset Zone offset of the user when the session started 106 * @param endTime End time of this activity 107 * @param endZoneOffset Zone offset of the user when the session finished 108 * @param mindfulnessSessionType type of the session. 109 * @param title Title of the session. Optional field. 110 * @param notes Additional notes for the session. Optional field. 111 * @param skipValidation Boolean flag to skip validation of record values. 112 */ MindfulnessSessionRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @MindfulnessSessionType int mindfulnessSessionType, @Nullable CharSequence title, @Nullable CharSequence notes, boolean skipValidation)113 private MindfulnessSessionRecord( 114 @NonNull Metadata metadata, 115 @NonNull Instant startTime, 116 @NonNull ZoneOffset startZoneOffset, 117 @NonNull Instant endTime, 118 @NonNull ZoneOffset endZoneOffset, 119 @MindfulnessSessionType int mindfulnessSessionType, 120 @Nullable CharSequence title, 121 @Nullable CharSequence notes, 122 boolean skipValidation) { 123 super( 124 metadata, 125 startTime, 126 startZoneOffset, 127 endTime, 128 endZoneOffset, 129 skipValidation, 130 /* enforceFutureTimeRestrictions= */ true); 131 if (!skipValidation) { 132 validateIntDefValue( 133 mindfulnessSessionType, 134 VALID_MINDFULNESS_SESSION_TYPES, 135 MindfulnessSessionType.class.getSimpleName()); 136 } 137 mMindfulnessSessionType = mindfulnessSessionType; 138 mTitle = title; 139 mNotes = notes; 140 } 141 142 /** Returns type of the mindfulness session. */ 143 @MindfulnessSessionType getMindfulnessSessionType()144 public int getMindfulnessSessionType() { 145 return mMindfulnessSessionType; 146 } 147 148 /** Returns title of the mindfulness session. Returns null if no title was specified. */ 149 @Nullable getTitle()150 public CharSequence getTitle() { 151 return mTitle; 152 } 153 154 /** Returns notes for the mindfulness session. Returns null if no notes was specified. */ 155 @Nullable getNotes()156 public CharSequence getNotes() { 157 return mNotes; 158 } 159 160 @Override equals(@ullable Object o)161 public boolean equals(@Nullable Object o) { 162 if (this == o) return true; 163 if (!(o instanceof MindfulnessSessionRecord)) return false; 164 if (!super.equals(o)) return false; 165 MindfulnessSessionRecord that = (MindfulnessSessionRecord) o; 166 return getMindfulnessSessionType() == that.getMindfulnessSessionType() 167 && isEqualNullableCharSequences(getTitle(), that.getTitle()) 168 && isEqualNullableCharSequences(getNotes(), that.getNotes()); 169 } 170 171 @Override hashCode()172 public int hashCode() { 173 return Objects.hash(super.hashCode(), getMindfulnessSessionType(), getTitle(), getNotes()); 174 } 175 176 /** @hide */ 177 @IntDef({ 178 MINDFULNESS_SESSION_TYPE_UNKNOWN, 179 MINDFULNESS_SESSION_TYPE_MEDITATION, 180 MINDFULNESS_SESSION_TYPE_OTHER, 181 MINDFULNESS_SESSION_TYPE_BREATHING, 182 MINDFULNESS_SESSION_TYPE_MUSIC, 183 MINDFULNESS_SESSION_TYPE_MOVEMENT, 184 MINDFULNESS_SESSION_TYPE_UNGUIDED 185 }) 186 @Retention(RetentionPolicy.SOURCE) 187 public @interface MindfulnessSessionType {} 188 189 /** Builder class for {@link MindfulnessSessionRecord} */ 190 public static final class Builder { 191 private final Metadata mMetadata; 192 private final Instant mStartTime; 193 private final Instant mEndTime; 194 private ZoneOffset mStartZoneOffset; 195 private ZoneOffset mEndZoneOffset; 196 private final int mMindfulnessSessionType; 197 @Nullable private CharSequence mTitle; 198 @Nullable private CharSequence mNotes; 199 200 /** 201 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 202 * @param startTime Start time of this mindfulness session 203 * @param endTime End time of this mindfulness session 204 */ Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, @MindfulnessSessionType int mindfulnessSessionType)205 public Builder( 206 @NonNull Metadata metadata, 207 @NonNull Instant startTime, 208 @NonNull Instant endTime, 209 @MindfulnessSessionType int mindfulnessSessionType) { 210 Objects.requireNonNull(metadata); 211 Objects.requireNonNull(startTime); 212 Objects.requireNonNull(endTime); 213 mMetadata = metadata; 214 mStartTime = startTime; 215 mEndTime = endTime; 216 mMindfulnessSessionType = mindfulnessSessionType; 217 mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime); 218 mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime); 219 } 220 221 /** Sets the zone offset of the user when the activity started */ 222 @NonNull setStartZoneOffset(@onNull ZoneOffset startZoneOffset)223 public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) { 224 Objects.requireNonNull(startZoneOffset); 225 226 mStartZoneOffset = startZoneOffset; 227 return this; 228 } 229 230 /** Sets the zone offset of the user when the activity ended */ 231 @NonNull setEndZoneOffset(@onNull ZoneOffset endZoneOffset)232 public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) { 233 Objects.requireNonNull(endZoneOffset); 234 mEndZoneOffset = endZoneOffset; 235 return this; 236 } 237 238 /** 239 * Sets a title of this activity 240 * 241 * @param title Title of the session. Optional field. 242 */ 243 @NonNull setTitle(@ullable CharSequence title)244 public Builder setTitle(@Nullable CharSequence title) { 245 mTitle = title; 246 return this; 247 } 248 249 /** 250 * Sets notes for this activity 251 * 252 * @param notes Additional notes for the session. Optional field. 253 */ 254 @NonNull setNotes(@ullable CharSequence notes)255 public Builder setNotes(@Nullable CharSequence notes) { 256 mNotes = notes; 257 return this; 258 } 259 260 /** 261 * @return Object of {@link MindfulnessSessionRecord} without validating the values. 262 * @hide 263 */ 264 @NonNull buildWithoutValidation()265 public MindfulnessSessionRecord buildWithoutValidation() { 266 return new MindfulnessSessionRecord( 267 mMetadata, 268 mStartTime, 269 mStartZoneOffset, 270 mEndTime, 271 mEndZoneOffset, 272 mMindfulnessSessionType, 273 mTitle, 274 mNotes, 275 true); 276 } 277 278 /** Returns {@link MindfulnessSessionRecord} */ 279 @NonNull build()280 public MindfulnessSessionRecord build() { 281 return new MindfulnessSessionRecord( 282 mMetadata, 283 mStartTime, 284 mStartZoneOffset, 285 mEndTime, 286 mEndZoneOffset, 287 mMindfulnessSessionType, 288 mTitle, 289 mNotes, 290 false); 291 } 292 } 293 294 /** @hide */ 295 @Override toRecordInternal()296 public MindfulnessSessionRecordInternal toRecordInternal() { 297 MindfulnessSessionRecordInternal recordInternal = 298 (MindfulnessSessionRecordInternal) 299 new MindfulnessSessionRecordInternal() 300 .setUuid(getMetadata().getId()) 301 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 302 .setLastModifiedTime( 303 getMetadata().getLastModifiedTime().toEpochMilli()) 304 .setClientRecordId(getMetadata().getClientRecordId()) 305 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 306 .setManufacturer(getMetadata().getDevice().getManufacturer()) 307 .setModel(getMetadata().getDevice().getModel()) 308 .setDeviceType(getMetadata().getDevice().getType()) 309 .setRecordingMethod(getMetadata().getRecordingMethod()); 310 recordInternal.setStartTime(getStartTime().toEpochMilli()); 311 recordInternal.setEndTime(getEndTime().toEpochMilli()); 312 recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds()); 313 recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds()); 314 recordInternal.setMindfulnessSessionType(getMindfulnessSessionType()); 315 if (getTitle() != null) { 316 recordInternal.setTitle(getTitle().toString()); 317 } 318 if (getNotes() != null) { 319 recordInternal.setNotes(getNotes().toString()); 320 } 321 return recordInternal; 322 } 323 } 324