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 
17 package android.healthconnect.cts.utils;
18 
19 import static android.health.connect.datatypes.Metadata.RECORDING_METHOD_ACTIVELY_RECORDED;
20 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_BASAL_METABOLIC_RATE;
21 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_HEART_RATE;
22 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_STEPS;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import android.content.Context;
27 import android.health.connect.changelog.ChangeLogTokenRequest;
28 import android.health.connect.datatypes.BasalMetabolicRateRecord;
29 import android.health.connect.datatypes.DataOrigin;
30 import android.health.connect.datatypes.Device;
31 import android.health.connect.datatypes.DistanceRecord;
32 import android.health.connect.datatypes.ExerciseCompletionGoal;
33 import android.health.connect.datatypes.ExerciseLap;
34 import android.health.connect.datatypes.ExerciseRoute;
35 import android.health.connect.datatypes.ExerciseSegment;
36 import android.health.connect.datatypes.ExerciseSegmentType;
37 import android.health.connect.datatypes.ExerciseSessionRecord;
38 import android.health.connect.datatypes.ExerciseSessionType;
39 import android.health.connect.datatypes.HeartRateRecord;
40 import android.health.connect.datatypes.Metadata;
41 import android.health.connect.datatypes.PlannedExerciseBlock;
42 import android.health.connect.datatypes.PlannedExerciseSessionRecord;
43 import android.health.connect.datatypes.PlannedExerciseStep;
44 import android.health.connect.datatypes.Record;
45 import android.health.connect.datatypes.SleepSessionRecord;
46 import android.health.connect.datatypes.StepsRecord;
47 import android.health.connect.datatypes.TotalCaloriesBurnedRecord;
48 import android.health.connect.datatypes.WeightRecord;
49 import android.health.connect.datatypes.units.Energy;
50 import android.health.connect.datatypes.units.Length;
51 import android.health.connect.datatypes.units.Mass;
52 import android.health.connect.datatypes.units.Power;
53 import android.healthconnect.cts.utils.TestUtils.RecordAndIdentifier;
54 
55 import androidx.annotation.Nullable;
56 import androidx.test.core.app.ApplicationProvider;
57 
58 import java.time.Instant;
59 import java.time.ZoneOffset;
60 import java.time.temporal.ChronoUnit;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.UUID;
65 
66 public final class DataFactory {
67     // truncate to MILLIS because HC does, so reduce flakiness in some tests.
68     public static final Instant NOW = Instant.now().truncatedTo(ChronoUnit.MILLIS);
69     public static final Instant SESSION_START_TIME = NOW.minus(10, ChronoUnit.DAYS);
70     public static final Instant SESSION_END_TIME = SESSION_START_TIME.plus(1, ChronoUnit.HOURS);
71     public static final long DEFAULT_LONG = -1;
72     public static final int DEFAULT_PAGE_SIZE = 1000;
73     public static final int MINIMUM_PAGE_SIZE = 1;
74     public static final int MAXIMUM_PAGE_SIZE = 5000;
75 
buildDevice()76     public static Device buildDevice() {
77         return new Device.Builder()
78                 .setManufacturer("google")
79                 .setModel("Pixel4a")
80                 .setType(2)
81                 .build();
82     }
83 
generateMetadata()84     public static Metadata generateMetadata() {
85         return generateMetadata(UUID.randomUUID().toString());
86     }
87 
88     /** Generates a {@link Metadata} with specific {@code id}. */
generateMetadata(String id)89     public static Metadata generateMetadata(String id) {
90         return generateMetadata(id, "clientRecordId" + Math.random());
91     }
92 
93     /** Generates a {@link Metadata} with specific {@code id} and {@code clientId}. */
generateMetadata(String id, String clientId)94     public static Metadata generateMetadata(String id, String clientId) {
95         Context context = ApplicationProvider.getApplicationContext();
96         return new Metadata.Builder()
97                 .setDevice(buildDevice())
98                 .setId(id)
99                 .setClientRecordId(clientId)
100                 .setDataOrigin(
101                         new DataOrigin.Builder().setPackageName(context.getPackageName()).build())
102                 .setRecordingMethod(Metadata.RECORDING_METHOD_UNKNOWN)
103                 .build();
104     }
105 
106     /** Generates a {@link Metadata} with a specific {@code clientId}. */
generateMetadataWithClientId(String clientId)107     public static Metadata generateMetadataWithClientId(String clientId) {
108         return generateMetadata(UUID.randomUUID().toString(), clientId);
109     }
110 
getEmptyMetadata()111     public static Metadata getEmptyMetadata() {
112         return new Metadata.Builder().build();
113     }
114 
115     /** Creates a {@link Metadata} with the given record id. */
getMetadataForId(String id)116     public static Metadata getMetadataForId(String id) {
117         return new Metadata.Builder().setId(id).build();
118     }
119 
120     /** Creates a {@link Metadata} with the given record id and data origin. */
getMetadataForId(String id, DataOrigin dataOrigin)121     public static Metadata getMetadataForId(String id, DataOrigin dataOrigin) {
122         return new Metadata.Builder().setId(id).setDataOrigin(dataOrigin).build();
123     }
124 
125     /** Creates a {@link Metadata} with the given client record id. */
getMetadataForClientId(String clientId)126     public static Metadata getMetadataForClientId(String clientId) {
127         return new Metadata.Builder().setClientRecordId(clientId).build();
128     }
129 
130     /** Creates a {@link Metadata} with the given client record id. */
getMetadataForClientId(String clientId, DataOrigin dataOrigin)131     public static Metadata getMetadataForClientId(String clientId, DataOrigin dataOrigin) {
132         return new Metadata.Builder().setClientRecordId(clientId).setDataOrigin(dataOrigin).build();
133     }
134 
135     /** Creates a {@link Metadata} with the given client record id. */
getMetadataForClientIdAndVersion(String clientId, long clientVersion)136     public static Metadata getMetadataForClientIdAndVersion(String clientId, long clientVersion) {
137         return new Metadata.Builder()
138                 .setClientRecordId(clientId)
139                 .setClientRecordVersion(clientVersion)
140                 .build();
141     }
142 
143     /** Creates a {@link Metadata} with the given data origin. */
getMetadata(DataOrigin dataOrigin)144     public static Metadata getMetadata(DataOrigin dataOrigin) {
145         return new Metadata.Builder().setDataOrigin(dataOrigin).build();
146     }
147 
148     /** Creates a {@link DataOrigin} with the given package name. */
getDataOrigin(String packageName)149     public static DataOrigin getDataOrigin(String packageName) {
150         return new DataOrigin.Builder().setPackageName(packageName).build();
151     }
152 
153     /** Creates a list of {@link DataOrigin} from a list of package names. */
getDataOrigins(String... packageNames)154     public static List<DataOrigin> getDataOrigins(String... packageNames) {
155         return Arrays.stream(packageNames).map(DataFactory::getDataOrigin).toList();
156     }
157 
buildSleepSession()158     public static SleepSessionRecord buildSleepSession() {
159         return buildSleepSession(generateMetadata());
160     }
161 
162     /** Builds a {@link SleepSessionRecord} with a specific {@code clientId}. */
buildSleepSessionWithClientId(String clientId)163     public static SleepSessionRecord buildSleepSessionWithClientId(String clientId) {
164         return buildSleepSession(generateMetadataWithClientId(clientId));
165     }
166 
167     /** Builds a {@link SleepSessionRecord} with empty {@link Metadata}. */
buildSleepSessionWithEmptyMetadata()168     public static SleepSessionRecord buildSleepSessionWithEmptyMetadata() {
169         return buildSleepSession(getEmptyMetadata());
170     }
171 
172     /** Builds a {@link SleepSessionRecord} with a specific {@link Metadata}. */
buildSleepSession(Metadata metadata)173     public static SleepSessionRecord buildSleepSession(Metadata metadata) {
174         return new SleepSessionRecord.Builder(metadata, SESSION_START_TIME, SESSION_END_TIME)
175                 .setNotes("warm")
176                 .setTitle("Afternoon nap")
177                 .setStages(
178                         List.of(
179                                 new SleepSessionRecord.Stage(
180                                         SESSION_START_TIME,
181                                         SESSION_START_TIME.plusSeconds(300),
182                                         SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_LIGHT),
183                                 new SleepSessionRecord.Stage(
184                                         SESSION_START_TIME.plusSeconds(300),
185                                         SESSION_START_TIME.plusSeconds(600),
186                                         SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_REM),
187                                 new SleepSessionRecord.Stage(
188                                         SESSION_START_TIME.plusSeconds(900),
189                                         SESSION_START_TIME.plusSeconds(1200),
190                                         SleepSessionRecord.StageType.STAGE_TYPE_SLEEPING_DEEP)))
191                 .build();
192     }
193 
194     /** Builds a {@link ExerciseSessionRecord} with {@link #generateMetadata()}. */
buildExerciseSession()195     public static ExerciseSessionRecord buildExerciseSession() {
196         return buildExerciseSession(generateMetadata());
197     }
198 
199     /** Builds a {@link ExerciseSessionRecord} with an empty {@link Metadata}. */
buildExerciseSessionWithEmptyMetadata()200     public static ExerciseSessionRecord buildExerciseSessionWithEmptyMetadata() {
201         return buildExerciseSession(getEmptyMetadata());
202     }
203 
204     /** Builds a {@link ExerciseSessionRecord} with a specific {@code clientId}. */
buildExerciseSessionWithClientId(String clientId)205     public static ExerciseSessionRecord buildExerciseSessionWithClientId(String clientId) {
206         return buildExerciseSession(generateMetadataWithClientId(clientId));
207     }
208 
209     /** Builds a {@link ExerciseSessionRecord} with a specific {@link Metadata}. */
buildExerciseSession(Metadata metadata)210     public static ExerciseSessionRecord buildExerciseSession(Metadata metadata) {
211         return new ExerciseSessionRecord.Builder(
212                         metadata,
213                         SESSION_START_TIME,
214                         SESSION_END_TIME,
215                         ExerciseSessionType.EXERCISE_SESSION_TYPE_OTHER_WORKOUT)
216                 .setRoute(buildExerciseRoute())
217                 .setLaps(
218                         List.of(
219                                 new ExerciseLap.Builder(
220                                                 SESSION_START_TIME,
221                                                 SESSION_START_TIME.plusSeconds(20))
222                                         .setLength(Length.fromMeters(10))
223                                         .build(),
224                                 new ExerciseLap.Builder(
225                                                 SESSION_END_TIME.minusSeconds(20), SESSION_END_TIME)
226                                         .build()))
227                 .setSegments(
228                         List.of(
229                                 new ExerciseSegment.Builder(
230                                                 SESSION_START_TIME.plusSeconds(1),
231                                                 SESSION_START_TIME.plusSeconds(10),
232                                                 ExerciseSegmentType
233                                                         .EXERCISE_SEGMENT_TYPE_BENCH_PRESS)
234                                         .build(),
235                                 new ExerciseSegment.Builder(
236                                                 SESSION_START_TIME.plusSeconds(21),
237                                                 SESSION_START_TIME.plusSeconds(124),
238                                                 ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_BURPEE)
239                                         .setRepetitionsCount(15)
240                                         .build()))
241                 .setEndZoneOffset(ZoneOffset.MAX)
242                 .setStartZoneOffset(ZoneOffset.MIN)
243                 .setNotes("rain")
244                 .setTitle("Morning training")
245                 .build();
246     }
247 
buildExerciseRoute()248     public static ExerciseRoute buildExerciseRoute() {
249         return new ExerciseRoute(
250                 List.of(
251                         buildLocationTimePoint(SESSION_START_TIME),
252                         buildLocationTimePoint(SESSION_START_TIME),
253                         buildLocationTimePoint(SESSION_START_TIME)));
254     }
255 
buildLocationTimePoint(Instant startTime)256     public static ExerciseRoute.Location buildLocationTimePoint(Instant startTime) {
257         return new ExerciseRoute.Location.Builder(
258                         Instant.ofEpochMilli(
259                                 (long) (startTime.toEpochMilli() + 10 + Math.random() * 50)),
260                         Math.random() * 50,
261                         Math.random() * 50)
262                 .build();
263     }
264 
265     /** Returns a training plan builder, prepopulated with test data. */
plannedExerciseSession(Metadata metadata)266     public static PlannedExerciseSessionRecord.Builder plannedExerciseSession(Metadata metadata) {
267         PlannedExerciseSessionRecord.Builder sessionBuilder =
268                 new PlannedExerciseSessionRecord.Builder(
269                         metadata,
270                         ExerciseSessionType.EXERCISE_SESSION_TYPE_BIKING,
271                         SESSION_START_TIME,
272                         SESSION_END_TIME);
273         sessionBuilder.setNotes("Some notes");
274         sessionBuilder.setTitle("Some training plan");
275         sessionBuilder.setStartZoneOffset(ZoneOffset.UTC);
276         sessionBuilder.setEndZoneOffset(ZoneOffset.UTC);
277         var stepBuilder =
278                 new PlannedExerciseStep.Builder(
279                         ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_BIKING,
280                         PlannedExerciseStep.EXERCISE_CATEGORY_ACTIVE,
281                         new ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(100)));
282         var blockBuilder = new PlannedExerciseBlock.Builder(3).setDescription("Main set");
283         blockBuilder.setSteps(List.of(stepBuilder.build()));
284         sessionBuilder.setBlocks(List.of(blockBuilder.build()));
285 
286         return sessionBuilder;
287     }
288 
289     /** Gets a {@link HeartRateRecord} with an empty {@link Metadata}. */
getHeartRateRecordWithEmptyMetadata()290     public static HeartRateRecord getHeartRateRecordWithEmptyMetadata() {
291         return getHeartRateRecord(72, getEmptyMetadata());
292     }
293 
294     /** Gets a {@link HeartRateRecord} with a specific heart rate and {@link Metadata}. */
getHeartRateRecord(int heartRate, Metadata metadata)295     public static HeartRateRecord getHeartRateRecord(int heartRate, Metadata metadata) {
296         Instant instant = NOW;
297         HeartRateRecord.HeartRateSample heartRateSample =
298                 new HeartRateRecord.HeartRateSample(heartRate, instant.plusMillis(10));
299         return new HeartRateRecord.Builder(
300                         metadata, instant, instant.plusMillis(1000), List.of(heartRateSample))
301                 .build();
302     }
303 
getHeartRateRecord()304     public static HeartRateRecord getHeartRateRecord() {
305         return getHeartRateRecord(72);
306     }
307 
getHeartRateRecord(int heartRate, String clientId)308     public static HeartRateRecord getHeartRateRecord(int heartRate, String clientId) {
309         return getHeartRateRecord(heartRate, NOW.plusMillis(100), clientId);
310     }
311 
getHeartRateRecord(int heartRate)312     public static HeartRateRecord getHeartRateRecord(int heartRate) {
313         return getHeartRateRecord(heartRate, NOW.plusMillis(100));
314     }
315 
getHeartRateRecord(int heartRate, Instant instant)316     public static HeartRateRecord getHeartRateRecord(int heartRate, Instant instant) {
317         return getHeartRateRecord(heartRate, instant, "HR" + Math.random());
318     }
319 
getHeartRateRecord( List<HeartRateRecord.HeartRateSample> samples, Instant start, Instant end)320     public static HeartRateRecord getHeartRateRecord(
321             List<HeartRateRecord.HeartRateSample> samples, Instant start, Instant end) {
322         return new HeartRateRecord.Builder(getEmptyMetadata(), start, end, samples).build();
323     }
324 
getHeartRateRecord( int heartRate, Instant instant, String clientId)325     public static HeartRateRecord getHeartRateRecord(
326             int heartRate, Instant instant, String clientId) {
327         String packageName = ApplicationProvider.getApplicationContext().getPackageName();
328         HeartRateRecord.HeartRateSample heartRateSample =
329                 new HeartRateRecord.HeartRateSample(heartRate, instant);
330         ArrayList<HeartRateRecord.HeartRateSample> heartRateSamples = new ArrayList<>();
331         heartRateSamples.add(heartRateSample);
332         heartRateSamples.add(heartRateSample);
333         Device device = buildDevice();
334         DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build();
335 
336         return new HeartRateRecord.Builder(
337                         new Metadata.Builder()
338                                 .setDevice(device)
339                                 .setDataOrigin(dataOrigin)
340                                 .setClientRecordId(clientId)
341                                 .build(),
342                         instant.minusMillis(100),
343                         instant.plusMillis(100),
344                         heartRateSamples)
345                 .build();
346     }
347 
348     /** Creates and returns a {@link WeightRecord} with the specified arguments. */
getWeightRecord(double grams, Instant time)349     public static WeightRecord getWeightRecord(double grams, Instant time) {
350         return new WeightRecord.Builder(new Metadata.Builder().build(), time, Mass.fromGrams(grams))
351                 .build();
352     }
353 
getWeightRecord(double weight, Instant time, ZoneOffset offset)354     public static WeightRecord getWeightRecord(double weight, Instant time, ZoneOffset offset) {
355         return new WeightRecord.Builder(getEmptyMetadata(), time, Mass.fromGrams(weight))
356                 .setZoneOffset(offset)
357                 .build();
358     }
359 
getStepsRecordWithEmptyMetaData()360     public static StepsRecord getStepsRecordWithEmptyMetaData() {
361         return getStepsRecord(10, getEmptyMetadata());
362     }
363 
getStepsRecord()364     public static StepsRecord getStepsRecord() {
365         return getStepsRecord(10);
366     }
367 
368     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getStepsRecord(long steps)369     public static StepsRecord getStepsRecord(long steps) {
370         return getStepsRecord(steps, generateMetadata());
371     }
372 
373     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getStepsRecord(long steps, String clientId)374     public static StepsRecord getStepsRecord(long steps, String clientId) {
375         return getStepsRecord(steps, getMetadataForClientId(clientId));
376     }
377 
378     /** Creates and returns a {@link StepsRecord} with the specified metadata. */
getStepsRecord(long steps, Metadata metadata)379     public static StepsRecord getStepsRecord(long steps, Metadata metadata) {
380         return new StepsRecord.Builder(metadata, NOW, NOW.plusMillis(1000), steps).build();
381     }
382 
383     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getStepsRecord(long steps, Instant start, Instant end)384     public static StepsRecord getStepsRecord(long steps, Instant start, Instant end) {
385         return new StepsRecord.Builder(getEmptyMetadata(), start, end, steps).build();
386     }
387 
388     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getStepsRecord( long steps, Instant start, Instant end, String clientId)389     public static StepsRecord getStepsRecord(
390             long steps, Instant start, Instant end, String clientId) {
391         return new StepsRecord.Builder(getMetadataForClientId(clientId), start, end, steps).build();
392     }
393 
getStepsRecord(String id)394     public static StepsRecord getStepsRecord(String id) {
395         return new StepsRecord.Builder(generateMetadata(id), NOW, NOW.plusMillis(1000), 10).build();
396     }
397 
398     /** Creates and returns a {@link StepsRecord} with default arguments. */
getCompleteStepsRecord()399     public static StepsRecord getCompleteStepsRecord() {
400         return getCompleteStepsRecord(
401                 NOW, NOW.plusMillis(1000), /* clientRecordId= */ "SR" + Math.random());
402     }
403 
404     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( Instant startTime, Instant endTime, String clientRecordId)405     public static StepsRecord getCompleteStepsRecord(
406             Instant startTime, Instant endTime, String clientRecordId) {
407         return getCompleteStepsRecord(startTime, endTime, clientRecordId, /* count= */ 10);
408     }
409 
410     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( String id, Instant startTime, Instant endTime, long count)411     public static StepsRecord getCompleteStepsRecord(
412             String id, Instant startTime, Instant endTime, long count) {
413         return getCompleteStepsRecord(
414                 id,
415                 startTime,
416                 endTime,
417                 /* clientRecordId= */ null,
418                 /* clientRecordVersion= */ 0L,
419                 count);
420     }
421 
422     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( Instant startTime, Instant endTime, long count)423     public static StepsRecord getCompleteStepsRecord(
424             Instant startTime, Instant endTime, long count) {
425         return getCompleteStepsRecord(
426                 startTime,
427                 endTime,
428                 /* clientRecordId= */ null,
429                 /* clientRecordVersion= */ 0L,
430                 count);
431     }
432 
433     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( Instant startTime, Instant endTime, String clientRecordId, long count)434     public static StepsRecord getCompleteStepsRecord(
435             Instant startTime, Instant endTime, String clientRecordId, long count) {
436         return getCompleteStepsRecord(
437                 startTime, endTime, clientRecordId, /* clientRecordVersion= */ 0L, count);
438     }
439 
440     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( Instant startTime, Instant endTime, String clientRecordId, long clientRecordVersion, long count)441     public static StepsRecord getCompleteStepsRecord(
442             Instant startTime,
443             Instant endTime,
444             String clientRecordId,
445             long clientRecordVersion,
446             long count) {
447         return getCompleteStepsRecord(
448                 /* id= */ null, startTime, endTime, clientRecordId, clientRecordVersion, count);
449     }
450 
451     /** Creates and returns a {@link StepsRecord} with the specified arguments. */
getCompleteStepsRecord( String id, Instant startTime, Instant endTime, String clientRecordId, long clientRecordVersion, long count)452     public static StepsRecord getCompleteStepsRecord(
453             String id,
454             Instant startTime,
455             Instant endTime,
456             String clientRecordId,
457             long clientRecordVersion,
458             long count) {
459         Device device =
460                 new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build();
461         DataOrigin dataOrigin =
462                 new DataOrigin.Builder().setPackageName("android.healthconnect.cts").build();
463 
464         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
465         if (id != null) {
466             testMetadataBuilder.setId(id);
467         }
468         testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin);
469         testMetadataBuilder.setClientRecordId(clientRecordId);
470         testMetadataBuilder.setClientRecordVersion(clientRecordVersion);
471         testMetadataBuilder.setRecordingMethod(RECORDING_METHOD_ACTIVELY_RECORDED);
472         Metadata testMetaData = testMetadataBuilder.build();
473         assertThat(testMetaData.getRecordingMethod()).isEqualTo(RECORDING_METHOD_ACTIVELY_RECORDED);
474         return new StepsRecord.Builder(testMetaData, startTime, endTime, count).build();
475     }
476 
getUpdatedStepsRecord( Record record, String id, String clientRecordId)477     public static StepsRecord getUpdatedStepsRecord(
478             Record record, String id, String clientRecordId) {
479         Metadata metadata = record.getMetadata();
480         Metadata metadataWithId =
481                 new Metadata.Builder()
482                         .setId(id)
483                         .setClientRecordId(clientRecordId)
484                         .setClientRecordVersion(metadata.getClientRecordVersion())
485                         .setDataOrigin(metadata.getDataOrigin())
486                         .setDevice(metadata.getDevice())
487                         .setLastModifiedTime(metadata.getLastModifiedTime())
488                         .build();
489         return new StepsRecord.Builder(metadataWithId, NOW, NOW.plusMillis(2000), 20)
490                 .setStartZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(NOW))
491                 .setEndZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(NOW))
492                 .build();
493     }
494 
495     /** Creates a {@link DistanceRecord}. */
getDistanceRecord()496     public static DistanceRecord getDistanceRecord() {
497         return getDistanceRecord(10.0, NOW, NOW.plusMillis(1000));
498     }
499 
500     /** Creates a {@link DistanceRecord} with a specified {@code clientId}. */
getDistanceRecordWithClientId(String clientId)501     public static DistanceRecord getDistanceRecordWithClientId(String clientId) {
502         return getDistanceRecord(
503                 10,
504                 NOW,
505                 NOW.plusMillis(1000),
506                 /* startZoneOffset= */ null,
507                 /* endZoneOffset= */ null,
508                 generateMetadataWithClientId(clientId));
509     }
510 
511     /** Create a {@link DistanceRecord} with non empty record ID. */
getDistanceRecordWithNonEmptyId()512     public static DistanceRecord getDistanceRecordWithNonEmptyId() {
513         return getDistanceRecord(
514                 10,
515                 NOW,
516                 NOW.plusMillis(1000),
517                 /* startZoneOffset= */ null,
518                 /* endZoneOffset= */ null,
519                 generateMetadata());
520     }
521 
522     /** Create a {@link DistanceRecord} with empty {@link Metadata}. */
getDistanceRecordWithEmptyMetadata()523     public static DistanceRecord getDistanceRecordWithEmptyMetadata() {
524         return getDistanceRecord(
525                 10,
526                 NOW,
527                 NOW.plusMillis(1000),
528                 /* startZoneOffset= */ null,
529                 /* endZoneOffset= */ null,
530                 getEmptyMetadata());
531     }
532 
533     /** Create a {@link DistanceRecord} with the specified arguments. */
getDistanceRecord(double distance, Instant start, Instant end)534     public static DistanceRecord getDistanceRecord(double distance, Instant start, Instant end) {
535         return getDistanceRecord(
536                 distance,
537                 start,
538                 end,
539                 /* startZoneOffset= */ null,
540                 /* endZoneOffset= */ null,
541                 getEmptyMetadata());
542     }
543 
544     /** Create a {@link DistanceRecord} with the specified arguments. */
getDistanceRecord( double distance, Instant start, Instant end, ZoneOffset offset)545     public static DistanceRecord getDistanceRecord(
546             double distance, Instant start, Instant end, ZoneOffset offset) {
547         return getDistanceRecord(distance, start, end, offset, offset, getEmptyMetadata());
548     }
549 
550     /** Create a {@link DistanceRecord} with the specified arguments. */
getDistanceRecord( double distance, Instant start, Instant end, String clientId)551     public static DistanceRecord getDistanceRecord(
552             double distance, Instant start, Instant end, String clientId) {
553         return getDistanceRecord(
554                 distance,
555                 start,
556                 end,
557                 /* startZoneOffset= */ null,
558                 /* endZoneOffset= */ null,
559                 getMetadataForClientId(clientId));
560     }
561 
562     /** Create a {@link DistanceRecord} with the specified arguments. */
getDistanceRecord( double distance, Instant start, Instant end, @Nullable ZoneOffset startZoneOffset, @Nullable ZoneOffset endZoneOffset, Metadata metadata)563     public static DistanceRecord getDistanceRecord(
564             double distance,
565             Instant start,
566             Instant end,
567             @Nullable ZoneOffset startZoneOffset,
568             @Nullable ZoneOffset endZoneOffset,
569             Metadata metadata) {
570         DistanceRecord.Builder builder =
571                 new DistanceRecord.Builder(metadata, start, end, Length.fromMeters(distance));
572         if (startZoneOffset != null) {
573             builder.setStartZoneOffset(startZoneOffset);
574         }
575         if (endZoneOffset != null) {
576             builder.setEndZoneOffset(endZoneOffset);
577         }
578         return builder.build();
579     }
580 
581     /** Gets a {@link TotalCaloriesBurnedRecord} with a specific {@code clientId}. */
getTotalCaloriesBurnedRecord(String clientId)582     public static TotalCaloriesBurnedRecord getTotalCaloriesBurnedRecord(String clientId) {
583         return getTotalCaloriesBurnedRecord(getMetadataForClientId(clientId));
584     }
585 
586     /** Gets a {@link TotalCaloriesBurnedRecord} with a specific {@link Metadata}. */
getTotalCaloriesBurnedRecord(Metadata metadata)587     public static TotalCaloriesBurnedRecord getTotalCaloriesBurnedRecord(Metadata metadata) {
588         return new TotalCaloriesBurnedRecord.Builder(
589                         metadata, NOW, NOW.plusMillis(1000), Energy.fromCalories(10.0))
590                 .build();
591     }
592 
getTotalCaloriesBurnedRecordWithEmptyMetadata()593     public static TotalCaloriesBurnedRecord getTotalCaloriesBurnedRecordWithEmptyMetadata() {
594         return getTotalCaloriesBurnedRecord(getEmptyMetadata());
595     }
596 
getTestRecords()597     public static List<Record> getTestRecords() {
598         return Arrays.asList(
599                 getStepsRecord(),
600                 getHeartRateRecord(),
601                 getBasalMetabolicRateRecord(),
602                 buildExerciseSession());
603     }
604 
getChangeLogTokenRequestForTestRecordTypes()605     public static ChangeLogTokenRequest.Builder getChangeLogTokenRequestForTestRecordTypes() {
606         return new ChangeLogTokenRequest.Builder()
607                 .addRecordType(StepsRecord.class)
608                 .addRecordType(HeartRateRecord.class)
609                 .addRecordType(BasalMetabolicRateRecord.class)
610                 .addRecordType(ExerciseSessionRecord.class);
611     }
612 
getRecordsAndIdentifiers()613     public static List<RecordAndIdentifier> getRecordsAndIdentifiers() {
614         return Arrays.asList(
615                 new RecordAndIdentifier(RECORD_TYPE_STEPS, getStepsRecord()),
616                 new RecordAndIdentifier(RECORD_TYPE_HEART_RATE, getHeartRateRecord()),
617                 new RecordAndIdentifier(
618                         RECORD_TYPE_BASAL_METABOLIC_RATE, getBasalMetabolicRateRecord()));
619     }
620 
getBasalMetabolicRateRecord()621     public static BasalMetabolicRateRecord getBasalMetabolicRateRecord() {
622         return new BasalMetabolicRateRecord.Builder(generateMetadata(), NOW, Power.fromWatts(100.0))
623                 .build();
624     }
625 }
626