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.SpeedRecord.SPEED_AVG; 20 import static android.health.connect.datatypes.SpeedRecord.SPEED_MAX; 21 import static android.health.connect.datatypes.SpeedRecord.SPEED_MIN; 22 import static android.healthconnect.cts.utils.TestUtils.copyRecordIdsViaReflection; 23 import static android.healthconnect.cts.utils.TestUtils.distinctByUuid; 24 import static android.healthconnect.cts.utils.TestUtils.getRecordIds; 25 import static android.healthconnect.cts.utils.TestUtils.insertRecords; 26 import static android.healthconnect.cts.utils.TestUtils.readRecords; 27 import static android.healthconnect.cts.utils.TestUtils.updateRecords; 28 29 import static com.google.common.truth.Truth.assertThat; 30 31 import android.content.Context; 32 import android.health.connect.AggregateRecordsRequest; 33 import android.health.connect.AggregateRecordsResponse; 34 import android.health.connect.DeleteUsingFiltersRequest; 35 import android.health.connect.HealthConnectException; 36 import android.health.connect.HealthDataCategory; 37 import android.health.connect.ReadRecordsRequestUsingFilters; 38 import android.health.connect.ReadRecordsRequestUsingIds; 39 import android.health.connect.RecordIdFilter; 40 import android.health.connect.TimeInstantRangeFilter; 41 import android.health.connect.changelog.ChangeLogTokenRequest; 42 import android.health.connect.changelog.ChangeLogTokenResponse; 43 import android.health.connect.changelog.ChangeLogsRequest; 44 import android.health.connect.changelog.ChangeLogsResponse; 45 import android.health.connect.datatypes.AggregationType; 46 import android.health.connect.datatypes.DataOrigin; 47 import android.health.connect.datatypes.Device; 48 import android.health.connect.datatypes.Metadata; 49 import android.health.connect.datatypes.Record; 50 import android.health.connect.datatypes.SpeedRecord; 51 import android.health.connect.datatypes.units.Velocity; 52 import android.healthconnect.cts.utils.AssumptionCheckerRule; 53 import android.healthconnect.cts.utils.TestUtils; 54 import android.platform.test.annotations.AppModeFull; 55 56 import androidx.test.core.app.ApplicationProvider; 57 import androidx.test.runner.AndroidJUnit4; 58 59 import org.junit.After; 60 import org.junit.Assert; 61 import org.junit.Before; 62 import org.junit.Rule; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 66 import java.time.Instant; 67 import java.time.ZoneOffset; 68 import java.time.temporal.ChronoUnit; 69 import java.util.ArrayList; 70 import java.util.Arrays; 71 import java.util.Collections; 72 import java.util.List; 73 import java.util.Set; 74 import java.util.UUID; 75 76 @AppModeFull(reason = "HealthConnectManager is not accessible to instant apps") 77 @RunWith(AndroidJUnit4.class) 78 public class SpeedRecordTest { 79 80 private static final String TAG = "SpeedRecordTest"; 81 private static final String PACKAGE_NAME = "android.healthconnect.cts"; 82 83 @Rule 84 public AssumptionCheckerRule mSupportedHardwareRule = 85 new AssumptionCheckerRule( 86 TestUtils::isHardwareSupported, "Tests should run on supported hardware only."); 87 88 @Before setUp()89 public void setUp() throws InterruptedException { 90 TestUtils.deleteAllStagedRemoteData(); 91 } 92 93 @After tearDown()94 public void tearDown() throws InterruptedException { 95 TestUtils.verifyDeleteRecords( 96 SpeedRecord.class, 97 new TimeInstantRangeFilter.Builder() 98 .setStartTime(Instant.EPOCH) 99 .setEndTime(Instant.now()) 100 .build()); 101 TestUtils.deleteAllStagedRemoteData(); 102 } 103 104 @Test testInsertSpeedRecord()105 public void testInsertSpeedRecord() throws InterruptedException { 106 TestUtils.insertRecords(Arrays.asList(getBaseSpeedRecord(), getCompleteSpeedRecord())); 107 } 108 109 @Test testReadSpeedRecord_usingIds()110 public void testReadSpeedRecord_usingIds() throws InterruptedException { 111 List<Record> recordList = 112 insertRecords(Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord())); 113 114 readSpeedRecordUsingIds(recordList); 115 } 116 117 @Test testReadSpeedRecord_invalidIds()118 public void testReadSpeedRecord_invalidIds() throws InterruptedException { 119 ReadRecordsRequestUsingIds<SpeedRecord> request = 120 new ReadRecordsRequestUsingIds.Builder<>(SpeedRecord.class) 121 .addId(UUID.randomUUID().toString()) 122 .build(); 123 List<SpeedRecord> result = TestUtils.readRecords(request); 124 assertThat(result.size()).isEqualTo(0); 125 } 126 127 @Test testReadSpeedRecord_usingClientRecordIds()128 public void testReadSpeedRecord_usingClientRecordIds() throws InterruptedException { 129 List<Record> recordList = Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord()); 130 List<Record> insertedRecords = TestUtils.insertRecords(recordList); 131 readSpeedRecordUsingClientId(insertedRecords); 132 } 133 134 @Test testReadSpeedRecord_invalidClientRecordIds()135 public void testReadSpeedRecord_invalidClientRecordIds() throws InterruptedException { 136 ReadRecordsRequestUsingIds<SpeedRecord> request = 137 new ReadRecordsRequestUsingIds.Builder<>(SpeedRecord.class) 138 .addClientRecordId("abc") 139 .build(); 140 List<SpeedRecord> result = TestUtils.readRecords(request); 141 assertThat(result.size()).isEqualTo(0); 142 } 143 144 @Test testReadSpeedRecordUsingFilters_default()145 public void testReadSpeedRecordUsingFilters_default() throws InterruptedException { 146 List<SpeedRecord> oldSpeedRecords = 147 TestUtils.readRecords( 148 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class).build()); 149 150 SpeedRecord testRecord = (SpeedRecord) TestUtils.insertRecord(getCompleteSpeedRecord()); 151 List<SpeedRecord> newSpeedRecords = 152 TestUtils.readRecords( 153 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class).build()); 154 assertThat(newSpeedRecords.size()).isEqualTo(oldSpeedRecords.size() + 1); 155 assertThat(newSpeedRecords.get(newSpeedRecords.size() - 1).equals(testRecord)).isTrue(); 156 } 157 158 @Test testReadSpeedRecordUsingFilters_timeFilter()159 public void testReadSpeedRecordUsingFilters_timeFilter() throws InterruptedException { 160 TimeInstantRangeFilter filter = 161 new TimeInstantRangeFilter.Builder() 162 .setStartTime(Instant.now()) 163 .setEndTime(Instant.now().plusMillis(3000)) 164 .build(); 165 166 SpeedRecord testRecord = (SpeedRecord) TestUtils.insertRecord(getCompleteSpeedRecord()); 167 List<SpeedRecord> newSpeedRecords = 168 TestUtils.readRecords( 169 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class) 170 .setTimeRangeFilter(filter) 171 .build()); 172 assertThat(newSpeedRecords.size()).isEqualTo(1); 173 assertThat(newSpeedRecords.get(newSpeedRecords.size() - 1).equals(testRecord)).isTrue(); 174 } 175 176 @Test testReadSpeedRecordUsingFilters_dataFilter_correct()177 public void testReadSpeedRecordUsingFilters_dataFilter_correct() throws InterruptedException { 178 Context context = ApplicationProvider.getApplicationContext(); 179 List<SpeedRecord> oldSpeedRecords = 180 TestUtils.readRecords( 181 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class) 182 .addDataOrigins( 183 new DataOrigin.Builder() 184 .setPackageName(context.getPackageName()) 185 .build()) 186 .build()); 187 188 SpeedRecord testRecord = (SpeedRecord) TestUtils.insertRecord(getCompleteSpeedRecord()); 189 List<SpeedRecord> newSpeedRecords = 190 TestUtils.readRecords( 191 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class) 192 .addDataOrigins( 193 new DataOrigin.Builder() 194 .setPackageName(context.getPackageName()) 195 .build()) 196 .build()); 197 assertThat(newSpeedRecords.size() - oldSpeedRecords.size()).isEqualTo(1); 198 assertThat(newSpeedRecords.get(newSpeedRecords.size() - 1).equals(testRecord)).isTrue(); 199 SpeedRecord newRecord = newSpeedRecords.get(newSpeedRecords.size() - 1); 200 assertThat(newRecord.equals(testRecord)).isTrue(); 201 for (int idx = 0; idx < newRecord.getSamples().size(); idx++) { 202 assertThat(newRecord.getSamples().get(idx).getTime().toEpochMilli()) 203 .isEqualTo(testRecord.getSamples().get(idx).getTime().toEpochMilli()); 204 assertThat(newRecord.getSamples().get(idx).getSpeed()) 205 .isEqualTo(testRecord.getSamples().get(idx).getSpeed()); 206 } 207 } 208 209 @Test testReadSpeedRecordUsingFilters_dataFilter_incorrect()210 public void testReadSpeedRecordUsingFilters_dataFilter_incorrect() throws InterruptedException { 211 TestUtils.insertRecords(Collections.singletonList(getCompleteSpeedRecord())); 212 List<SpeedRecord> newSpeedRecords = 213 TestUtils.readRecords( 214 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class) 215 .addDataOrigins( 216 new DataOrigin.Builder().setPackageName("abc").build()) 217 .build()); 218 assertThat(newSpeedRecords.size()).isEqualTo(0); 219 } 220 221 @Test testDeleteSpeedRecord_no_filters()222 public void testDeleteSpeedRecord_no_filters() throws InterruptedException { 223 String id = TestUtils.insertRecordAndGetId(getCompleteSpeedRecord()); 224 TestUtils.verifyDeleteRecords(new DeleteUsingFiltersRequest.Builder().build()); 225 TestUtils.assertRecordNotFound(id, SpeedRecord.class); 226 } 227 228 @Test testDeleteSpeedRecord_time_filters()229 public void testDeleteSpeedRecord_time_filters() throws InterruptedException { 230 TimeInstantRangeFilter timeRangeFilter = 231 new TimeInstantRangeFilter.Builder() 232 .setStartTime(Instant.now()) 233 .setEndTime(Instant.now().plusMillis(1000)) 234 .build(); 235 String id = TestUtils.insertRecordAndGetId(getCompleteSpeedRecord()); 236 TestUtils.verifyDeleteRecords( 237 new DeleteUsingFiltersRequest.Builder() 238 .addRecordType(SpeedRecord.class) 239 .setTimeRangeFilter(timeRangeFilter) 240 .build()); 241 TestUtils.assertRecordNotFound(id, SpeedRecord.class); 242 } 243 244 @Test testDeleteSpeedRecord_recordId_filters()245 public void testDeleteSpeedRecord_recordId_filters() throws InterruptedException { 246 List<Record> records = 247 TestUtils.insertRecords(List.of(getBaseSpeedRecord(), getCompleteSpeedRecord())); 248 249 for (Record record : records) { 250 TestUtils.verifyDeleteRecords( 251 new DeleteUsingFiltersRequest.Builder() 252 .addRecordType(record.getClass()) 253 .build()); 254 TestUtils.assertRecordNotFound(record.getMetadata().getId(), record.getClass()); 255 } 256 } 257 258 @Test testDeleteSpeedRecord_dataOrigin_filters()259 public void testDeleteSpeedRecord_dataOrigin_filters() throws InterruptedException { 260 Context context = ApplicationProvider.getApplicationContext(); 261 String id = TestUtils.insertRecordAndGetId(getCompleteSpeedRecord()); 262 TestUtils.verifyDeleteRecords( 263 new DeleteUsingFiltersRequest.Builder() 264 .addDataOrigin( 265 new DataOrigin.Builder() 266 .setPackageName(context.getPackageName()) 267 .build()) 268 .build()); 269 TestUtils.assertRecordNotFound(id, SpeedRecord.class); 270 } 271 272 @Test testDeleteSpeedRecord_dataOrigin_filter_incorrect()273 public void testDeleteSpeedRecord_dataOrigin_filter_incorrect() throws InterruptedException { 274 String id = TestUtils.insertRecordAndGetId(getCompleteSpeedRecord()); 275 TestUtils.verifyDeleteRecords( 276 new DeleteUsingFiltersRequest.Builder() 277 .addDataOrigin(new DataOrigin.Builder().setPackageName("abc").build()) 278 .build()); 279 TestUtils.assertRecordFound(id, SpeedRecord.class); 280 } 281 282 @Test testDeleteSpeedRecord_usingIds()283 public void testDeleteSpeedRecord_usingIds() throws InterruptedException { 284 List<Record> records = 285 TestUtils.insertRecords(List.of(getBaseSpeedRecord(), getCompleteSpeedRecord())); 286 List<RecordIdFilter> recordIds = new ArrayList<>(records.size()); 287 for (Record record : records) { 288 recordIds.add(RecordIdFilter.fromId(record.getClass(), record.getMetadata().getId())); 289 } 290 291 TestUtils.verifyDeleteRecords(recordIds); 292 for (Record record : records) { 293 TestUtils.assertRecordNotFound(record.getMetadata().getId(), record.getClass()); 294 } 295 } 296 297 @Test testDeleteSpeedRecord_time_range()298 public void testDeleteSpeedRecord_time_range() throws InterruptedException { 299 TimeInstantRangeFilter timeRangeFilter = 300 new TimeInstantRangeFilter.Builder() 301 .setStartTime(Instant.now()) 302 .setEndTime(Instant.now().plusMillis(1000)) 303 .build(); 304 String id = TestUtils.insertRecordAndGetId(getCompleteSpeedRecord()); 305 TestUtils.verifyDeleteRecords(SpeedRecord.class, timeRangeFilter); 306 TestUtils.assertRecordNotFound(id, SpeedRecord.class); 307 } 308 309 @Test testZoneOffsets()310 public void testZoneOffsets() { 311 final ZoneOffset defaultZoneOffset = 312 ZoneOffset.systemDefault().getRules().getOffset(Instant.now()); 313 final ZoneOffset startZoneOffset = ZoneOffset.UTC; 314 final ZoneOffset endZoneOffset = ZoneOffset.MAX; 315 SpeedRecord.Builder builder = 316 new SpeedRecord.Builder( 317 new Metadata.Builder().build(), 318 Instant.now(), 319 Instant.now().plusMillis(1000), 320 Collections.emptyList()); 321 322 assertThat(builder.setStartZoneOffset(startZoneOffset).build().getStartZoneOffset()) 323 .isEqualTo(startZoneOffset); 324 assertThat(builder.setEndZoneOffset(endZoneOffset).build().getEndZoneOffset()) 325 .isEqualTo(endZoneOffset); 326 assertThat(builder.clearStartZoneOffset().build().getStartZoneOffset()) 327 .isEqualTo(defaultZoneOffset); 328 assertThat(builder.clearEndZoneOffset().build().getEndZoneOffset()) 329 .isEqualTo(defaultZoneOffset); 330 } 331 332 @Test testInsertAndDeleteRecord_changelogs()333 public void testInsertAndDeleteRecord_changelogs() throws InterruptedException { 334 Context context = ApplicationProvider.getApplicationContext(); 335 ChangeLogTokenResponse tokenResponse = 336 TestUtils.getChangeLogToken( 337 new ChangeLogTokenRequest.Builder() 338 .addDataOriginFilter( 339 new DataOrigin.Builder() 340 .setPackageName(context.getPackageName()) 341 .build()) 342 .addRecordType(SpeedRecord.class) 343 .build()); 344 ChangeLogsRequest changeLogsRequest = 345 new ChangeLogsRequest.Builder(tokenResponse.getToken()).build(); 346 ChangeLogsResponse response = TestUtils.getChangeLogs(changeLogsRequest); 347 assertThat(response.getUpsertedRecords().size()).isEqualTo(0); 348 assertThat(response.getDeletedLogs().size()).isEqualTo(0); 349 350 List<Record> testRecord = 351 TestUtils.insertRecords(Collections.singletonList(getCompleteSpeedRecord())); 352 response = TestUtils.getChangeLogs(changeLogsRequest); 353 assertThat(response.getUpsertedRecords().size()).isEqualTo(1); 354 assertThat( 355 response.getUpsertedRecords().stream() 356 .map(Record::getMetadata) 357 .map(Metadata::getId) 358 .toList()) 359 .containsExactlyElementsIn( 360 testRecord.stream().map(Record::getMetadata).map(Metadata::getId).toList()); 361 assertThat(response.getDeletedLogs().size()).isEqualTo(0); 362 363 TestUtils.verifyDeleteRecords( 364 new DeleteUsingFiltersRequest.Builder().addRecordType(SpeedRecord.class).build()); 365 response = TestUtils.getChangeLogs(changeLogsRequest); 366 assertThat(response.getDeletedLogs()).hasSize(testRecord.size()); 367 assertThat( 368 response.getDeletedLogs().stream() 369 .map(ChangeLogsResponse.DeletedLog::getDeletedRecordId) 370 .toList()) 371 .containsExactlyElementsIn( 372 testRecord.stream().map(Record::getMetadata).map(Metadata::getId).toList()); 373 } 374 375 @Test testSpeedAggregation_getAggregationFromThreerecords_aggResponsesAreCorrect()376 public void testSpeedAggregation_getAggregationFromThreerecords_aggResponsesAreCorrect() 377 throws Exception { 378 TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY); 379 List<Record> records = 380 Arrays.asList( 381 buildRecordForSpeed(120, 100), 382 buildRecordForSpeed(80, 101), 383 buildRecordForSpeed(100, 102)); 384 AggregateRecordsResponse<Velocity> response = 385 TestUtils.getAggregateResponse( 386 new AggregateRecordsRequest.Builder<Velocity>( 387 new TimeInstantRangeFilter.Builder() 388 .setStartTime(Instant.ofEpochMilli(0)) 389 .setEndTime(Instant.now().plus(1, ChronoUnit.DAYS)) 390 .build()) 391 .addAggregationType(SPEED_MAX) 392 .addAggregationType(SPEED_AVG) 393 .addAggregationType(SPEED_MIN) 394 .build(), 395 records); 396 checkAggregationResult(SPEED_MIN, Velocity.fromMetersPerSecond(80), response); 397 checkAggregationResult(SPEED_AVG, Velocity.fromMetersPerSecond(100), response); 398 checkAggregationResult(SPEED_MAX, Velocity.fromMetersPerSecond(120), response); 399 } 400 checkAggregationResult( AggregationType<Velocity> type, Velocity expectedResult, AggregateRecordsResponse<Velocity> response)401 private void checkAggregationResult( 402 AggregationType<Velocity> type, 403 Velocity expectedResult, 404 AggregateRecordsResponse<Velocity> response) { 405 assertThat(response.get(type)).isNotNull(); 406 assertThat(response.get(type)).isEqualTo(expectedResult); 407 assertThat(response.getZoneOffset(type)) 408 .isEqualTo(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())); 409 Set<DataOrigin> dataOrigins = response.getDataOrigins(type); 410 assertThat(dataOrigins).hasSize(1); 411 for (DataOrigin itr : dataOrigins) { 412 assertThat(itr.getPackageName()).isEqualTo("android.healthconnect.cts"); 413 } 414 } 415 readSpeedRecordUsingClientId(List<? extends Record> insertedRecord)416 private void readSpeedRecordUsingClientId(List<? extends Record> insertedRecord) 417 throws InterruptedException { 418 ReadRecordsRequestUsingIds.Builder<SpeedRecord> request = 419 new ReadRecordsRequestUsingIds.Builder<>(SpeedRecord.class); 420 for (Record record : insertedRecord) { 421 request.addClientRecordId(record.getMetadata().getClientRecordId()); 422 } 423 List<SpeedRecord> result = TestUtils.readRecords(request.build()); 424 assertThat(result.size()).isEqualTo(insertedRecord.size()); 425 assertThat(result).containsExactlyElementsIn(insertedRecord); 426 } 427 readSpeedRecordUsingIds(List<? extends Record> recordList)428 private void readSpeedRecordUsingIds(List<? extends Record> recordList) 429 throws InterruptedException { 430 ReadRecordsRequestUsingIds.Builder<SpeedRecord> request = 431 new ReadRecordsRequestUsingIds.Builder<>(SpeedRecord.class); 432 for (Record record : recordList) { 433 request.addId(record.getMetadata().getId()); 434 } 435 ReadRecordsRequestUsingIds requestUsingIds = request.build(); 436 assertThat(requestUsingIds.getRecordType()).isEqualTo(SpeedRecord.class); 437 assertThat(requestUsingIds.getRecordIdFilters()).isNotNull(); 438 List<SpeedRecord> result = TestUtils.readRecords(requestUsingIds); 439 assertThat(result).hasSize(recordList.size()); 440 assertThat(result.containsAll(recordList)).isTrue(); 441 } 442 443 @Test(expected = IllegalArgumentException.class) testCreateSpeedRecord_invalidValue()444 public void testCreateSpeedRecord_invalidValue() { 445 new SpeedRecord.SpeedRecordSample( 446 Velocity.fromMetersPerSecond(1000001), Instant.now().plusMillis(100)); 447 } 448 449 @Test(expected = IllegalArgumentException.class) testCreateSpeedRecord_invalidSampleTime()450 public void testCreateSpeedRecord_invalidSampleTime() { 451 Instant startTime = Instant.now(); 452 Instant endTime = startTime.plusMillis(100); 453 SpeedRecord.SpeedRecordSample speedRecord = 454 new SpeedRecord.SpeedRecordSample( 455 Velocity.fromMetersPerSecond(10.0), endTime.plusMillis(1)); 456 ArrayList<SpeedRecord.SpeedRecordSample> speedRecords = new ArrayList<>(); 457 speedRecords.add(speedRecord); 458 new SpeedRecord.Builder(new Metadata.Builder().build(), startTime, endTime, speedRecords) 459 .build(); 460 } 461 462 @Test testUpdateRecords_validInput_dataBaseUpdatedSuccessfully()463 public void testUpdateRecords_validInput_dataBaseUpdatedSuccessfully() 464 throws InterruptedException { 465 466 List<Record> insertedRecords = 467 TestUtils.insertRecords( 468 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord())); 469 470 // read inserted records and verify that the data is same as inserted. 471 readSpeedRecordUsingIds(insertedRecords); 472 473 // Generate a new set of records that will be used to perform the update operation. 474 List<Record> updateRecords = 475 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord()); 476 477 // Modify the uid of the updateRecords to the uuid that was present in the insert records. 478 for (int itr = 0; itr < updateRecords.size(); itr++) { 479 updateRecords.set( 480 itr, 481 getSpeedRecord_update( 482 updateRecords.get(itr), 483 insertedRecords.get(itr).getMetadata().getId(), 484 insertedRecords.get(itr).getMetadata().getClientRecordId())); 485 } 486 487 TestUtils.updateRecords(updateRecords); 488 489 // assert the inserted data has been modified by reading the data. 490 readSpeedRecordUsingIds(updateRecords); 491 } 492 493 @Test testUpdateRecords_invalidInputRecords_noChangeInDataBase()494 public void testUpdateRecords_invalidInputRecords_noChangeInDataBase() 495 throws InterruptedException { 496 List<Record> insertedRecords = 497 TestUtils.insertRecords( 498 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord())); 499 500 // read inserted records and verify that the data is same as inserted. 501 readSpeedRecordUsingIds(insertedRecords); 502 503 // Generate a second set of records that will be used to perform the update operation. 504 List<Record> updateRecords = 505 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord()); 506 507 // Modify the Uid of the updateRecords to the UUID that was present in the insert records, 508 // leaving out alternate records so that they have a new UUID which is not present in the 509 // dataBase. 510 for (int itr = 0; itr < updateRecords.size(); itr++) { 511 updateRecords.set( 512 itr, 513 getSpeedRecord_update( 514 updateRecords.get(itr), 515 itr % 2 == 0 516 ? insertedRecords.get(itr).getMetadata().getId() 517 : UUID.randomUUID().toString(), 518 itr % 2 == 0 519 ? insertedRecords.get(itr).getMetadata().getId() 520 : UUID.randomUUID().toString())); 521 } 522 523 try { 524 TestUtils.updateRecords(updateRecords); 525 Assert.fail("Expected to fail due to invalid records ids."); 526 } catch (HealthConnectException exception) { 527 assertThat(exception.getErrorCode()) 528 .isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT); 529 } 530 531 // assert the inserted data has not been modified by reading the data. 532 readSpeedRecordUsingIds(insertedRecords); 533 } 534 535 @Test testUpdateRecords_recordWithInvalidPackageName_noChangeInDataBase()536 public void testUpdateRecords_recordWithInvalidPackageName_noChangeInDataBase() 537 throws InterruptedException { 538 List<Record> insertedRecords = 539 TestUtils.insertRecords( 540 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord())); 541 542 // read inserted records and verify that the data is same as inserted. 543 readSpeedRecordUsingIds(insertedRecords); 544 545 // Generate a second set of records that will be used to perform the update operation. 546 List<Record> updateRecords = 547 Arrays.asList(getCompleteSpeedRecord(), getCompleteSpeedRecord()); 548 549 // Modify the Uuid of the updateRecords to the uuid that was present in the insert records. 550 for (int itr = 0; itr < updateRecords.size(); itr++) { 551 updateRecords.set( 552 itr, 553 getSpeedRecord_update( 554 updateRecords.get(itr), 555 insertedRecords.get(itr).getMetadata().getId(), 556 insertedRecords.get(itr).getMetadata().getClientRecordId())); 557 // adding an entry with invalid packageName. 558 updateRecords.set(itr, getCompleteSpeedRecord()); 559 } 560 561 try { 562 TestUtils.updateRecords(updateRecords); 563 Assert.fail("Expected to fail due to invalid package."); 564 } catch (Exception exception) { 565 // verify that the testcase failed due to invalid argument exception. 566 assertThat(exception).isNotNull(); 567 } 568 569 // assert the inserted data has not been modified by reading the data. 570 readSpeedRecordUsingIds(insertedRecords); 571 } 572 573 @Test insertRecords_withDuplicatedClientRecordId_readNoDuplicates()574 public void insertRecords_withDuplicatedClientRecordId_readNoDuplicates() throws Exception { 575 int distinctRecordCount = 10; 576 List<SpeedRecord> records = new ArrayList<>(); 577 Instant now = Instant.now(); 578 for (int i = 0; i < distinctRecordCount; i++) { 579 SpeedRecord record = 580 buildRecordForSpeed( 581 /* speed= */ 10, 582 /* millisFromStart= */ 0, 583 /* startTime= */ now.minusMillis(i + 1), 584 /* endTime= */ now.minusMillis(i), 585 /* clientRecordId= */ "client_id_" + i); 586 587 records.add(record); 588 records.add(record); // Add each record twice 589 } 590 591 List<Record> insertedRecords = TestUtils.insertRecords(records); 592 assertThat(insertedRecords.size()).isEqualTo(records.size()); 593 594 List<Record> distinctRecords = distinctByUuid(insertedRecords); 595 assertThat(distinctRecords.size()).isEqualTo(distinctRecordCount); 596 597 readSpeedRecordUsingIds(distinctRecords); 598 } 599 600 @Test insertRecords_sameClientRecordIdAndNewData_readNewData()601 public void insertRecords_sameClientRecordIdAndNewData_readNewData() throws Exception { 602 int recordCount = 10; 603 insertAndReadRecords(recordCount, /* speed= */ 10); 604 605 double newSpeed = 20; 606 List<SpeedRecord> newRecords = insertAndReadRecords(recordCount, newSpeed); 607 608 for (SpeedRecord record : newRecords) { 609 assertRecord(record, newSpeed); 610 } 611 } 612 613 @Test insertRecords_sameClientRecordIdAndNewerVersion_readNewData()614 public void insertRecords_sameClientRecordIdAndNewerVersion_readNewData() throws Exception { 615 int recordCount = 10; 616 long oldVersion = 0L; 617 double oldSpeed = 10; 618 insertAndReadRecords(recordCount, oldVersion, oldSpeed); 619 620 long newVersion = 1L; 621 double newSpeed = 20; 622 List<SpeedRecord> newRecords = insertAndReadRecords(recordCount, newVersion, newSpeed); 623 624 for (SpeedRecord record : newRecords) { 625 assertRecord(record, newSpeed); 626 } 627 } 628 629 @Test insertRecords_sameClientRecordIdAndSameVersion_readNewData()630 public void insertRecords_sameClientRecordIdAndSameVersion_readNewData() throws Exception { 631 int recordCount = 10; 632 long version = 1L; 633 double oldSpeed = 10; 634 insertAndReadRecords(recordCount, version, oldSpeed); 635 636 double newSpeed = 20; 637 List<SpeedRecord> newRecords = insertAndReadRecords(recordCount, version, newSpeed); 638 639 for (SpeedRecord record : newRecords) { 640 assertRecord(record, newSpeed); 641 } 642 } 643 644 @Test insertRecords_sameClientRecordIdAndOlderVersion_readOldData()645 public void insertRecords_sameClientRecordIdAndOlderVersion_readOldData() throws Exception { 646 int recordCount = 10; 647 long oldVersion = 1L; 648 double oldSpeed = 10; 649 insertAndReadRecords(recordCount, oldVersion, oldSpeed); 650 651 long newVersion = 0L; 652 double newSpeed = 20; 653 List<SpeedRecord> newRecords = insertAndReadRecords(recordCount, newVersion, newSpeed); 654 655 for (SpeedRecord record : newRecords) { 656 assertRecord(record, oldSpeed); 657 } 658 } 659 660 @Test updateRecords_byId_readNewData()661 public void updateRecords_byId_readNewData() throws Exception { 662 Instant now = Instant.now(); 663 List<Record> insertedRecords = 664 insertRecords( 665 buildRecordForSpeed(1, now.minusMillis(2), now.minusMillis(1)), 666 buildRecordForSpeed(2, now.minusMillis(3), now.minusMillis(2)), 667 buildRecordForSpeed(3, now.minusMillis(4), now.minusMillis(3))); 668 List<String> insertedIds = getRecordIds(insertedRecords); 669 670 List<Record> updatedRecords = 671 List.of( 672 buildRecordForSpeed( 673 insertedIds.get(0), 10, now.minusMillis(2), now.minusMillis(1)), 674 buildRecordForSpeed( 675 insertedIds.get(1), 2, now.minusMillis(30), now.minusMillis(20)), 676 buildRecordForSpeed( 677 insertedIds.get(2), 30, now.minusMillis(4), now.minusMillis(3))); 678 updateRecords(updatedRecords); 679 680 readSpeedRecordUsingIds(updatedRecords); 681 } 682 683 @Test updateRecords_byClientRecordId_readNewData()684 public void updateRecords_byClientRecordId_readNewData() throws Exception { 685 Instant now = Instant.now(); 686 List<Record> insertedRecords = 687 insertRecords( 688 buildRecordForSpeed(1, now.minusMillis(2), now.minusMillis(1), "id1"), 689 buildRecordForSpeed(2, now.minusMillis(3), now.minusMillis(2), "id2"), 690 buildRecordForSpeed(3, now.minusMillis(4), now.minusMillis(3), "id3")); 691 692 List<SpeedRecord> updatedRecords = 693 List.of( 694 buildRecordForSpeed(10, now.minusMillis(2), now.minusMillis(1), "id1"), 695 buildRecordForSpeed(2, now.minusMillis(30), now.minusMillis(20), "id2"), 696 buildRecordForSpeed(30, now.minusMillis(4), now.minusMillis(3), "id3")); 697 updateRecords(updatedRecords); 698 copyRecordIdsViaReflection(insertedRecords, updatedRecords); 699 700 readSpeedRecordUsingIds(updatedRecords); 701 } 702 assertRecord(SpeedRecord record, double speed)703 private static void assertRecord(SpeedRecord record, double speed) { 704 assertThat( 705 record.getSamples().stream() 706 .map(sample -> sample.getSpeed().getInMetersPerSecond()) 707 .distinct() 708 .toList()) 709 .containsExactly(speed); 710 } 711 insertAndReadRecords(int recordCount, double speed)712 private static List<SpeedRecord> insertAndReadRecords(int recordCount, double speed) 713 throws Exception { 714 return insertAndReadRecords(recordCount, /* version= */ 0L, speed); 715 } 716 insertAndReadRecords( int recordCount, long version, double speed)717 private static List<SpeedRecord> insertAndReadRecords( 718 int recordCount, long version, double speed) throws Exception { 719 List<SpeedRecord> records = new ArrayList<>(); 720 Instant now = Instant.now(); 721 for (int i = 0; i < recordCount; i++) { 722 long millisFromStart = 0; 723 Instant startTime = now.minusSeconds(i + 1); 724 Instant endTime = now.minusSeconds(i); 725 String clientRecordId = "client_id_" + i; 726 records.add( 727 buildRecordForSpeed( 728 speed, millisFromStart, startTime, endTime, clientRecordId, version)); 729 } 730 List<Record> insertedRecords = insertRecords(records); 731 assertThat(insertedRecords).hasSize(recordCount); 732 733 List<SpeedRecord> readRecords = 734 readRecords( 735 new ReadRecordsRequestUsingFilters.Builder<>(SpeedRecord.class).build()); 736 assertThat(readRecords).hasSize(recordCount); 737 738 return readRecords; 739 } 740 getSpeedRecord_update(Record record, String id, String clientRecordId)741 SpeedRecord getSpeedRecord_update(Record record, String id, String clientRecordId) { 742 Metadata metadata = record.getMetadata(); 743 Metadata metadataWithId = 744 new Metadata.Builder() 745 .setId(id) 746 .setClientRecordId(clientRecordId) 747 .setClientRecordVersion(metadata.getClientRecordVersion()) 748 .setDataOrigin(metadata.getDataOrigin()) 749 .setDevice(metadata.getDevice()) 750 .setLastModifiedTime(metadata.getLastModifiedTime()) 751 .build(); 752 753 SpeedRecord.SpeedRecordSample speedRecordSample = 754 new SpeedRecord.SpeedRecordSample( 755 Velocity.fromMetersPerSecond(8.0), Instant.now().plusMillis(100)); 756 757 return new SpeedRecord.Builder( 758 metadataWithId, 759 Instant.now(), 760 Instant.now().plusMillis(2000), 761 List.of(speedRecordSample, speedRecordSample)) 762 .setStartZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())) 763 .setEndZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())) 764 .build(); 765 } 766 getBaseSpeedRecord()767 private static SpeedRecord getBaseSpeedRecord() { 768 SpeedRecord.SpeedRecordSample speedRecord = 769 new SpeedRecord.SpeedRecordSample( 770 Velocity.fromMetersPerSecond(10.0), Instant.now().plusMillis(100)); 771 ArrayList<SpeedRecord.SpeedRecordSample> speedRecords = new ArrayList<>(); 772 speedRecords.add(speedRecord); 773 speedRecords.add(speedRecord); 774 775 return new SpeedRecord.Builder( 776 new Metadata.Builder().build(), 777 Instant.now(), 778 Instant.now().plusMillis(1000), 779 speedRecords) 780 .build(); 781 } 782 getCompleteSpeedRecord()783 private static SpeedRecord getCompleteSpeedRecord() { 784 return buildRecordForSpeed(10, 100); 785 } 786 buildRecordForSpeed(double speed, long millisFromStart)787 private static SpeedRecord buildRecordForSpeed(double speed, long millisFromStart) { 788 return buildRecordForSpeed( 789 speed, 790 millisFromStart, 791 /* startTime= */ Instant.now(), 792 /* endTime= */ Instant.now().plusMillis(1000), 793 /* clientRecordId= */ "SPR" + Math.random()); 794 } 795 buildRecordForSpeed( double speed, long millisFromStart, Instant startTime, Instant endTime, String clientRecordId)796 private static SpeedRecord buildRecordForSpeed( 797 double speed, 798 long millisFromStart, 799 Instant startTime, 800 Instant endTime, 801 String clientRecordId) { 802 return buildRecordForSpeed( 803 speed, 804 millisFromStart, 805 startTime, 806 endTime, 807 clientRecordId, 808 /* clientRecordVersion= */ 0L); 809 } 810 buildRecordForSpeed( double speed, Instant startTime, Instant endTime)811 private static SpeedRecord buildRecordForSpeed( 812 double speed, Instant startTime, Instant endTime) { 813 return buildRecordForSpeed(speed, startTime, endTime, /* clientRecordId= */ null); 814 } 815 buildRecordForSpeed( double speed, Instant startTime, Instant endTime, String clientRecordId)816 private static SpeedRecord buildRecordForSpeed( 817 double speed, Instant startTime, Instant endTime, String clientRecordId) { 818 return buildRecordForSpeed( 819 /* id= */ null, 820 speed, 821 /* millisFromStart= */ 0, 822 startTime, 823 endTime, 824 clientRecordId, 825 /* clientRecordVersion= */ 0L); 826 } 827 buildRecordForSpeed( String id, double speed, Instant startTime, Instant endTime)828 private static SpeedRecord buildRecordForSpeed( 829 String id, double speed, Instant startTime, Instant endTime) { 830 return buildRecordForSpeed( 831 id, 832 speed, 833 /* millisFromStart= */ 0, 834 startTime, 835 endTime, 836 /* clientRecordId= */ null, 837 /* clientRecordVersion= */ 0L); 838 } 839 buildRecordForSpeed( double speed, long millisFromStart, Instant startTime, Instant endTime, String clientRecordId, long clientRecordVersion)840 private static SpeedRecord buildRecordForSpeed( 841 double speed, 842 long millisFromStart, 843 Instant startTime, 844 Instant endTime, 845 String clientRecordId, 846 long clientRecordVersion) { 847 return buildRecordForSpeed( 848 null, 849 speed, 850 millisFromStart, 851 startTime, 852 endTime, 853 clientRecordId, 854 clientRecordVersion); 855 } 856 buildRecordForSpeed( String id, double speed, long millisFromStart, Instant startTime, Instant endTime, String clientRecordId, long clientRecordVersion)857 private static SpeedRecord buildRecordForSpeed( 858 String id, 859 double speed, 860 long millisFromStart, 861 Instant startTime, 862 Instant endTime, 863 String clientRecordId, 864 long clientRecordVersion) { 865 866 Device device = 867 new Device.Builder() 868 .setManufacturer("google") 869 .setModel("Pixel4a") 870 .setType(2) 871 .build(); 872 DataOrigin dataOrigin = 873 new DataOrigin.Builder().setPackageName("android.healthconnect.cts").build(); 874 Metadata.Builder testMetadataBuilder = new Metadata.Builder(); 875 if (id != null) { 876 testMetadataBuilder.setId(id); 877 } 878 testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin); 879 testMetadataBuilder.setClientRecordId(clientRecordId); 880 testMetadataBuilder.setClientRecordVersion(clientRecordVersion); 881 testMetadataBuilder.setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED); 882 883 SpeedRecord.SpeedRecordSample speedRecord = 884 new SpeedRecord.SpeedRecordSample( 885 Velocity.fromMetersPerSecond(speed), startTime.plusMillis(millisFromStart)); 886 887 ArrayList<SpeedRecord.SpeedRecordSample> speedRecords = new ArrayList<>(); 888 speedRecords.add(speedRecord); 889 speedRecords.add(speedRecord); 890 891 return new SpeedRecord.Builder( 892 testMetadataBuilder.build(), startTime, endTime, speedRecords) 893 .build(); 894 } 895 } 896