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.internal.datatypes;
18 
19 import static android.health.connect.Constants.DEFAULT_INT;
20 import static android.health.connect.Constants.DEFAULT_LONG;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.health.connect.datatypes.DataOrigin;
25 import android.health.connect.datatypes.Device;
26 import android.health.connect.datatypes.Identifier;
27 import android.health.connect.datatypes.Metadata;
28 import android.health.connect.datatypes.Record;
29 import android.health.connect.datatypes.RecordTypeIdentifier;
30 import android.os.Parcel;
31 
32 import java.time.Instant;
33 import java.time.LocalDate;
34 import java.util.Objects;
35 import java.util.UUID;
36 
37 /**
38  * Base class for all health connect datatype records.
39  *
40  * @hide
41  */
42 public abstract class RecordInternal<T extends Record> {
43     private final int mRecordIdentifier;
44     private UUID mUuid;
45     private String mPackageName;
46     private String mAppName;
47     private long mLastModifiedTime = DEFAULT_LONG;
48     private String mClientRecordId;
49     private long mClientRecordVersion = DEFAULT_LONG;
50     private String mManufacturer;
51     private String mModel;
52     private int mDeviceType;
53     private long mDeviceInfoId = DEFAULT_LONG;
54     private long mAppInfoId = DEFAULT_LONG;
55     private int mRowId = DEFAULT_INT;
56 
57     @Metadata.RecordingMethod private int mRecordingMethod;
58 
59     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
RecordInternal()60     RecordInternal() {
61         Identifier annotation = this.getClass().getAnnotation(Identifier.class);
62         Objects.requireNonNull(annotation);
63         mRecordIdentifier = annotation.recordIdentifier();
64     }
65 
66     @RecordTypeIdentifier.RecordType
getRecordType()67     public int getRecordType() {
68         return mRecordIdentifier;
69     }
70 
71     /**
72      * Populates self with the data present in {@code parcel}. Reads should be in the same order as
73      * write
74      */
populateUsing(@onNull Parcel parcel)75     public final void populateUsing(@NonNull Parcel parcel) {
76         String uuidString = parcel.readString();
77         if (uuidString != null && !uuidString.isEmpty()) {
78             mUuid = UUID.fromString(uuidString);
79         }
80         mPackageName = parcel.readString();
81         mAppName = parcel.readString();
82         mLastModifiedTime = parcel.readLong();
83         mClientRecordId = parcel.readString();
84         mClientRecordVersion = parcel.readLong();
85         mManufacturer = parcel.readString();
86         mModel = parcel.readString();
87         mDeviceType = parcel.readInt();
88         mRecordingMethod = parcel.readInt();
89 
90         populateRecordFrom(parcel);
91     }
92 
93     /**
94      * Populates {@code parcel} with the self information, required to reconstructor this object
95      * during IPC
96      */
97     @NonNull
writeToParcel(@onNull Parcel parcel)98     public final void writeToParcel(@NonNull Parcel parcel) {
99         parcel.writeString(mUuid == null ? "" : mUuid.toString());
100         parcel.writeString(mPackageName);
101         parcel.writeString(mAppName);
102         parcel.writeLong(mLastModifiedTime);
103         parcel.writeString(mClientRecordId);
104         parcel.writeLong(mClientRecordVersion);
105         parcel.writeString(mManufacturer);
106         parcel.writeString(mModel);
107         parcel.writeInt(mDeviceType);
108         parcel.writeInt(mRecordingMethod);
109 
110         populateRecordTo(parcel);
111     }
112 
113     @Nullable
getUuid()114     public UUID getUuid() {
115         return mUuid;
116     }
117 
118     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
119     @NonNull
setUuid(@ullable UUID uuid)120     public RecordInternal<T> setUuid(@Nullable UUID uuid) {
121         this.mUuid = uuid;
122         return this;
123     }
124 
125     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
126     @NonNull
setUuid(@ullable String uuid)127     public RecordInternal<T> setUuid(@Nullable String uuid) {
128         if (uuid == null || uuid.isEmpty()) {
129             mUuid = null;
130             return this;
131         }
132 
133         mUuid = UUID.fromString(uuid);
134         return this;
135     }
136 
137     @Nullable
getPackageName()138     public String getPackageName() {
139         return mPackageName;
140     }
141 
142     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
143     @NonNull
setPackageName(@ullable String packageName)144     public RecordInternal<T> setPackageName(@Nullable String packageName) {
145         this.mPackageName = packageName;
146         return this;
147     }
148 
149     /** Gets row id of this record. */
getRowId()150     public int getRowId() {
151         return mRowId;
152     }
153 
154     /** Sets the row id for this record. */
setRowId(int rowId)155     public RecordInternal<T> setRowId(int rowId) {
156         mRowId = rowId;
157         return this;
158     }
159 
160     /**
161      * Returns an application name associated with this record. Currently, it is used for AppInfo
162      * generation when inserting a record. May be {@code null}, in which case the app name may be
163      * missing in AppInfo.
164      */
165     @Nullable
getAppName()166     public String getAppName() {
167         return mAppName;
168     }
169 
170     /** Sets the application name for this record. */
171     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
172     @NonNull
setAppName(@ullable String appName)173     public RecordInternal<T> setAppName(@Nullable String appName) {
174         mAppName = appName;
175         return this;
176     }
177 
getLastModifiedTime()178     public long getLastModifiedTime() {
179         return mLastModifiedTime;
180     }
181 
182     @NonNull
setLastModifiedTime(long lastModifiedTime)183     public RecordInternal<T> setLastModifiedTime(long lastModifiedTime) {
184         this.mLastModifiedTime = lastModifiedTime;
185         return this;
186     }
187 
188     @Nullable
getClientRecordId()189     public String getClientRecordId() {
190         return mClientRecordId;
191     }
192 
193     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
194     @NonNull
setClientRecordId(@ullable String clientRecordId)195     public RecordInternal<T> setClientRecordId(@Nullable String clientRecordId) {
196         this.mClientRecordId = clientRecordId;
197         return this;
198     }
199 
getClientRecordVersion()200     public long getClientRecordVersion() {
201         return mClientRecordVersion;
202     }
203 
204     @NonNull
setClientRecordVersion(long clientRecordVersion)205     public RecordInternal<T> setClientRecordVersion(long clientRecordVersion) {
206         this.mClientRecordVersion = clientRecordVersion;
207         return this;
208     }
209 
210     @Nullable
getManufacturer()211     public String getManufacturer() {
212         return mManufacturer;
213     }
214 
215     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
216     @NonNull
setManufacturer(@ullable String manufacturer)217     public RecordInternal<T> setManufacturer(@Nullable String manufacturer) {
218         this.mManufacturer = manufacturer;
219         return this;
220     }
221 
222     @Nullable
getModel()223     public String getModel() {
224         return mModel;
225     }
226 
227     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
228     @NonNull
setModel(@ullable String model)229     public RecordInternal<T> setModel(@Nullable String model) {
230         this.mModel = model;
231         return this;
232     }
233 
234     @Device.DeviceType
getDeviceType()235     public int getDeviceType() {
236         return mDeviceType;
237     }
238 
239     @NonNull
setDeviceType(@evice.DeviceType int deviceType)240     public RecordInternal<T> setDeviceType(@Device.DeviceType int deviceType) {
241         this.mDeviceType = deviceType;
242         return this;
243     }
244 
getDeviceInfoId()245     public long getDeviceInfoId() {
246         return mDeviceInfoId;
247     }
248 
249     @NonNull
setDeviceInfoId(long deviceInfoId)250     public RecordInternal<T> setDeviceInfoId(long deviceInfoId) {
251         this.mDeviceInfoId = deviceInfoId;
252         return this;
253     }
254 
getAppInfoId()255     public long getAppInfoId() {
256         return mAppInfoId;
257     }
258 
259     @NonNull
setAppInfoId(long appInfoId)260     public RecordInternal<T> setAppInfoId(long appInfoId) {
261         this.mAppInfoId = appInfoId;
262         return this;
263     }
264 
265     /** Returns recording method which indicates how data was recorded for the {@link Record} */
266     @Metadata.RecordingMethod
getRecordingMethod()267     public int getRecordingMethod() {
268         return mRecordingMethod;
269     }
270 
271     /** Sets Recording method to know how data was recorded for the {@link Record} */
272     @NonNull
setRecordingMethod(@etadata.RecordingMethod int recordingMethod)273     public RecordInternal<T> setRecordingMethod(@Metadata.RecordingMethod int recordingMethod) {
274         this.mRecordingMethod = recordingMethod;
275         return this;
276     }
277 
278     /** Child class must implement this method and return an external record for this record */
toExternalRecord()279     public abstract T toExternalRecord();
280 
281     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
282     @NonNull
buildMetaData()283     Metadata buildMetaData() {
284         return new Metadata.Builder()
285                 .setClientRecordId(getClientRecordId())
286                 .setClientRecordVersion(getClientRecordVersion())
287                 .setDataOrigin(new DataOrigin.Builder().setPackageName(getPackageName()).build())
288                 .setId(getUuid() == null ? null : getUuid().toString())
289                 .setLastModifiedTime(Instant.ofEpochMilli(getLastModifiedTime()))
290                 .setRecordingMethod(getRecordingMethod())
291                 .setDevice(
292                         new Device.Builder()
293                                 .setManufacturer(getManufacturer())
294                                 .setType(getDeviceType())
295                                 .setModel(getModel())
296                                 .build())
297                 .build();
298     }
299 
300     /**
301      * @return the {@link LocalDate} object of this activity start time.
302      */
getLocalDate()303     public abstract LocalDate getLocalDate();
304 
305     /**
306      * Populate {@code bundle} with the data required to un-bundle self. This is used suring IPC
307      * transmissions
308      */
populateRecordTo(@onNull Parcel bundle)309     abstract void populateRecordTo(@NonNull Parcel bundle);
310 
311     /**
312      * Child class must implement this method and populates itself with the data present in {@code
313      * bundle}
314      */
populateRecordFrom(@onNull Parcel bundle)315     abstract void populateRecordFrom(@NonNull Parcel bundle);
316 }
317