• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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_ACTIVITY_INTENSITY;
19 import static android.health.connect.datatypes.validation.ValidationUtils.validateIntDefValue;
20 
21 import static com.android.healthfitness.flags.Flags.FLAG_ACTIVITY_INTENSITY;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.IntDef;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.health.connect.datatypes.AggregationType.AggregationTypeIdentifier;
28 import android.health.connect.internal.datatypes.ActivityIntensityRecordInternal;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.time.Duration;
33 import java.time.Instant;
34 import java.time.ZoneOffset;
35 import java.util.Objects;
36 import java.util.Set;
37 
38 /**
39  * Represents intensity of an activity.
40  *
41  * <p>Intensity can be either moderate or vigorous.
42  *
43  * <p>Each record requires the start time, the end time and the activity intensity type.
44  */
45 @FlaggedApi(FLAG_ACTIVITY_INTENSITY)
46 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_ACTIVITY_INTENSITY)
47 public final class ActivityIntensityRecord extends IntervalRecord {
48 
49     /** Moderate activity intensity. */
50     public static final int ACTIVITY_INTENSITY_TYPE_MODERATE = 0;
51 
52     /** Vigorous activity intensity. */
53     public static final int ACTIVITY_INTENSITY_TYPE_VIGOROUS = 1;
54 
55     /**
56      * Metric identifier to retrieve the total duration of moderate activity intensity using
57      * aggregate APIs in {@link android.health.connect.HealthConnectManager}.
58      */
59     @NonNull
60     public static final AggregationType<Duration> MODERATE_DURATION_TOTAL =
61             new AggregationType<>(
62                     AggregationTypeIdentifier.ACTIVITY_INTENSITY_MODERATE_DURATION_TOTAL,
63                     AggregationType.SUM,
64                     RECORD_TYPE_ACTIVITY_INTENSITY,
65                     Duration.class);
66 
67     /**
68      * Metric identifier to retrieve the total duration of vigorous activity intensity using
69      * aggregate APIs in {@link android.health.connect.HealthConnectManager}.
70      */
71     @NonNull
72     public static final AggregationType<Duration> VIGOROUS_DURATION_TOTAL =
73             new AggregationType<>(
74                     AggregationTypeIdentifier.ACTIVITY_INTENSITY_VIGOROUS_DURATION_TOTAL,
75                     AggregationType.SUM,
76                     RECORD_TYPE_ACTIVITY_INTENSITY,
77                     Duration.class);
78 
79     /**
80      * Metric identifier to retrieve the total duration of activity intensity regardless of the type
81      * using aggregate APIs in {@link android.health.connect.HealthConnectManager}.
82      *
83      * <p>Equivalent to {@link #MODERATE_DURATION_TOTAL} + {@link #VIGOROUS_DURATION_TOTAL}.
84      */
85     @NonNull
86     public static final AggregationType<Duration> DURATION_TOTAL =
87             new AggregationType<>(
88                     AggregationTypeIdentifier.ACTIVITY_INTENSITY_DURATION_TOTAL,
89                     AggregationType.SUM,
90                     RECORD_TYPE_ACTIVITY_INTENSITY,
91                     Duration.class);
92 
93     /**
94      * Metric identifier to retrieve the number of weighted intensity minutes using aggregate APIs
95      * in {@link android.health.connect.HealthConnectManager}.
96      *
97      * <p>Records of type {@link #ACTIVITY_INTENSITY_TYPE_MODERATE} contribute their full duration
98      * to the result, while records of type {@link #ACTIVITY_INTENSITY_TYPE_VIGOROUS} contribute
99      * double their duration.
100      *
101      * <p>Equivalent to {@link #MODERATE_DURATION_TOTAL} + 2 * {@link #VIGOROUS_DURATION_TOTAL}
102      * rounded to minutes.
103      *
104      * <p>Calculated in minutes.
105      */
106     @NonNull
107     public static final AggregationType<Long> INTENSITY_MINUTES_TOTAL =
108             new AggregationType<>(
109                     AggregationTypeIdentifier.ACTIVITY_INTENSITY_MINUTES_TOTAL,
110                     AggregationType.SUM,
111                     RECORD_TYPE_ACTIVITY_INTENSITY,
112                     Long.class);
113 
114     /**
115      * Valid set of values for {@link ActivityIntensityType}. Update this set when add a new type or
116      * deprecate an existing type.
117      */
118     private static final Set<Integer> VALID_ACTIVITY_INTENSITY_TYPES =
119             Set.of(ACTIVITY_INTENSITY_TYPE_MODERATE, ACTIVITY_INTENSITY_TYPE_VIGOROUS);
120 
121     private final int mActivityIntensityType;
122 
123     /**
124      * Builds {@link ActivityIntensityRecord} instance
125      *
126      * @param metadata Metadata to be associated with the record. See {@link Metadata}.
127      * @param startTime Start time of this activity
128      * @param startZoneOffset Zone offset of the user when the session started
129      * @param endTime End time of this activity
130      * @param endZoneOffset Zone offset of the user when the session finished
131      * @param activityIntensityType type of the session.
132      * @param skipValidation Boolean flag to skip validation of record values.
133      */
ActivityIntensityRecord( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull ZoneOffset startZoneOffset, @NonNull Instant endTime, @NonNull ZoneOffset endZoneOffset, @ActivityIntensityType int activityIntensityType, boolean skipValidation)134     private ActivityIntensityRecord(
135             @NonNull Metadata metadata,
136             @NonNull Instant startTime,
137             @NonNull ZoneOffset startZoneOffset,
138             @NonNull Instant endTime,
139             @NonNull ZoneOffset endZoneOffset,
140             @ActivityIntensityType int activityIntensityType,
141             boolean skipValidation) {
142         super(
143                 metadata,
144                 startTime,
145                 startZoneOffset,
146                 endTime,
147                 endZoneOffset,
148                 skipValidation,
149                 /* enforceFutureTimeRestrictions= */ true);
150         if (!skipValidation) {
151             validateIntDefValue(
152                     activityIntensityType,
153                     VALID_ACTIVITY_INTENSITY_TYPES,
154                     ActivityIntensityType.class.getSimpleName());
155         }
156         mActivityIntensityType = activityIntensityType;
157     }
158 
159     /** Returns the type of the activity intensity. */
160     @ActivityIntensityType
getActivityIntensityType()161     public int getActivityIntensityType() {
162         return mActivityIntensityType;
163     }
164 
165     @Override
equals(@ullable Object o)166     public boolean equals(@Nullable Object o) {
167         if (this == o) return true;
168         if (!(o instanceof ActivityIntensityRecord)) return false;
169         if (!super.equals(o)) return false;
170         ActivityIntensityRecord that = (ActivityIntensityRecord) o;
171         return getActivityIntensityType() == that.getActivityIntensityType();
172     }
173 
174     @Override
hashCode()175     public int hashCode() {
176         return Objects.hash(super.hashCode(), getActivityIntensityType());
177     }
178 
179     /** @hide */
180     @IntDef({ACTIVITY_INTENSITY_TYPE_MODERATE, ACTIVITY_INTENSITY_TYPE_VIGOROUS})
181     @Retention(RetentionPolicy.SOURCE)
182     public @interface ActivityIntensityType {}
183 
184     /** Builder class for {@link ActivityIntensityRecord} */
185     public static final class Builder {
186         private final Metadata mMetadata;
187         private final Instant mStartTime;
188         private final Instant mEndTime;
189         private ZoneOffset mStartZoneOffset;
190         private ZoneOffset mEndZoneOffset;
191         private final int mActivityIntensityType;
192 
193         /**
194          * @param metadata Metadata to be associated with the record. See {@link Metadata}.
195          * @param startTime Start time of this activity instensity record.
196          * @param endTime End time of this activity intensity record.
197          */
Builder( @onNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime, @ActivityIntensityType int activityIntensityType)198         public Builder(
199                 @NonNull Metadata metadata,
200                 @NonNull Instant startTime,
201                 @NonNull Instant endTime,
202                 @ActivityIntensityType int activityIntensityType) {
203             Objects.requireNonNull(metadata);
204             Objects.requireNonNull(startTime);
205             Objects.requireNonNull(endTime);
206             mMetadata = metadata;
207             mStartTime = startTime;
208             mEndTime = endTime;
209             mActivityIntensityType = activityIntensityType;
210             mStartZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(startTime);
211             mEndZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(endTime);
212         }
213 
214         /**
215          * Sets the {@link ZoneOffset} of the user when the activity started.
216          *
217          * <p>Defaults to the system zone offset if not set.
218          */
219         @NonNull
setStartZoneOffset(@onNull ZoneOffset startZoneOffset)220         public Builder setStartZoneOffset(@NonNull ZoneOffset startZoneOffset) {
221             Objects.requireNonNull(startZoneOffset);
222 
223             mStartZoneOffset = startZoneOffset;
224             return this;
225         }
226 
227         /**
228          * Sets the {@link ZoneOffset} of the user when the activity ended.
229          *
230          * <p>Defaults to the system zone offset if not set.
231          */
232         @NonNull
setEndZoneOffset(@onNull ZoneOffset endZoneOffset)233         public Builder setEndZoneOffset(@NonNull ZoneOffset endZoneOffset) {
234             Objects.requireNonNull(endZoneOffset);
235             mEndZoneOffset = endZoneOffset;
236             return this;
237         }
238 
239         /**
240          * @return Object of {@link ActivityIntensityRecord} without validating the values.
241          * @hide
242          */
243         @NonNull
buildWithoutValidation()244         public ActivityIntensityRecord buildWithoutValidation() {
245             return new ActivityIntensityRecord(
246                     mMetadata,
247                     mStartTime,
248                     mStartZoneOffset,
249                     mEndTime,
250                     mEndZoneOffset,
251                     mActivityIntensityType,
252                     true);
253         }
254 
255         /** Returns {@link ActivityIntensityRecord} */
256         @NonNull
build()257         public ActivityIntensityRecord build() {
258             return new ActivityIntensityRecord(
259                     mMetadata,
260                     mStartTime,
261                     mStartZoneOffset,
262                     mEndTime,
263                     mEndZoneOffset,
264                     mActivityIntensityType,
265                     false);
266         }
267     }
268 
269     /** @hide */
270     @Override
toRecordInternal()271     public ActivityIntensityRecordInternal toRecordInternal() {
272         ActivityIntensityRecordInternal recordInternal =
273                 (ActivityIntensityRecordInternal)
274                         new ActivityIntensityRecordInternal()
275                                 .setUuid(getMetadata().getId())
276                                 .setPackageName(getMetadata().getDataOrigin().getPackageName())
277                                 .setLastModifiedTime(
278                                         getMetadata().getLastModifiedTime().toEpochMilli())
279                                 .setClientRecordId(getMetadata().getClientRecordId())
280                                 .setClientRecordVersion(getMetadata().getClientRecordVersion())
281                                 .setManufacturer(getMetadata().getDevice().getManufacturer())
282                                 .setModel(getMetadata().getDevice().getModel())
283                                 .setDeviceType(getMetadata().getDevice().getType())
284                                 .setRecordingMethod(getMetadata().getRecordingMethod());
285         recordInternal.setStartTime(getStartTime().toEpochMilli());
286         recordInternal.setEndTime(getEndTime().toEpochMilli());
287         recordInternal.setStartZoneOffset(getStartZoneOffset().getTotalSeconds());
288         recordInternal.setEndZoneOffset(getEndZoneOffset().getTotalSeconds());
289         recordInternal.setActivityIntensityType(getActivityIntensityType());
290         return recordInternal;
291     }
292 }
293