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