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