1 /*
2  * Copyright (C) 2022 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.datatypes;
18 
19 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_AVG;
20 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_MAX;
21 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_MIN;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import android.content.Context;
26 import android.health.connect.AggregateRecordsRequest;
27 import android.health.connect.AggregateRecordsResponse;
28 import android.health.connect.DeleteUsingFiltersRequest;
29 import android.health.connect.HealthConnectException;
30 import android.health.connect.HealthDataCategory;
31 import android.health.connect.ReadRecordsRequestUsingFilters;
32 import android.health.connect.ReadRecordsRequestUsingIds;
33 import android.health.connect.RecordIdFilter;
34 import android.health.connect.TimeInstantRangeFilter;
35 import android.health.connect.changelog.ChangeLogTokenRequest;
36 import android.health.connect.changelog.ChangeLogTokenResponse;
37 import android.health.connect.changelog.ChangeLogsRequest;
38 import android.health.connect.changelog.ChangeLogsResponse;
39 import android.health.connect.datatypes.AggregationType;
40 import android.health.connect.datatypes.DataOrigin;
41 import android.health.connect.datatypes.Device;
42 import android.health.connect.datatypes.Metadata;
43 import android.health.connect.datatypes.Record;
44 import android.health.connect.datatypes.StepsCadenceRecord;
45 import android.healthconnect.cts.utils.AssumptionCheckerRule;
46 import android.healthconnect.cts.utils.TestUtils;
47 import android.platform.test.annotations.AppModeFull;
48 
49 import androidx.test.core.app.ApplicationProvider;
50 import androidx.test.runner.AndroidJUnit4;
51 
52 import org.junit.After;
53 import org.junit.Assert;
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.time.Instant;
60 import java.time.ZoneOffset;
61 import java.time.temporal.ChronoUnit;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collections;
65 import java.util.List;
66 import java.util.Set;
67 import java.util.UUID;
68 
69 @AppModeFull(reason = "HealthConnectManager is not accessible to instant apps")
70 @RunWith(AndroidJUnit4.class)
71 public class StepsCadenceRecordTest {
72 
73     private static final String TAG = "StepsCadenceRecordTest";
74 
75     private static final String PACKAGE_NAME = "android.healthconnect.cts";
76 
77     @Rule
78     public AssumptionCheckerRule mSupportedHardwareRule =
79             new AssumptionCheckerRule(
80                     TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
81 
82     @Before
setUp()83     public void setUp() throws InterruptedException {
84         TestUtils.deleteAllStagedRemoteData();
85     }
86 
87     @After
tearDown()88     public void tearDown() throws InterruptedException {
89         TestUtils.verifyDeleteRecords(
90                 StepsCadenceRecord.class,
91                 new TimeInstantRangeFilter.Builder()
92                         .setStartTime(Instant.EPOCH)
93                         .setEndTime(Instant.now())
94                         .build());
95         TestUtils.deleteAllStagedRemoteData();
96     }
97 
98     @Test
testInsertStepsCadenceRecord()99     public void testInsertStepsCadenceRecord() throws InterruptedException {
100         TestUtils.insertRecords(
101                 Arrays.asList(getBaseStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
102     }
103 
104     @Test
testReadStepsCadenceRecord_usingIds()105     public void testReadStepsCadenceRecord_usingIds() throws InterruptedException {
106         testReadStepsCadenceRecordIds();
107     }
108 
109     @Test
testReadStepsCadenceRecord_invalidIds()110     public void testReadStepsCadenceRecord_invalidIds() throws InterruptedException {
111         ReadRecordsRequestUsingIds<StepsCadenceRecord> request =
112                 new ReadRecordsRequestUsingIds.Builder<>(StepsCadenceRecord.class)
113                         .addId(UUID.randomUUID().toString())
114                         .build();
115         List<StepsCadenceRecord> result = TestUtils.readRecords(request);
116         assertThat(result.size()).isEqualTo(0);
117     }
118 
119     @Test
testReadStepsCadenceRecord_usingClientRecordIds()120     public void testReadStepsCadenceRecord_usingClientRecordIds() throws InterruptedException {
121         List<Record> recordList =
122                 Arrays.asList(getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord());
123         List<Record> insertedRecords = TestUtils.insertRecords(recordList);
124         readStepsCadenceRecordUsingClientId(insertedRecords);
125     }
126 
127     @Test
testReadStepsCadenceRecord_invalidClientRecordIds()128     public void testReadStepsCadenceRecord_invalidClientRecordIds() throws InterruptedException {
129         ReadRecordsRequestUsingIds<StepsCadenceRecord> request =
130                 new ReadRecordsRequestUsingIds.Builder<>(StepsCadenceRecord.class)
131                         .addClientRecordId("abc")
132                         .build();
133         List<StepsCadenceRecord> result = TestUtils.readRecords(request);
134         assertThat(result.size()).isEqualTo(0);
135     }
136 
137     @Test
testReadStepsCadenceRecordUsingFilters_default()138     public void testReadStepsCadenceRecordUsingFilters_default() throws InterruptedException {
139         List<StepsCadenceRecord> oldStepsCadenceRecords =
140                 TestUtils.readRecords(
141                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
142                                 .build());
143 
144         StepsCadenceRecord testRecord =
145                 (StepsCadenceRecord) TestUtils.insertRecord(getCompleteStepsCadenceRecord());
146         List<StepsCadenceRecord> newStepsCadenceRecords =
147                 TestUtils.readRecords(
148                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
149                                 .build());
150         assertThat(newStepsCadenceRecords.size()).isEqualTo(oldStepsCadenceRecords.size() + 1);
151         assertThat(newStepsCadenceRecords.get(newStepsCadenceRecords.size() - 1).equals(testRecord))
152                 .isTrue();
153     }
154 
155     @Test
testReadStepsCadenceRecordUsingFilters_timeFilter()156     public void testReadStepsCadenceRecordUsingFilters_timeFilter() throws InterruptedException {
157         TimeInstantRangeFilter filter =
158                 new TimeInstantRangeFilter.Builder()
159                         .setStartTime(Instant.now())
160                         .setEndTime(Instant.now().plusMillis(3000))
161                         .build();
162 
163         StepsCadenceRecord testRecord =
164                 (StepsCadenceRecord) TestUtils.insertRecord(getCompleteStepsCadenceRecord());
165         List<StepsCadenceRecord> newStepsCadenceRecords =
166                 TestUtils.readRecords(
167                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
168                                 .setTimeRangeFilter(filter)
169                                 .build());
170         assertThat(newStepsCadenceRecords.size()).isEqualTo(1);
171         assertThat(newStepsCadenceRecords.get(newStepsCadenceRecords.size() - 1).equals(testRecord))
172                 .isTrue();
173     }
174 
175     @Test
testReadStepsCadenceRecordUsingFilters_dataFilter_correct()176     public void testReadStepsCadenceRecordUsingFilters_dataFilter_correct()
177             throws InterruptedException {
178         Context context = ApplicationProvider.getApplicationContext();
179         List<StepsCadenceRecord> oldStepsCadenceRecords =
180                 TestUtils.readRecords(
181                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
182                                 .addDataOrigins(
183                                         new DataOrigin.Builder()
184                                                 .setPackageName(context.getPackageName())
185                                                 .build())
186                                 .build());
187 
188         StepsCadenceRecord testRecord =
189                 (StepsCadenceRecord) TestUtils.insertRecord(getCompleteStepsCadenceRecord());
190         List<StepsCadenceRecord> newStepsCadenceRecords =
191                 TestUtils.readRecords(
192                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
193                                 .addDataOrigins(
194                                         new DataOrigin.Builder()
195                                                 .setPackageName(context.getPackageName())
196                                                 .build())
197                                 .build());
198         assertThat(newStepsCadenceRecords.size() - oldStepsCadenceRecords.size()).isEqualTo(1);
199         assertThat(newStepsCadenceRecords.get(newStepsCadenceRecords.size() - 1).equals(testRecord))
200                 .isTrue();
201         StepsCadenceRecord newRecord =
202                 newStepsCadenceRecords.get(newStepsCadenceRecords.size() - 1);
203         assertThat(newRecord.equals(testRecord)).isTrue();
204         for (int idx = 0; idx < newRecord.getSamples().size(); idx++) {
205             assertThat(newRecord.getSamples().get(idx).getTime().toEpochMilli())
206                     .isEqualTo(testRecord.getSamples().get(idx).getTime().toEpochMilli());
207             assertThat(newRecord.getSamples().get(idx).getRate())
208                     .isEqualTo(testRecord.getSamples().get(idx).getRate());
209         }
210     }
211 
212     @Test
testReadStepsCadenceRecordUsingFilters_dataFilter_incorrect()213     public void testReadStepsCadenceRecordUsingFilters_dataFilter_incorrect()
214             throws InterruptedException {
215         TestUtils.insertRecords(Collections.singletonList(getCompleteStepsCadenceRecord()));
216         List<StepsCadenceRecord> newStepsCadenceRecords =
217                 TestUtils.readRecords(
218                         new ReadRecordsRequestUsingFilters.Builder<>(StepsCadenceRecord.class)
219                                 .addDataOrigins(
220                                         new DataOrigin.Builder().setPackageName("abc").build())
221                                 .build());
222         assertThat(newStepsCadenceRecords.size()).isEqualTo(0);
223     }
224 
225     @Test
testDeleteStepsCadenceRecord_no_filters()226     public void testDeleteStepsCadenceRecord_no_filters() throws InterruptedException {
227         String id = TestUtils.insertRecordAndGetId(getCompleteStepsCadenceRecord());
228         TestUtils.verifyDeleteRecords(new DeleteUsingFiltersRequest.Builder().build());
229         TestUtils.assertRecordNotFound(id, StepsCadenceRecord.class);
230     }
231 
232     @Test
testDeleteStepsCadenceRecord_time_filters()233     public void testDeleteStepsCadenceRecord_time_filters() throws InterruptedException {
234         TimeInstantRangeFilter timeRangeFilter =
235                 new TimeInstantRangeFilter.Builder()
236                         .setStartTime(Instant.now())
237                         .setEndTime(Instant.now().plusMillis(1000))
238                         .build();
239         String id = TestUtils.insertRecordAndGetId(getCompleteStepsCadenceRecord());
240         TestUtils.verifyDeleteRecords(
241                 new DeleteUsingFiltersRequest.Builder()
242                         .addRecordType(StepsCadenceRecord.class)
243                         .setTimeRangeFilter(timeRangeFilter)
244                         .build());
245         TestUtils.assertRecordNotFound(id, StepsCadenceRecord.class);
246     }
247 
248     @Test
testDeleteStepsCadenceRecord_recordId_filters()249     public void testDeleteStepsCadenceRecord_recordId_filters() throws InterruptedException {
250         List<Record> records =
251                 TestUtils.insertRecords(
252                         List.of(getBaseStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
253 
254         for (Record record : records) {
255             TestUtils.verifyDeleteRecords(
256                     new DeleteUsingFiltersRequest.Builder()
257                             .addRecordType(record.getClass())
258                             .build());
259             TestUtils.assertRecordNotFound(record.getMetadata().getId(), record.getClass());
260         }
261     }
262 
263     @Test
testDeleteStepsCadenceRecord_dataOrigin_filters()264     public void testDeleteStepsCadenceRecord_dataOrigin_filters() throws InterruptedException {
265         Context context = ApplicationProvider.getApplicationContext();
266         String id = TestUtils.insertRecordAndGetId(getCompleteStepsCadenceRecord());
267         TestUtils.verifyDeleteRecords(
268                 new DeleteUsingFiltersRequest.Builder()
269                         .addDataOrigin(
270                                 new DataOrigin.Builder()
271                                         .setPackageName(context.getPackageName())
272                                         .build())
273                         .build());
274         TestUtils.assertRecordNotFound(id, StepsCadenceRecord.class);
275     }
276 
277     @Test
testDeleteStepsCadenceRecord_dataOrigin_filter_incorrect()278     public void testDeleteStepsCadenceRecord_dataOrigin_filter_incorrect()
279             throws InterruptedException {
280         String id = TestUtils.insertRecordAndGetId(getCompleteStepsCadenceRecord());
281         TestUtils.verifyDeleteRecords(
282                 new DeleteUsingFiltersRequest.Builder()
283                         .addDataOrigin(new DataOrigin.Builder().setPackageName("abc").build())
284                         .build());
285         TestUtils.assertRecordFound(id, StepsCadenceRecord.class);
286     }
287 
288     @Test
testDeleteStepsCadenceRecord_usingIds()289     public void testDeleteStepsCadenceRecord_usingIds() throws InterruptedException {
290         List<Record> insertedRecord =
291                 TestUtils.insertRecords(
292                         List.of(getBaseStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
293         List<RecordIdFilter> recordIds = new ArrayList<>(insertedRecord.size());
294         for (Record record : insertedRecord) {
295             recordIds.add(RecordIdFilter.fromId(record.getClass(), record.getMetadata().getId()));
296         }
297 
298         TestUtils.verifyDeleteRecords(recordIds);
299         for (Record record : insertedRecord) {
300             TestUtils.assertRecordNotFound(record.getMetadata().getId(), record.getClass());
301         }
302     }
303 
304     @Test
testDeleteStepsCadenceRecord_time_range()305     public void testDeleteStepsCadenceRecord_time_range() throws InterruptedException {
306         TimeInstantRangeFilter timeRangeFilter =
307                 new TimeInstantRangeFilter.Builder()
308                         .setStartTime(Instant.now())
309                         .setEndTime(Instant.now().plusMillis(1000))
310                         .build();
311         String id = TestUtils.insertRecordAndGetId(getCompleteStepsCadenceRecord());
312         TestUtils.verifyDeleteRecords(StepsCadenceRecord.class, timeRangeFilter);
313         TestUtils.assertRecordNotFound(id, StepsCadenceRecord.class);
314     }
315 
316     @Test
testZoneOffsets()317     public void testZoneOffsets() {
318         final ZoneOffset defaultZoneOffset =
319                 ZoneOffset.systemDefault().getRules().getOffset(Instant.now());
320         final ZoneOffset startZoneOffset = ZoneOffset.UTC;
321         final ZoneOffset endZoneOffset = ZoneOffset.MAX;
322         StepsCadenceRecord.Builder builder =
323                 new StepsCadenceRecord.Builder(
324                         new Metadata.Builder().build(),
325                         Instant.now(),
326                         Instant.now().plusMillis(1000),
327                         Collections.emptyList());
328 
329         assertThat(builder.setStartZoneOffset(startZoneOffset).build().getStartZoneOffset())
330                 .isEqualTo(startZoneOffset);
331         assertThat(builder.setEndZoneOffset(endZoneOffset).build().getEndZoneOffset())
332                 .isEqualTo(endZoneOffset);
333         assertThat(builder.clearStartZoneOffset().build().getStartZoneOffset())
334                 .isEqualTo(defaultZoneOffset);
335         assertThat(builder.clearEndZoneOffset().build().getEndZoneOffset())
336                 .isEqualTo(defaultZoneOffset);
337     }
338 
339     @Test
testUpdateRecords_validInput_dataBaseUpdatedSuccessfully()340     public void testUpdateRecords_validInput_dataBaseUpdatedSuccessfully()
341             throws InterruptedException {
342 
343         List<Record> insertedRecords =
344                 TestUtils.insertRecords(
345                         Arrays.asList(
346                                 getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
347 
348         // read inserted records and verify that the data is same as inserted.
349         readStepsCadenceRecordUsingIds(insertedRecords);
350 
351         // Generate a new set of records that will be used to perform the update operation.
352         List<Record> updateRecords =
353                 Arrays.asList(getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord());
354 
355         // Modify the uid of the updateRecords to the uuid that was present in the insert records.
356         for (int itr = 0; itr < updateRecords.size(); itr++) {
357             updateRecords.set(
358                     itr,
359                     getStepsCadenceRecord_update(
360                             updateRecords.get(itr),
361                             insertedRecords.get(itr).getMetadata().getId(),
362                             insertedRecords.get(itr).getMetadata().getClientRecordId()));
363         }
364 
365         TestUtils.updateRecords(updateRecords);
366 
367         // assert the inserted data has been modified by reading the data.
368         readStepsCadenceRecordUsingIds(updateRecords);
369     }
370 
371     @Test
testUpdateRecords_invalidInputRecords_noChangeInDataBase()372     public void testUpdateRecords_invalidInputRecords_noChangeInDataBase()
373             throws InterruptedException {
374         List<Record> insertedRecords =
375                 TestUtils.insertRecords(
376                         Arrays.asList(
377                                 getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
378 
379         // read inserted records and verify that the data is same as inserted.
380         readStepsCadenceRecordUsingIds(insertedRecords);
381 
382         // Generate a second set of records that will be used to perform the update operation.
383         List<Record> updateRecords =
384                 Arrays.asList(getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord());
385 
386         // Modify the Uid of the updateRecords to the UUID that was present in the insert records,
387         // leaving out alternate records so that they have a new UUID which is not present in the
388         // dataBase.
389         for (int itr = 0; itr < updateRecords.size(); itr++) {
390             updateRecords.set(
391                     itr,
392                     getStepsCadenceRecord_update(
393                             updateRecords.get(itr),
394                             itr % 2 == 0
395                                     ? insertedRecords.get(itr).getMetadata().getId()
396                                     : UUID.randomUUID().toString(),
397                             itr % 2 == 0
398                                     ? insertedRecords.get(itr).getMetadata().getId()
399                                     : UUID.randomUUID().toString()));
400         }
401 
402         try {
403             TestUtils.updateRecords(updateRecords);
404             Assert.fail("Expected to fail due to invalid records ids.");
405         } catch (HealthConnectException exception) {
406             assertThat(exception.getErrorCode())
407                     .isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT);
408         }
409 
410         // assert the inserted data has not been modified by reading the data.
411         readStepsCadenceRecordUsingIds(insertedRecords);
412     }
413 
414     @Test
testUpdateRecords_recordWithInvalidPackageName_noChangeInDataBase()415     public void testUpdateRecords_recordWithInvalidPackageName_noChangeInDataBase()
416             throws InterruptedException {
417         List<Record> insertedRecords =
418                 TestUtils.insertRecords(
419                         Arrays.asList(
420                                 getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
421 
422         // read inserted records and verify that the data is same as inserted.
423         readStepsCadenceRecordUsingIds(insertedRecords);
424 
425         // Generate a second set of records that will be used to perform the update operation.
426         List<Record> updateRecords =
427                 Arrays.asList(getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord());
428 
429         // Modify the Uuid of the updateRecords to the uuid that was present in the insert records.
430         for (int itr = 0; itr < updateRecords.size(); itr++) {
431             updateRecords.set(
432                     itr,
433                     getStepsCadenceRecord_update(
434                             updateRecords.get(itr),
435                             insertedRecords.get(itr).getMetadata().getId(),
436                             insertedRecords.get(itr).getMetadata().getClientRecordId()));
437             //             adding an entry with invalid packageName.
438             updateRecords.set(itr, getCompleteStepsCadenceRecord());
439         }
440 
441         try {
442             TestUtils.updateRecords(updateRecords);
443             Assert.fail("Expected to fail due to invalid package.");
444         } catch (Exception exception) {
445             // verify that the testcase failed due to invalid argument exception.
446             assertThat(exception).isNotNull();
447         }
448 
449         // assert the inserted data has not been modified by reading the data.
450         readStepsCadenceRecordUsingIds(insertedRecords);
451     }
452 
453     @Test
testInsertAndDeleteRecord_changelogs()454     public void testInsertAndDeleteRecord_changelogs() throws InterruptedException {
455         Context context = ApplicationProvider.getApplicationContext();
456         ChangeLogTokenResponse tokenResponse =
457                 TestUtils.getChangeLogToken(
458                         new ChangeLogTokenRequest.Builder()
459                                 .addDataOriginFilter(
460                                         new DataOrigin.Builder()
461                                                 .setPackageName(context.getPackageName())
462                                                 .build())
463                                 .addRecordType(StepsCadenceRecord.class)
464                                 .build());
465         ChangeLogsRequest changeLogsRequest =
466                 new ChangeLogsRequest.Builder(tokenResponse.getToken()).build();
467         ChangeLogsResponse response = TestUtils.getChangeLogs(changeLogsRequest);
468         assertThat(response.getUpsertedRecords().size()).isEqualTo(0);
469         assertThat(response.getDeletedLogs().size()).isEqualTo(0);
470 
471         List<Record> testRecord =
472                 TestUtils.insertRecords(Collections.singletonList(getCompleteStepsCadenceRecord()));
473         response = TestUtils.getChangeLogs(changeLogsRequest);
474         assertThat(response.getUpsertedRecords().size()).isEqualTo(1);
475         assertThat(
476                         response.getUpsertedRecords().stream()
477                                 .map(Record::getMetadata)
478                                 .map(Metadata::getId)
479                                 .toList())
480                 .containsExactlyElementsIn(
481                         testRecord.stream().map(Record::getMetadata).map(Metadata::getId).toList());
482         assertThat(response.getDeletedLogs().size()).isEqualTo(0);
483 
484         TestUtils.verifyDeleteRecords(
485                 new DeleteUsingFiltersRequest.Builder()
486                         .addRecordType(StepsCadenceRecord.class)
487                         .build());
488         response = TestUtils.getChangeLogs(changeLogsRequest);
489         assertThat(response.getDeletedLogs()).hasSize(testRecord.size());
490         assertThat(
491                         response.getDeletedLogs().stream()
492                                 .map(ChangeLogsResponse.DeletedLog::getDeletedRecordId)
493                                 .toList())
494                 .containsExactlyElementsIn(
495                         testRecord.stream().map(Record::getMetadata).map(Metadata::getId).toList());
496     }
497 
testReadStepsCadenceRecordIds()498     private void testReadStepsCadenceRecordIds() throws InterruptedException {
499         List<Record> recordList =
500                 TestUtils.insertRecords(
501                         Arrays.asList(
502                                 getCompleteStepsCadenceRecord(), getCompleteStepsCadenceRecord()));
503         readStepsCadenceRecordUsingIds(recordList);
504     }
505 
readStepsCadenceRecordUsingClientId(List<Record> insertedRecord)506     private void readStepsCadenceRecordUsingClientId(List<Record> insertedRecord)
507             throws InterruptedException {
508         ReadRecordsRequestUsingIds.Builder<StepsCadenceRecord> request =
509                 new ReadRecordsRequestUsingIds.Builder<>(StepsCadenceRecord.class);
510         for (Record record : insertedRecord) {
511             request.addClientRecordId(record.getMetadata().getClientRecordId());
512         }
513         List<StepsCadenceRecord> result = TestUtils.readRecords(request.build());
514         assertThat(result.size()).isEqualTo(insertedRecord.size());
515         assertThat(result).containsExactlyElementsIn(insertedRecord);
516     }
517 
readStepsCadenceRecordUsingIds(List<Record> insertedRecords)518     private void readStepsCadenceRecordUsingIds(List<Record> insertedRecords)
519             throws InterruptedException {
520         ReadRecordsRequestUsingIds.Builder<StepsCadenceRecord> request =
521                 new ReadRecordsRequestUsingIds.Builder<>(StepsCadenceRecord.class);
522         for (Record record : insertedRecords) {
523             request.addId(record.getMetadata().getId());
524         }
525         ReadRecordsRequestUsingIds requestUsingIds = request.build();
526         assertThat(requestUsingIds.getRecordType()).isEqualTo(StepsCadenceRecord.class);
527         assertThat(requestUsingIds.getRecordIdFilters()).isNotNull();
528         List<StepsCadenceRecord> result = TestUtils.readRecords(requestUsingIds);
529         assertThat(result).hasSize(insertedRecords.size());
530         assertThat(result.containsAll(insertedRecords)).isTrue();
531     }
532 
533     @Test(expected = IllegalArgumentException.class)
testCreateStepsCadenceRecord_invalidValue()534     public void testCreateStepsCadenceRecord_invalidValue() {
535         new StepsCadenceRecord.StepsCadenceRecordSample(10001.0, Instant.now().plusMillis(100));
536     }
537 
538     @Test
testInsertWithClientVersion()539     public void testInsertWithClientVersion() throws InterruptedException {
540         List<Record> records = List.of(getStepsCadenceRecordWithClientVersion(10, 1, "testId"));
541         final String id = TestUtils.insertRecords(records).get(0).getMetadata().getId();
542         ReadRecordsRequestUsingIds<StepsCadenceRecord> request =
543                 new ReadRecordsRequestUsingIds.Builder<>(StepsCadenceRecord.class)
544                         .addClientRecordId("testId")
545                         .build();
546         StepsCadenceRecord stepsRecord = TestUtils.readRecords(request).get(0);
547         int sampleSize = ((StepsCadenceRecord) records.get(0)).getSamples().size();
548         assertThat(stepsRecord.getSamples()).hasSize(sampleSize);
549         assertThat(stepsRecord.getSamples().get(0).getRate()).isEqualTo(10);
550 
551         records = List.of(getStepsCadenceRecordWithClientVersion(20, 2, "testId"));
552         TestUtils.insertRecords(records);
553 
554         stepsRecord = TestUtils.readRecords(request).get(0);
555         assertThat(stepsRecord.getMetadata().getId()).isEqualTo(id);
556         assertThat(stepsRecord.getSamples()).hasSize(sampleSize);
557         assertThat(stepsRecord.getSamples().get(0).getRate()).isEqualTo(20);
558 
559         records = List.of(getStepsCadenceRecordWithClientVersion(30, 1, "testId"));
560         TestUtils.insertRecords(records);
561         stepsRecord = TestUtils.readRecords(request).get(0);
562         assertThat(stepsRecord.getMetadata().getId()).isEqualTo(id);
563         assertThat(stepsRecord.getSamples()).hasSize(sampleSize);
564         assertThat(stepsRecord.getSamples().get(0).getRate()).isEqualTo(20);
565     }
566 
567     @Test
testRateAggregation_getAggregationFromThreerecords_aggResponsesAreCorrect()568     public void testRateAggregation_getAggregationFromThreerecords_aggResponsesAreCorrect()
569             throws Exception {
570         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
571         List<Record> records =
572                 Arrays.asList(
573                         buildRecordForStepsCadence(120, 100),
574                         buildRecordForStepsCadence(100, 101),
575                         buildRecordForStepsCadence(80, 102));
576         AggregateRecordsResponse<Double> response =
577                 TestUtils.getAggregateResponse(
578                         new AggregateRecordsRequest.Builder<Double>(
579                                         new TimeInstantRangeFilter.Builder()
580                                                 .setStartTime(Instant.ofEpochMilli(0))
581                                                 .setEndTime(Instant.now().plus(1, ChronoUnit.DAYS))
582                                                 .build())
583                                 .addAggregationType(STEPS_CADENCE_RATE_MAX)
584                                 .addAggregationType(STEPS_CADENCE_RATE_MIN)
585                                 .addAggregationType(STEPS_CADENCE_RATE_AVG)
586                                 .build(),
587                         records);
588         checkAggregationResult(STEPS_CADENCE_RATE_MAX, 120, response);
589         checkAggregationResult(STEPS_CADENCE_RATE_MIN, 80, response);
590         checkAggregationResult(STEPS_CADENCE_RATE_AVG, 100, response);
591     }
592 
checkAggregationResult( AggregationType<Double> type, double expectedResult, AggregateRecordsResponse<Double> response)593     private void checkAggregationResult(
594             AggregationType<Double> type,
595             double expectedResult,
596             AggregateRecordsResponse<Double> response) {
597         assertThat(response.get(type)).isNotNull();
598         assertThat(response.get(type)).isEqualTo(expectedResult);
599         assertThat(response.getZoneOffset(type))
600                 .isEqualTo(ZoneOffset.systemDefault().getRules().getOffset(Instant.now()));
601         Set<DataOrigin> dataOrigins = response.getDataOrigins(type);
602         assertThat(dataOrigins).hasSize(1);
603         for (DataOrigin itr : dataOrigins) {
604             assertThat(itr.getPackageName()).isEqualTo("android.healthconnect.cts");
605         }
606     }
607 
getStepsCadenceRecord_update( Record record, String id, String clientRecordId)608     StepsCadenceRecord getStepsCadenceRecord_update(
609             Record record, String id, String clientRecordId) {
610         Metadata metadata = record.getMetadata();
611         Metadata metadataWithId =
612                 new Metadata.Builder()
613                         .setId(id)
614                         .setClientRecordId(clientRecordId)
615                         .setClientRecordVersion(metadata.getClientRecordVersion())
616                         .setDataOrigin(metadata.getDataOrigin())
617                         .setDevice(metadata.getDevice())
618                         .setLastModifiedTime(metadata.getLastModifiedTime())
619                         .build();
620 
621         StepsCadenceRecord.StepsCadenceRecordSample stepsCadenceRecordSample =
622                 new StepsCadenceRecord.StepsCadenceRecordSample(8.0, Instant.now().plusMillis(100));
623 
624         return new StepsCadenceRecord.Builder(
625                         metadataWithId,
626                         Instant.now(),
627                         Instant.now().plusMillis(2000),
628                         List.of(stepsCadenceRecordSample, stepsCadenceRecordSample))
629                 .setStartZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now()))
630                 .setEndZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now()))
631                 .build();
632     }
633 
getStepsCadenceRecordWithClientVersion( int rate, int version, String clientRecordId)634     static StepsCadenceRecord getStepsCadenceRecordWithClientVersion(
635             int rate, int version, String clientRecordId) {
636         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
637         testMetadataBuilder.setClientRecordId(clientRecordId);
638         testMetadataBuilder.setClientRecordVersion(version);
639         Metadata testMetaData = testMetadataBuilder.build();
640         StepsCadenceRecord.StepsCadenceRecordSample stepsCadenceRecord =
641                 new StepsCadenceRecord.StepsCadenceRecordSample(
642                         rate, Instant.now().plusMillis(100));
643         ArrayList<StepsCadenceRecord.StepsCadenceRecordSample> stepsCadenceRecords =
644                 new ArrayList<>();
645         stepsCadenceRecords.add(stepsCadenceRecord);
646         stepsCadenceRecords.add(stepsCadenceRecord);
647 
648         return new StepsCadenceRecord.Builder(
649                         testMetaData,
650                         Instant.now(),
651                         Instant.now().plusMillis(1000),
652                         stepsCadenceRecords)
653                 .build();
654     }
655 
getBaseStepsCadenceRecord()656     private static StepsCadenceRecord getBaseStepsCadenceRecord() {
657         StepsCadenceRecord.StepsCadenceRecordSample stepsCadenceRecord =
658                 new StepsCadenceRecord.StepsCadenceRecordSample(1, Instant.now().plusMillis(100));
659         ArrayList<StepsCadenceRecord.StepsCadenceRecordSample> stepsCadenceRecords =
660                 new ArrayList<>();
661         stepsCadenceRecords.add(stepsCadenceRecord);
662         stepsCadenceRecords.add(stepsCadenceRecord);
663 
664         return new StepsCadenceRecord.Builder(
665                         new Metadata.Builder().build(),
666                         Instant.now(),
667                         Instant.now().plusMillis(1000),
668                         stepsCadenceRecords)
669                 .build();
670     }
671 
getCompleteStepsCadenceRecord()672     private static StepsCadenceRecord getCompleteStepsCadenceRecord() {
673         return buildRecordForStepsCadence(1, 100);
674     }
675 
buildRecordForStepsCadence( double rate, long millisFromStart)676     private static StepsCadenceRecord buildRecordForStepsCadence(
677             double rate, long millisFromStart) {
678         Device device =
679                 new Device.Builder()
680                         .setManufacturer("google")
681                         .setModel("Pixel4a")
682                         .setType(2)
683                         .build();
684         DataOrigin dataOrigin =
685                 new DataOrigin.Builder().setPackageName("android.healthconnect.cts").build();
686         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
687         testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin);
688         testMetadataBuilder.setClientRecordId("SCR" + Math.random());
689         testMetadataBuilder.setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED);
690         StepsCadenceRecord.StepsCadenceRecordSample stepsCadenceRecord =
691                 new StepsCadenceRecord.StepsCadenceRecordSample(
692                         rate, Instant.now().plusMillis(millisFromStart));
693         ArrayList<StepsCadenceRecord.StepsCadenceRecordSample> stepsCadenceRecords =
694                 new ArrayList<>();
695         stepsCadenceRecords.add(stepsCadenceRecord);
696         stepsCadenceRecords.add(stepsCadenceRecord);
697 
698         return new StepsCadenceRecord.Builder(
699                         testMetadataBuilder.build(),
700                         Instant.now(),
701                         Instant.now().plusMillis(1000),
702                         stepsCadenceRecords)
703                 .build();
704     }
705 }
706