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