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