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 android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.health.connect.HealthConnectManager; 21 import android.health.connect.datatypes.validation.ValidationUtils; 22 import android.health.connect.internal.datatypes.StepsCadenceRecordInternal; 23 24 import java.time.Instant; 25 import java.time.ZoneOffset; 26 import java.util.HashSet; 27 import java.util.List; 28 import java.util.Objects; 29 import java.util.Set; 30 31 /** Captures the user's steps cadence. */ 32 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_STEPS_CADENCE) 33 public final class StepsCadenceRecord extends IntervalRecord { 34 35 /** 36 * Metric identifier to retrieve average Steps cadence rate using aggregate APIs in {@link 37 * HealthConnectManager} 38 */ 39 @NonNull 40 public static final AggregationType<Double> STEPS_CADENCE_RATE_AVG = 41 new AggregationType<>( 42 AggregationType.AggregationTypeIdentifier.STEPS_CADENCE_RECORD_RATE_AVG, 43 AggregationType.AVG, 44 RecordTypeIdentifier.RECORD_TYPE_STEPS_CADENCE, 45 Double.class); 46 47 /** 48 * Metric identifier to retrieve minimum Steps cadence rate using aggregate APIs in {@link 49 * HealthConnectManager} 50 */ 51 @NonNull 52 public static final AggregationType<Double> STEPS_CADENCE_RATE_MIN = 53 new AggregationType<>( 54 AggregationType.AggregationTypeIdentifier.STEPS_CADENCE_RECORD_RATE_MIN, 55 AggregationType.MIN, 56 RecordTypeIdentifier.RECORD_TYPE_STEPS_CADENCE, 57 Double.class); 58 59 /** 60 * Metric identifier to retrieve maximum Steps cadence rate using aggregate APIs in {@link 61 * HealthConnectManager} 62 */ 63 @NonNull 64 public static final AggregationType<Double> STEPS_CADENCE_RATE_MAX = 65 new AggregationType<>( 66 AggregationType.AggregationTypeIdentifier.STEPS_CADENCE_RECORD_RATE_MAX, 67 AggregationType.MAX, 68 RecordTypeIdentifier.RECORD_TYPE_STEPS_CADENCE, 69 Double.class); 70 71 private final List<StepsCadenceRecordSample> mStepsCadenceRecordSamples; 72 73 /** 74 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 75 * @param startTime Start time of this activity 76 * @param startZoneOffset Zone offset of the user when the activity started 77 * @param endTime End time of this activity 78 * @param endZoneOffset Zone offset of the user when the activity finished 79 * @param stepsCadenceRecordSamples Samples of recorded StepsCadenceRecord 80 * @param skipValidation Boolean flag to skip validation of record values. 81 */ StepsCadenceRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @NonNull List<StepsCadenceRecordSample> stepsCadenceRecordSamples, boolean skipValidation)82 private StepsCadenceRecord( 83 @NonNull Metadata metadata, 84 @NonNull Instant startTime, 85 @NonNull ZoneOffset startZoneOffset, 86 @NonNull Instant endTime, 87 @NonNull ZoneOffset endZoneOffset, 88 @NonNull List<StepsCadenceRecordSample> stepsCadenceRecordSamples, 89 boolean skipValidation) { 90 super( 91 metadata, 92 startTime, 93 startZoneOffset, 94 endTime, 95 endZoneOffset, 96 skipValidation, 97 /* enforceFutureTimeRestrictions= */ true); 98 Objects.requireNonNull(stepsCadenceRecordSamples); 99 if (!skipValidation) { 100 ValidationUtils.validateSampleStartAndEndTime( 101 startTime, 102 endTime, 103 stepsCadenceRecordSamples.stream() 104 .map(StepsCadenceRecordSample::getTime) 105 .toList()); 106 } 107 mStepsCadenceRecordSamples = stepsCadenceRecordSamples; 108 } 109 110 /** 111 * @return StepsCadenceRecord samples corresponding to this record 112 */ 113 @NonNull getSamples()114 public List<StepsCadenceRecordSample> getSamples() { 115 return mStepsCadenceRecordSamples; 116 } 117 118 /** Represents a single measurement of the steps cadence. */ 119 public static final class StepsCadenceRecordSample { 120 private final double mRate; 121 private final Instant mTime; 122 123 /** 124 * StepsCadenceRecord sample for entries of {@link StepsCadenceRecord} 125 * 126 * @param rate Rate in steps per minute. 127 * @param time The point in time when the measurement was taken. 128 */ StepsCadenceRecordSample(double rate, @NonNull Instant time)129 public StepsCadenceRecordSample(double rate, @NonNull Instant time) { 130 this(rate, time, false); 131 } 132 133 /** 134 * StepsCadenceRecord sample for entries of {@link StepsCadenceRecord} 135 * 136 * @param rate Rate in steps per minute. 137 * @param time The point in time when the measurement was taken. 138 * @param skipValidation Boolean flag to skip validation of record values. 139 * @hide 140 */ StepsCadenceRecordSample( double rate, @NonNull Instant time, boolean skipValidation)141 public StepsCadenceRecordSample( 142 double rate, @NonNull Instant time, boolean skipValidation) { 143 Objects.requireNonNull(time); 144 if (!skipValidation) { 145 ValidationUtils.requireInRange(rate, 0.0, 10000.0, "rate"); 146 } 147 mTime = time; 148 mRate = rate; 149 } 150 151 /** 152 * @return Rate for this sample 153 */ getRate()154 public double getRate() { 155 return mRate; 156 } 157 158 /** 159 * @return time at which this sample was recorded 160 */ 161 @NonNull getTime()162 public Instant getTime() { 163 return mTime; 164 } 165 166 /** 167 * Indicates whether some other object is "equal to" this one. 168 * 169 * @param object the reference object with which to compare. 170 * @return {@code true} if this object is the same as the obj 171 */ 172 @Override equals(@ullable Object object)173 public boolean equals(@Nullable Object object) { 174 if (super.equals(object) && object instanceof StepsCadenceRecordSample) { 175 StepsCadenceRecordSample other = (StepsCadenceRecordSample) object; 176 return getRate() == other.getRate() 177 && getTime().toEpochMilli() == other.getTime().toEpochMilli(); 178 } 179 return false; 180 } 181 182 /** 183 * Returns a hash code value for the object. 184 * 185 * @return a hash code value for this object. 186 */ 187 @Override hashCode()188 public int hashCode() { 189 return Objects.hash(super.hashCode(), getRate(), getTime()); 190 } 191 } 192 193 /** Builder class for {@link StepsCadenceRecord} */ 194 public static final class Builder { 195 private final Metadata mMetadata; 196 private final Instant mStartTime; 197 private final Instant mEndTime; 198 private final List<StepsCadenceRecordSample> mStepsCadenceRecordSamples; 199 private ZoneOffset mStartZoneOffset; 200 private ZoneOffset mEndZoneOffset; 201 202 /** 203 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 204 * @param startTime Start time of this activity 205 * @param endTime End time of this activity 206 * @param stepsCadenceRecordSamples Samples of recorded StepsCadenceRecord 207 */ Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, @NonNull List<StepsCadenceRecordSample> stepsCadenceRecordSamples)208 public Builder( 209 @NonNull Metadata metadata, 210 @NonNull Instant startTime, 211 @NonNull Instant endTime, 212 @NonNull List<StepsCadenceRecordSample> stepsCadenceRecordSamples) { 213 Objects.requireNonNull(metadata); 214 Objects.requireNonNull(startTime); 215 Objects.requireNonNull(endTime); 216 Objects.requireNonNull(stepsCadenceRecordSamples); 217 mMetadata = metadata; 218 mStartTime = startTime; 219 mEndTime = endTime; 220 mStepsCadenceRecordSamples = stepsCadenceRecordSamples; 221 mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime); 222 mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime); 223 } 224 225 /** Sets the zone offset of the user when the activity started */ 226 @NonNull setStartZoneOffset(@onNull ZoneOffset startZoneOffset)227 public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) { 228 Objects.requireNonNull(startZoneOffset); 229 mStartZoneOffset = startZoneOffset; 230 return this; 231 } 232 233 /** Sets the zone offset of the user when the activity ended */ 234 @NonNull setEndZoneOffset(@onNull ZoneOffset endZoneOffset)235 public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) { 236 Objects.requireNonNull(endZoneOffset); 237 mEndZoneOffset = endZoneOffset; 238 return this; 239 } 240 241 /** Sets the start zone offset of this record to system default. */ 242 @NonNull clearStartZoneOffset()243 public Builder clearStartZoneOffset() { 244 mStartZoneOffset = RecordUtils.getDefaultZoneOffset(); 245 return this; 246 } 247 248 /** Sets the start zone offset of this record to system default. */ 249 @NonNull clearEndZoneOffset()250 public Builder clearEndZoneOffset() { 251 mEndZoneOffset = RecordUtils.getDefaultZoneOffset(); 252 return this; 253 } 254 /** 255 * @return Object of {@link StepsCadenceRecord} without validating the values. 256 * @hide 257 */ 258 @NonNull buildWithoutValidation()259 public StepsCadenceRecord buildWithoutValidation() { 260 return new StepsCadenceRecord( 261 mMetadata, 262 mStartTime, 263 mStartZoneOffset, 264 mEndTime, 265 mEndZoneOffset, 266 mStepsCadenceRecordSamples, 267 true); 268 } 269 270 /** 271 * @return Object of {@link StepsCadenceRecord} 272 */ 273 @NonNull build()274 public StepsCadenceRecord build() { 275 return new StepsCadenceRecord( 276 mMetadata, 277 mStartTime, 278 mStartZoneOffset, 279 mEndTime, 280 mEndZoneOffset, 281 mStepsCadenceRecordSamples, 282 false); 283 } 284 } 285 286 /** 287 * Indicates whether some other object is "equal to" this one. 288 * 289 * @param object the reference object with which to compare. 290 * @return {@code true} if this object is the same as the obj 291 */ 292 @Override equals(@ullable Object object)293 public boolean equals(@Nullable Object object) { 294 if (super.equals(object) && object instanceof StepsCadenceRecord) { 295 StepsCadenceRecord other = (StepsCadenceRecord) object; 296 if (getSamples().size() != other.getSamples().size()) return false; 297 for (int idx = 0; idx < getSamples().size(); idx++) { 298 if (getSamples().get(idx).getRate() != other.getSamples().get(idx).getRate() 299 || getSamples().get(idx).getTime().toEpochMilli() 300 != other.getSamples().get(idx).getTime().toEpochMilli()) { 301 return false; 302 } 303 } 304 return true; 305 } 306 return false; 307 } 308 309 /** Returns a hash code value for the object. */ 310 @Override hashCode()311 public int hashCode() { 312 return Objects.hash(super.hashCode(), getSamples()); 313 } 314 315 /** @hide */ 316 @Override toRecordInternal()317 public StepsCadenceRecordInternal toRecordInternal() { 318 StepsCadenceRecordInternal recordInternal = 319 (StepsCadenceRecordInternal) 320 new StepsCadenceRecordInternal() 321 .setUuid(getMetadata().getId()) 322 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 323 .setLastModifiedTime( 324 getMetadata().getLastModifiedTime().toEpochMilli()) 325 .setClientRecordId(getMetadata().getClientRecordId()) 326 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 327 .setManufacturer(getMetadata().getDevice().getManufacturer()) 328 .setModel(getMetadata().getDevice().getModel()) 329 .setDeviceType(getMetadata().getDevice().getType()) 330 .setRecordingMethod(getMetadata().getRecordingMethod()); 331 Set<StepsCadenceRecordInternal.StepsCadenceRecordSample> samples = 332 new HashSet<>(getSamples().size()); 333 334 for (StepsCadenceRecord.StepsCadenceRecordSample stepsCadenceRecordSample : getSamples()) { 335 samples.add( 336 new StepsCadenceRecordInternal.StepsCadenceRecordSample( 337 stepsCadenceRecordSample.getRate(), 338 stepsCadenceRecordSample.getTime().toEpochMilli())); 339 } 340 recordInternal.setSamples(samples); 341 recordInternal.setStartTime(getStartTime().toEpochMilli()); 342 recordInternal.setEndTime(getEndTime().toEpochMilli()); 343 recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds()); 344 recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds()); 345 346 return recordInternal; 347 } 348 } 349