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.validation.ValidationUtils.validateIntDefValue; 19 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.health.connect.datatypes.units.BloodGlucose; 23 import android.health.connect.datatypes.validation.ValidationUtils; 24 import android.health.connect.internal.datatypes.BloodGlucoseRecordInternal; 25 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.time.Instant; 29 import java.time.ZoneOffset; 30 import java.util.Objects; 31 import java.util.Set; 32 33 /** 34 * Captures the concentration of glucose in the blood. Each record represents a single instantaneous 35 * blood glucose reading. 36 */ 37 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_BLOOD_GLUCOSE) 38 public final class BloodGlucoseRecord extends InstantRecord { 39 private final int mSpecimenSource; 40 private final BloodGlucose mLevel; 41 private final int mRelationToMeal; 42 private final int mMealType; 43 44 /** 45 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 46 * @param time Start time of this activity 47 * @param zoneOffset Zone offset of the user when the activity started 48 * @param specimenSource SpecimenSource of this activity 49 * @param level Level of this activity 50 * @param relationToMeal RelationToMeal of this activity 51 * @param mealType MealType of this activity 52 * @param skipValidation Boolean flag to skip validation of record values. 53 */ BloodGlucoseRecord( @onNull Metadata metadata, @NonNull Instant time, @NonNull ZoneOffset zoneOffset, @SpecimenSource.SpecimenSourceType int specimenSource, @NonNull BloodGlucose level, @RelationToMealType.RelationToMealTypes int relationToMeal, @MealType.MealTypes int mealType, boolean skipValidation)54 private BloodGlucoseRecord( 55 @NonNull Metadata metadata, 56 @NonNull Instant time, 57 @NonNull ZoneOffset zoneOffset, 58 @SpecimenSource.SpecimenSourceType int specimenSource, 59 @NonNull BloodGlucose level, 60 @RelationToMealType.RelationToMealTypes int relationToMeal, 61 @MealType.MealTypes int mealType, 62 boolean skipValidation) { 63 super(metadata, time, zoneOffset, skipValidation); 64 Objects.requireNonNull(metadata); 65 Objects.requireNonNull(time); 66 Objects.requireNonNull(zoneOffset); 67 Objects.requireNonNull(level); 68 if (!skipValidation) { 69 ValidationUtils.requireInRange(level.getInMillimolesPerLiter(), 0.0, 50.0, "level"); 70 } 71 validateIntDefValue( 72 specimenSource, SpecimenSource.VALID_TYPES, SpecimenSource.class.getSimpleName()); 73 validateIntDefValue( 74 relationToMeal, 75 RelationToMealType.VALID_TYPES, 76 RelationToMealType.class.getSimpleName()); 77 validateIntDefValue(mealType, MealType.VALID_TYPES, MealType.class.getSimpleName()); 78 mSpecimenSource = specimenSource; 79 mLevel = level; 80 mRelationToMeal = relationToMeal; 81 mMealType = mealType; 82 } 83 84 /** 85 * @return specimenSource 86 */ 87 @SpecimenSource.SpecimenSourceType getSpecimenSource()88 public int getSpecimenSource() { 89 return mSpecimenSource; 90 } 91 92 /** 93 * @return level 94 */ 95 @NonNull getLevel()96 public BloodGlucose getLevel() { 97 return mLevel; 98 } 99 100 /** 101 * @return relationToMeal 102 */ 103 @RelationToMealType.RelationToMealTypes getRelationToMeal()104 public int getRelationToMeal() { 105 return mRelationToMeal; 106 } 107 108 /** 109 * @return mealType 110 */ 111 @MealType.MealTypes getMealType()112 public int getMealType() { 113 return mMealType; 114 } 115 116 /** Relationship of the meal to the blood glucose measurement. */ 117 public static final class RelationToMealType { 118 119 /** Represents a blood glucose which relationship to any meal is unknown / not defined. */ 120 public static final int RELATION_TO_MEAL_UNKNOWN = 0; 121 122 /** Reading was not taken immediately before or after eating. */ 123 public static final int RELATION_TO_MEAL_GENERAL = 1; 124 125 /** Reading was taken during a fasting period. */ 126 public static final int RELATION_TO_MEAL_FASTING = 2; 127 128 /** Reading was taken before an unspecified meal. */ 129 public static final int RELATION_TO_MEAL_BEFORE_MEAL = 3; 130 131 /** Reading was taken after an unspecified meal. */ 132 public static final int RELATION_TO_MEAL_AFTER_MEAL = 4; 133 134 /** 135 * Valid set of values for this IntDef. Update this set when add new type or deprecate 136 * existing type. 137 * 138 * @hide 139 */ 140 public static final Set<Integer> VALID_TYPES = 141 Set.of( 142 RELATION_TO_MEAL_UNKNOWN, 143 RELATION_TO_MEAL_GENERAL, 144 RELATION_TO_MEAL_FASTING, 145 RELATION_TO_MEAL_BEFORE_MEAL, 146 RELATION_TO_MEAL_AFTER_MEAL); 147 RelationToMealType()148 private RelationToMealType() {} 149 150 /** @hide */ 151 @IntDef({ 152 RELATION_TO_MEAL_UNKNOWN, 153 RELATION_TO_MEAL_GENERAL, 154 RELATION_TO_MEAL_FASTING, 155 RELATION_TO_MEAL_BEFORE_MEAL, 156 RELATION_TO_MEAL_AFTER_MEAL 157 }) 158 @Retention(RetentionPolicy.SOURCE) 159 public @interface RelationToMealTypes {} 160 } 161 162 /** Type of body fluid used to measure the blood glucose. */ 163 public static final class SpecimenSource { 164 /** Fluid used to measure glucose is not identified. */ 165 public static final int SPECIMEN_SOURCE_UNKNOWN = 0; 166 /** Glucose was measured in interstitial fluid. */ 167 public static final int SPECIMEN_SOURCE_INTERSTITIAL_FLUID = 1; 168 /** Glucose was measured in capillary blood. */ 169 public static final int SPECIMEN_SOURCE_CAPILLARY_BLOOD = 2; 170 /** Glucose was measured in plasma. */ 171 public static final int SPECIMEN_SOURCE_PLASMA = 3; 172 /** Glucose was measured in serum. */ 173 public static final int SPECIMEN_SOURCE_SERUM = 4; 174 /** Glucose was measured in tears. */ 175 public static final int SPECIMEN_SOURCE_TEARS = 5; 176 /** Glucose was measured from whole blood. */ 177 public static final int SPECIMEN_SOURCE_WHOLE_BLOOD = 6; 178 179 /** 180 * Valid set of values for this IntDef. Update this set when add new type or deprecate 181 * existing type. 182 * 183 * @hide 184 */ 185 public static final Set<Integer> VALID_TYPES = 186 Set.of( 187 SPECIMEN_SOURCE_UNKNOWN, 188 SPECIMEN_SOURCE_INTERSTITIAL_FLUID, 189 SPECIMEN_SOURCE_CAPILLARY_BLOOD, 190 SPECIMEN_SOURCE_PLASMA, 191 SPECIMEN_SOURCE_SERUM, 192 SPECIMEN_SOURCE_TEARS, 193 SPECIMEN_SOURCE_WHOLE_BLOOD); 194 SpecimenSource()195 private SpecimenSource() {} 196 197 /** @hide */ 198 @IntDef({ 199 SPECIMEN_SOURCE_UNKNOWN, 200 SPECIMEN_SOURCE_INTERSTITIAL_FLUID, 201 SPECIMEN_SOURCE_CAPILLARY_BLOOD, 202 SPECIMEN_SOURCE_PLASMA, 203 SPECIMEN_SOURCE_SERUM, 204 SPECIMEN_SOURCE_TEARS, 205 SPECIMEN_SOURCE_WHOLE_BLOOD 206 }) 207 @Retention(RetentionPolicy.SOURCE) 208 public @interface SpecimenSourceType {} 209 } 210 211 /** 212 * Indicates whether some other object is "equal to" this one. 213 * 214 * @param o the reference object with which to compare. 215 * @return {@code true} if this object is the same as the obj 216 */ 217 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 218 @Override equals(Object o)219 public boolean equals(Object o) { 220 if (this == o) return true; 221 if (!super.equals(o)) return false; 222 BloodGlucoseRecord that = (BloodGlucoseRecord) o; 223 return getSpecimenSource() == that.getSpecimenSource() 224 && getRelationToMeal() == that.getRelationToMeal() 225 && getMealType() == that.getMealType() 226 && Objects.equals(getLevel(), that.getLevel()); 227 } 228 229 /** Returns a hash code value for the object. */ 230 @Override hashCode()231 public int hashCode() { 232 return Objects.hash( 233 super.hashCode(), 234 getSpecimenSource(), 235 getLevel(), 236 getRelationToMeal(), 237 getMealType()); 238 } 239 240 /** Builder class for {@link BloodGlucoseRecord} */ 241 public static final class Builder { 242 private final Metadata mMetadata; 243 private final Instant mTime; 244 private ZoneOffset mZoneOffset; 245 private final int mSpecimenSource; 246 private final BloodGlucose mLevel; 247 private final int mRelationToMeal; 248 private final int mMealType; 249 250 /** 251 * @param metadata Metadata to be associated with the record. See {@link Metadata}. 252 * @param time Start time of this activity 253 * @param specimenSource Type of body fluid used to measure the blood glucose. Optional, 254 * enum field. Allowed values: {@link SpecimenSource}. 255 * @param level Blood glucose level or concentration in {@link BloodGlucose} unit. Required 256 * field. Valid range: 0-50 mmol/L. 257 * @param relationToMeal Relationship of the meal to the blood glucose measurement. 258 * Optional, enum field. Allowed values: {@link RelationToMealType}. 259 * @param mealType Type of meal related to the blood glucose measurement. Optional, enum 260 * field. Allowed values: {@link MealType}. 261 */ Builder( @onNull Metadata metadata, @NonNull Instant time, @SpecimenSource.SpecimenSourceType int specimenSource, @NonNull BloodGlucose level, @RelationToMealType.RelationToMealTypes int relationToMeal, @MealType.MealTypes int mealType)262 public Builder( 263 @NonNull Metadata metadata, 264 @NonNull Instant time, 265 @SpecimenSource.SpecimenSourceType int specimenSource, 266 @NonNull BloodGlucose level, 267 @RelationToMealType.RelationToMealTypes int relationToMeal, 268 @MealType.MealTypes int mealType) { 269 Objects.requireNonNull(metadata); 270 Objects.requireNonNull(time); 271 Objects.requireNonNull(level); 272 mMetadata = metadata; 273 mTime = time; 274 mSpecimenSource = specimenSource; 275 mLevel = level; 276 mRelationToMeal = relationToMeal; 277 mMealType = mealType; 278 mZoneOffset = ZoneOffset.systemDefault().getRules().getOffset(time); 279 } 280 281 /** Sets the zone offset of the user when the activity happened */ 282 @NonNull setZoneOffset(@onNull ZoneOffset zoneOffset)283 public Builder setZoneOffset(@NonNull ZoneOffset zoneOffset) { 284 Objects.requireNonNull(zoneOffset); 285 mZoneOffset = zoneOffset; 286 return this; 287 } 288 289 /** Sets the zone offset of this record to system default. */ 290 @NonNull clearZoneOffset()291 public Builder clearZoneOffset() { 292 mZoneOffset = RecordUtils.getDefaultZoneOffset(); 293 return this; 294 } 295 /** 296 * @return Object of {@link BloodGlucoseRecord} without validating the values. 297 * @hide 298 */ 299 @NonNull buildWithoutValidation()300 public BloodGlucoseRecord buildWithoutValidation() { 301 return new BloodGlucoseRecord( 302 mMetadata, 303 mTime, 304 mZoneOffset, 305 mSpecimenSource, 306 mLevel, 307 mRelationToMeal, 308 mMealType, 309 true); 310 } 311 312 /** 313 * @return Object of {@link BloodGlucoseRecord} 314 */ 315 @NonNull build()316 public BloodGlucoseRecord build() { 317 return new BloodGlucoseRecord( 318 mMetadata, 319 mTime, 320 mZoneOffset, 321 mSpecimenSource, 322 mLevel, 323 mRelationToMeal, 324 mMealType, 325 false); 326 } 327 } 328 329 /** @hide */ 330 @Override toRecordInternal()331 public BloodGlucoseRecordInternal toRecordInternal() { 332 BloodGlucoseRecordInternal recordInternal = 333 (BloodGlucoseRecordInternal) 334 new BloodGlucoseRecordInternal() 335 .setUuid(getMetadata().getId()) 336 .setPackageName(getMetadata().getDataOrigin().getPackageName()) 337 .setLastModifiedTime( 338 getMetadata().getLastModifiedTime().toEpochMilli()) 339 .setClientRecordId(getMetadata().getClientRecordId()) 340 .setClientRecordVersion(getMetadata().getClientRecordVersion()) 341 .setManufacturer(getMetadata().getDevice().getManufacturer()) 342 .setModel(getMetadata().getDevice().getModel()) 343 .setDeviceType(getMetadata().getDevice().getType()) 344 .setRecordingMethod(getMetadata().getRecordingMethod()); 345 recordInternal.setTime(getTime().toEpochMilli()); 346 recordInternal.setZoneOffset(getZoneOffset().getTotalSeconds()); 347 recordInternal.setSpecimenSource(mSpecimenSource); 348 recordInternal.setLevel(mLevel.getInMillimolesPerLiter()); 349 recordInternal.setRelationToMeal(mRelationToMeal); 350 recordInternal.setMealType(mMealType); 351 return recordInternal; 352 } 353 } 354