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.validation.ValidationUtils.validateIntDefValue; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.time.Instant; 28 import java.util.Objects; 29 import java.util.Set; 30 31 /** Set of shared metadata fields for {@link Record} */ 32 public final class Metadata { 33 /** Unknown recording method. */ 34 public static final int RECORDING_METHOD_UNKNOWN = 0; 35 36 /** 37 * For actively recorded data by the user. 38 * 39 * <p>For e.g. An exercise session actively recorded by the user using a phone or a watch 40 * device. 41 */ 42 public static final int RECORDING_METHOD_ACTIVELY_RECORDED = 1; 43 44 /** 45 * For passively recorded data by the app. 46 * 47 * <p>For e.g. Steps data recorded by a watch or phone without the user starting a session. 48 */ 49 public static final int RECORDING_METHOD_AUTOMATICALLY_RECORDED = 2; 50 51 /** 52 * For manually entered data by the user. 53 * 54 * <p>For e.g. Nutrition or weight data entered by the user. 55 */ 56 public static final int RECORDING_METHOD_MANUAL_ENTRY = 3; 57 /** 58 * Valid set of values for this IntDef. Update this set when add new type or deprecate existing 59 * type. 60 * 61 * @hide 62 */ 63 public static final Set<Integer> VALID_TYPES = 64 Set.of( 65 RECORDING_METHOD_UNKNOWN, 66 RECORDING_METHOD_ACTIVELY_RECORDED, 67 RECORDING_METHOD_AUTOMATICALLY_RECORDED, 68 RECORDING_METHOD_MANUAL_ENTRY); 69 70 private final Device mDevice; 71 private final DataOrigin mDataOrigin; 72 private final Instant mLastModifiedTime; 73 private final String mClientRecordId; 74 private final long mClientRecordVersion; 75 @RecordingMethod private final int mRecordingMethod; 76 private String mId; 77 78 /** 79 * @param device Optional client supplied device information associated with the data. 80 * @param dataOrigin Where the data comes from, such as application information originally 81 * generated this data. When {@link Record} is created before insertion, this contains a 82 * sentinel value, any assigned value will be ignored. After insertion, this will be 83 * populated with inserted application. 84 * @param id Unique identifier of this data, assigned by the Android Health Platform at 85 * insertion time. When {@link Record} is created before insertion, this takes a sentinel 86 * value, any assigned value will be ignored. 87 * @param lastModifiedTime Automatically populated to when data was last modified (or originally 88 * created). When {@link Record} is created before inserted, this contains a sentinel value, 89 * any assigned value will be ignored. 90 * @param clientRecordId Optional client supplied record unique data identifier associated with 91 * the data. There is guaranteed a single entry for any type of data with same client 92 * provided identifier for a given client. Any new insertions with the same client provided 93 * identifier will either replace or be ignored depending on associated {@code 94 * clientRecordVersion}. @see clientRecordVersion 95 * @param clientRecordVersion Optional client supplied version associated with the data. This 96 * determines conflict resolution outcome when there are multiple insertions of the same 97 * {@code clientRecordId}. Data with the highest {@code clientRecordVersion} takes 98 * precedence. {@code clientRecordVersion} starts with 0. @see clientRecordId 99 * @param recordingMethod Optional client supplied data recording method to help to understand 100 * how the data was recorded. 101 */ Metadata( Device device, DataOrigin dataOrigin, String id, Instant lastModifiedTime, String clientRecordId, long clientRecordVersion, @RecordingMethod int recordingMethod)102 private Metadata( 103 Device device, 104 DataOrigin dataOrigin, 105 String id, 106 Instant lastModifiedTime, 107 String clientRecordId, 108 long clientRecordVersion, 109 @RecordingMethod int recordingMethod) { 110 validateIntDefValue(recordingMethod, VALID_TYPES, RecordingMethod.class.getSimpleName()); 111 mDevice = device; 112 mDataOrigin = dataOrigin; 113 mId = id; 114 mLastModifiedTime = lastModifiedTime; 115 mClientRecordId = clientRecordId; 116 mClientRecordVersion = clientRecordVersion; 117 mRecordingMethod = recordingMethod; 118 } 119 120 /** 121 * @return Client record ID if set, null otherwise 122 */ 123 @Nullable getClientRecordId()124 public String getClientRecordId() { 125 return mClientRecordId; 126 } 127 128 /** 129 * @return Client record version if set, 0 otherwise 130 */ getClientRecordVersion()131 public long getClientRecordVersion() { 132 return mClientRecordVersion; 133 } 134 135 /** 136 * @return Corresponds to package name if set. If no data origin is set {@code 137 * getDataOrigin().getPackageName()} will return null 138 */ 139 @NonNull getDataOrigin()140 public DataOrigin getDataOrigin() { 141 return mDataOrigin; 142 } 143 144 /** 145 * @return Record identifier if set, empty string otherwise 146 */ 147 @NonNull getId()148 public String getId() { 149 return mId; 150 } 151 152 /** 153 * Sets record identifier 154 * 155 * @hide 156 */ setId(@onNull String id)157 public void setId(@NonNull String id) { 158 Objects.requireNonNull(id); 159 160 mId = id; 161 } 162 163 /** Returns recording method which indicates how data was recorded for the {@link Record} */ 164 @RecordingMethod getRecordingMethod()165 public int getRecordingMethod() { 166 return mRecordingMethod; 167 } 168 169 /** 170 * @return Record's last modified time if set, Instant.EPOCH otherwise 171 */ 172 @NonNull getLastModifiedTime()173 public Instant getLastModifiedTime() { 174 return mLastModifiedTime; 175 } 176 177 /** 178 * @return The device details that contributed to this record 179 */ 180 @NonNull getDevice()181 public Device getDevice() { 182 return mDevice; 183 } 184 185 /** 186 * Indicates whether some other object is "equal to" this one. 187 * 188 * @param object the reference object with which to compare. 189 * @return {@code true} if this object is the same as the obj 190 */ 191 @Override equals(@ullable Object object)192 public boolean equals(@Nullable Object object) { 193 if (this == object) return true; 194 if (object instanceof Metadata) { 195 Metadata other = (Metadata) object; 196 return getDevice().equals(other.getDevice()) 197 && getDataOrigin().equals(other.getDataOrigin()) 198 && getId().equals(other.getId()) 199 && Objects.equals(getClientRecordId(), other.getClientRecordId()) 200 && getClientRecordVersion() == other.getClientRecordVersion() 201 && getRecordingMethod() == other.getRecordingMethod(); 202 } 203 return false; 204 } 205 206 /** 207 * Returns a hash code value for the object. 208 * 209 * @return a hash code value for this object. 210 */ 211 @Override hashCode()212 public int hashCode() { 213 return Objects.hash( 214 getDevice(), 215 getDataOrigin(), 216 getId(), 217 getClientRecordId(), 218 getClientRecordVersion(), 219 getLastModifiedTime(), 220 getRecordingMethod()); 221 } 222 223 /** 224 * List of possible Recording method for the {@link Record}. 225 * 226 * @hide 227 */ 228 @IntDef({ 229 RECORDING_METHOD_UNKNOWN, 230 RECORDING_METHOD_ACTIVELY_RECORDED, 231 RECORDING_METHOD_AUTOMATICALLY_RECORDED, 232 RECORDING_METHOD_MANUAL_ENTRY 233 }) 234 @Retention(RetentionPolicy.SOURCE) 235 public @interface RecordingMethod {} 236 237 /** 238 * @see Metadata 239 */ 240 public static final class Builder { 241 private Device mDevice = new Device.Builder().build(); 242 private DataOrigin mDataOrigin = new DataOrigin.Builder().build(); 243 private String mId = ""; 244 private Instant mLastModifiedTime = Instant.EPOCH; 245 private String mClientRecordId; 246 private long mClientRecordVersion = 0; 247 248 @RecordingMethod private int mRecordingMethod = RECORDING_METHOD_UNKNOWN; 249 250 @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression Builder()251 public Builder() {} 252 253 /** Sets optional client supplied device information associated with the data. */ 254 @NonNull setDevice(@onNull Device device)255 public Builder setDevice(@NonNull Device device) { 256 Objects.requireNonNull(device); 257 258 mDevice = device; 259 return this; 260 } 261 262 /** 263 * Sets where the data comes from, such as application information originally generated this 264 * data. When {@link Record} is created before insertion, this contains a sentinel value, 265 * any assigned value will be ignored. After insertion, this will be populated with inserted 266 * application. 267 */ 268 @NonNull setDataOrigin(@onNull DataOrigin dataOrigin)269 public Builder setDataOrigin(@NonNull DataOrigin dataOrigin) { 270 Objects.requireNonNull(dataOrigin); 271 272 mDataOrigin = dataOrigin; 273 return this; 274 } 275 276 /** 277 * Sets unique identifier of this data, assigned by the Android Health Platform at insertion 278 * time. When {@link Record} is created before insertion, this takes a sentinel value, any 279 * assigned value will be ignored. 280 */ 281 @NonNull setId(@onNull String id)282 public Builder setId(@NonNull String id) { 283 Objects.requireNonNull(id); 284 285 mId = id; 286 return this; 287 } 288 289 /** 290 * Sets when data was last modified (or originally created). When {@link Record} is created 291 * before inserted, this contains a sentinel value, any assigned value will be ignored. 292 */ 293 @NonNull setLastModifiedTime(@onNull Instant lastModifiedTime)294 public Builder setLastModifiedTime(@NonNull Instant lastModifiedTime) { 295 Objects.requireNonNull(lastModifiedTime); 296 297 mLastModifiedTime = lastModifiedTime; 298 return this; 299 } 300 301 /** 302 * Sets optional client supplied record unique data identifier associated with the data. 303 * There is guaranteed a single entry for any type of data with same client provided 304 * identifier for a given client. Any new insertions with the same client provided 305 * identifier will either replace or be ignored depending on associated {@code 306 * clientRecordVersion}. @see clientRecordVersion 307 * 308 * <p>A null value means that no clientRecordId is set 309 */ 310 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 311 @NonNull setClientRecordId(@ullable String clientRecordId)312 public Builder setClientRecordId(@Nullable String clientRecordId) { 313 mClientRecordId = clientRecordId; 314 return this; 315 } 316 317 /** 318 * Sets optional client supplied version associated with the data. This determines conflict 319 * resolution outcome when there are multiple insertions of the same {@code clientRecordId}. 320 * Data with the highest {@code clientRecordVersion} takes precedence. {@code 321 * clientRecordVersion} starts with 0. @see clientRecordId 322 */ 323 @NonNull setClientRecordVersion(long clientRecordVersion)324 public Builder setClientRecordVersion(long clientRecordVersion) { 325 mClientRecordVersion = clientRecordVersion; 326 return this; 327 } 328 329 /** 330 * Sets recording method for the {@link Record}. This detail helps to know how the data was 331 * recorded which can be useful for prioritization of the record 332 */ 333 @NonNull setRecordingMethod(@ecordingMethod int recordingMethod)334 public Builder setRecordingMethod(@RecordingMethod int recordingMethod) { 335 336 mRecordingMethod = recordingMethod; 337 return this; 338 } 339 340 /** 341 * @return {@link Metadata} object 342 */ 343 @NonNull build()344 public Metadata build() { 345 return new Metadata( 346 mDevice, 347 mDataOrigin, 348 mId, 349 mLastModifiedTime, 350 mClientRecordId, 351 mClientRecordVersion, 352 mRecordingMethod); 353 } 354 } 355 } 356