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