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 17 package android.health.connect.datatypes; 18 19 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_STEPS; 20 21 import android.annotation.NonNull; 22 import android.health.connect.HealthConnectManager; 23 import android.health.connect.datatypes.validation.ValidationUtils; 24 import android.health.connect.internal.datatypes.StepsRecordInternal; 25 26 import java.time.Instant; 27 import java.time.ZoneOffset; 28 import java.util.Objects; 29 30 /** 31 * Captures the number of steps taken since the last reading. Each step is only reported once so 32 * records shouldn't have overlapping time. The start time of each record should represent the start 33 * of the interval in which steps were taken. 34 */ 35 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_STEPS) 36 public final class StepsRecord extends IntervalRecord { 37 /** Builder class for {@link StepsRecord} */ 38 public static final class Builder { 39 private final Metadata mMetadata; 40 private final Instant mStartTime; 41 private final Instant mEndTime; 42 private final long mCount; 43 private ZoneOffset mStartZoneOffset; 44 private ZoneOffset mEndZoneOffset; 45 /** 46 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 47 * @param startTime Start time of this activity 48 * @param endTime End time of this activity 49 * @param count Number of steps recorded for this activity 50 */ Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, long count)51 public Builder( 52 @NonNull Metadata metadata, 53 @NonNull Instant startTime, 54 @NonNull Instant endTime, 55 long count) { 56 Objects.requireNonNull(metadata); 57 Objects.requireNonNull(startTime); 58 Objects.requireNonNull(endTime); 59 mMetadata = metadata; 60 mStartTime = startTime; 61 mEndTime = endTime; 62 mCount = count; 63 mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime); 64 mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime); 65 } 66 67 /** Sets the zone offset of the user when the activity started */ 68 @NonNull setStartZoneOffset(@onNull ZoneOffset startZoneOffset)69 public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) { 70 Objects.requireNonNull(startZoneOffset); 71 72 mStartZoneOffset = startZoneOffset; 73 return this; 74 } 75 76 /** Sets the zone offset of the user when the activity ended */ 77 @NonNull setEndZoneOffset(@onNull ZoneOffset endZoneOffset)78 public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) { 79 Objects.requireNonNull(endZoneOffset); 80 81 mEndZoneOffset = endZoneOffset; 82 return this; 83 } 84 85 /** Sets the start zone offset of this record to system default. */ 86 @NonNull clearStartZoneOffset()87 public Builder clearStartZoneOffset() { 88 mStartZoneOffset = RecordUtils.getDefaultZoneOffset(); 89 return this; 90 } 91 92 /** Sets the start zone offset of this record to system default. */ 93 @NonNull clearEndZoneOffset()94 public Builder clearEndZoneOffset() { 95 mEndZoneOffset = RecordUtils.getDefaultZoneOffset(); 96 return this; 97 } 98 99 /** 100 * @return Object of {@link StepsRecord} without validating the values. 101 * @hide 102 */ 103 @NonNull buildWithoutValidation()104 public StepsRecord buildWithoutValidation() { 105 return new StepsRecord( 106 mMetadata, 107 mStartTime, 108 mStartZoneOffset, 109 mEndTime, 110 mEndZoneOffset, 111 mCount, 112 true); 113 } 114 115 /** 116 * @return Object of {@link StepsRecord} 117 */ 118 @NonNull build()119 public StepsRecord build() { 120 return new StepsRecord( 121 mMetadata, 122 mStartTime, 123 mStartZoneOffset, 124 mEndTime, 125 mEndZoneOffset, 126 mCount, 127 false); 128 } 129 } 130 131 /** 132 * Metric identifier to get total steps count using aggregate APIs in {@link 133 * HealthConnectManager} 134 */ 135 @NonNull 136 public static final AggregationType<Long> STEPS_COUNT_TOTAL = 137 new AggregationType<>( 138 AggregationType.AggregationTypeIdentifier.STEPS_RECORD_COUNT_TOTAL, 139 AggregationType.SUM, 140 RECORD_TYPE_STEPS, 141 Long.class); 142 143 private final long mCount; 144 StepsRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, long count, boolean skipValidation)145 private StepsRecord( 146 @NonNull Metadata metadata, 147 @NonNull Instant startTime, 148 @NonNull ZoneOffset startZoneOffset, 149 @NonNull Instant endTime, 150 @NonNull ZoneOffset endZoneOffset, 151 long count, 152 boolean skipValidation) { 153 super( 154 metadata, 155 startTime, 156 startZoneOffset, 157 endTime, 158 endZoneOffset, 159 skipValidation, 160 /* enforceFutureTimeRestrictions= */ true); 161 if (!skipValidation) { 162 ValidationUtils.requireInRange(count, 0, 1000000, "stepsCount"); 163 } 164 mCount = count; 165 } 166 167 /** 168 * @return Number of steps taken 169 */ getCount()170 public long getCount() { 171 return mCount; 172 } 173 174 /** 175 * Indicates whether some other object is "equal to" this one. 176 * 177 * @param o the reference object with which to compare. 178 * @return {@code true} if this object is the same as the obj 179 */ 180 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 181 @Override equals(Object o)182 public boolean equals(Object o) { 183 if (this == o) return true; 184 if (!super.equals(o)) return false; 185 StepsRecord record = (StepsRecord) o; 186 return getCount() == record.getCount(); 187 } 188 189 /** 190 * @return a hash code value for this object. 191 */ 192 @Override hashCode()193 public int hashCode() { 194 return Objects.hash(super.hashCode(), getCount()); 195 } 196 197 /** @hide */ 198 @Override toRecordInternal()199 public StepsRecordInternal toRecordInternal() { 200 StepsRecordInternal recordInternal = 201 (StepsRecordInternal) 202 new StepsRecordInternal() 203 .setUuid(getMetadata().getId()) 204 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 205 .setLastModifiedTime( 206 getMetadata().getLastModifiedTime().toEpochMilli()) 207 .setClientRecordId(getMetadata().getClientRecordId()) 208 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 209 .setManufacturer(getMetadata().getDevice().getManufacturer()) 210 .setModel(getMetadata().getDevice().getModel()) 211 .setDeviceType(getMetadata().getDevice().getType()) 212 .setRecordingMethod(getMetadata().getRecordingMethod()); 213 recordInternal.setStartTime(getStartTime().toEpochMilli()); 214 recordInternal.setEndTime(getEndTime().toEpochMilli()); 215 recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds()); 216 recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds()); 217 recordInternal.setCount((int) mCount); 218 return recordInternal; 219 } 220 } 221