1 /**
<lambda>null2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * ```
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * ```
10 *
11 * Unless required by applicable law or agreed to in writing, software distributed under the License
12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing permissions and limitations under
14 * the License.
15 */
16 package com.android.healthconnect.controller.tests.utils
17
18 import android.health.connect.datatypes.BasalMetabolicRateRecord
19 import android.health.connect.datatypes.BodyTemperatureMeasurementLocation
20 import android.health.connect.datatypes.BodyTemperatureRecord
21 import android.health.connect.datatypes.BodyWaterMassRecord
22 import android.health.connect.datatypes.DataOrigin
23 import android.health.connect.datatypes.Device
24 import android.health.connect.datatypes.DistanceRecord
25 import android.health.connect.datatypes.ExerciseCompletionGoal
26 import android.health.connect.datatypes.ExercisePerformanceGoal
27 import android.health.connect.datatypes.ExerciseSegmentType
28 import android.health.connect.datatypes.ExerciseSessionType
29 import android.health.connect.datatypes.FhirResource
30 import android.health.connect.datatypes.FhirResource.FHIR_RESOURCE_TYPE_IMMUNIZATION
31 import android.health.connect.datatypes.FhirVersion
32 import android.health.connect.datatypes.HeartRateRecord
33 import android.health.connect.datatypes.HydrationRecord
34 import android.health.connect.datatypes.IntermenstrualBleedingRecord
35 import android.health.connect.datatypes.MedicalDataSource
36 import android.health.connect.datatypes.MedicalResource
37 import android.health.connect.datatypes.Metadata
38 import android.health.connect.datatypes.OxygenSaturationRecord
39 import android.health.connect.datatypes.PlannedExerciseBlock
40 import android.health.connect.datatypes.PlannedExerciseSessionRecord
41 import android.health.connect.datatypes.PlannedExerciseStep
42 import android.health.connect.datatypes.Record
43 import android.health.connect.datatypes.SleepSessionRecord
44 import android.health.connect.datatypes.StepsCadenceRecord
45 import android.health.connect.datatypes.StepsRecord
46 import android.health.connect.datatypes.TotalCaloriesBurnedRecord
47 import android.health.connect.datatypes.WeightRecord
48 import android.health.connect.datatypes.units.Energy
49 import android.health.connect.datatypes.units.Length
50 import android.health.connect.datatypes.units.Mass
51 import android.health.connect.datatypes.units.Percentage
52 import android.health.connect.datatypes.units.Power
53 import android.health.connect.datatypes.units.Temperature
54 import android.health.connect.datatypes.units.Velocity
55 import android.health.connect.datatypes.units.Volume
56 import android.net.Uri
57 import androidx.test.platform.app.InstrumentationRegistry
58 import androidx.test.uiautomator.UiDevice
59 import com.android.healthconnect.controller.dataentries.units.PowerConverter
60 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType
61 import com.android.healthconnect.controller.permissions.data.HealthPermission
62 import com.android.healthconnect.controller.shared.app.AppMetadata
63 import com.android.healthconnect.controller.utils.TimeSource
64 import com.android.healthconnect.controller.utils.randomInstant
65 import com.android.healthconnect.controller.utils.toInstant
66 import com.android.healthconnect.controller.utils.toLocalDateTime
67 import com.google.common.truth.Truth.assertThat
68 import java.time.Instant
69 import java.time.LocalDate
70 import java.time.ZoneOffset
71 import java.util.UUID
72 import kotlin.random.Random
73 import org.mockito.Mockito
74
75 val NOW: Instant = Instant.parse("2022-10-20T07:06:05.432Z")
76 val MIDNIGHT: Instant = Instant.parse("2022-10-20T00:00:00.000Z")
77
78 fun getHeartRateRecord(heartRateValues: List<Long>, startTime: Instant = NOW): HeartRateRecord {
79 return HeartRateRecord.Builder(
80 getMetaData(),
81 startTime,
82 startTime.plusSeconds(2),
83 heartRateValues.map { HeartRateRecord.HeartRateSample(it, NOW) },
84 )
85 .build()
86 }
87
getStepsRecordnull88 fun getStepsRecord(steps: Long, time: Instant = NOW): StepsRecord {
89 return StepsRecord.Builder(getMetaData(), time, time.plusSeconds(2), steps).build()
90 }
91
getStepsCadenceRecordnull92 fun getStepsCadenceRecord(time: Instant = NOW): StepsCadenceRecord {
93 return StepsCadenceRecord.Builder(
94 getMetaData(),
95 time,
96 time.plusSeconds(2),
97 listOf(
98 StepsCadenceRecord.StepsCadenceRecordSample(12.5, time),
99 StepsCadenceRecord.StepsCadenceRecordSample(12.3, time.plusSeconds(1)),
100 ),
101 )
102 .build()
103 }
104
getStepsRecordWithUniqueIdsnull105 fun getStepsRecordWithUniqueIds(steps: Long, time: Instant = NOW): StepsRecord {
106 return StepsRecord.Builder(getMetaDataWithUniqueIds(), time, time.plusSeconds(2), steps).build()
107 }
108
getBasalMetabolicRateRecordnull109 fun getBasalMetabolicRateRecord(calories: Long): BasalMetabolicRateRecord {
110 val watts = PowerConverter.convertWattsFromCalories(calories)
111 return BasalMetabolicRateRecord.Builder(getMetaData(), NOW, Power.fromWatts(watts)).build()
112 }
113
getDistanceRecordnull114 fun getDistanceRecord(distance: Length, time: Instant = NOW): DistanceRecord {
115 return DistanceRecord.Builder(getMetaData(), time, time.plusSeconds(2), distance).build()
116 }
117
getTotalCaloriesBurnedRecordnull118 fun getTotalCaloriesBurnedRecord(calories: Energy, time: Instant = NOW): TotalCaloriesBurnedRecord {
119 return TotalCaloriesBurnedRecord.Builder(getMetaData(), time, time.plusSeconds(2), calories)
120 .build()
121 }
122
getSleepSessionRecordnull123 fun getSleepSessionRecord(startTime: Instant = NOW): SleepSessionRecord {
124 val endTime = startTime.toLocalDateTime().plusHours(8).toInstant()
125 return SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build()
126 }
127
getSleepSessionRecordnull128 fun getSleepSessionRecord(startTime: Instant, endTime: Instant): SleepSessionRecord {
129 return SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build()
130 }
131
getWeightRecordnull132 fun getWeightRecord(time: Instant = NOW, weight: Mass): WeightRecord {
133 return WeightRecord.Builder(getMetaData(), time, weight).build()
134 }
135
getIntermenstrualBleedingRecordnull136 fun getIntermenstrualBleedingRecord(time: Instant): IntermenstrualBleedingRecord {
137 return IntermenstrualBleedingRecord.Builder(getMetaData(), time).build()
138 }
139
getBodyTemperatureRecordnull140 fun getBodyTemperatureRecord(
141 time: Instant,
142 location: Int,
143 temperature: Temperature,
144 ): BodyTemperatureRecord {
145 return BodyTemperatureRecord.Builder(getMetaData(), time, location, temperature).build()
146 }
147
getOxygenSaturationRecordnull148 fun getOxygenSaturationRecord(time: Instant, percentage: Percentage): OxygenSaturationRecord {
149 return OxygenSaturationRecord.Builder(getMetaData(), time, percentage).build()
150 }
151
getHydrationRecordnull152 fun getHydrationRecord(startTime: Instant, endTime: Instant, volume: Volume): HydrationRecord {
153 return HydrationRecord.Builder(getMetaData(), startTime, endTime, volume).build()
154 }
155
getBodyWaterMassRecordnull156 fun getBodyWaterMassRecord(time: Instant, bodyWaterMass: Mass): BodyWaterMassRecord {
157 return BodyWaterMassRecord.Builder(getMetaData(), time, bodyWaterMass).build()
158 }
159
getRandomRecordnull160 fun getRandomRecord(fitnessPermissionType: FitnessPermissionType, date: LocalDate): Record {
161 return when (fitnessPermissionType) {
162 FitnessPermissionType.STEPS ->
163 getStepsRecord(Random.nextLong(0, 5000), date.randomInstant())
164 FitnessPermissionType.DISTANCE ->
165 getDistanceRecord(
166 Length.fromMeters(Random.nextDouble(0.0, 5000.0)),
167 date.randomInstant(),
168 )
169 FitnessPermissionType.TOTAL_CALORIES_BURNED ->
170 getTotalCaloriesBurnedRecord(
171 Energy.fromCalories(Random.nextDouble(1500.0, 5000.0)),
172 date.randomInstant(),
173 )
174 FitnessPermissionType.SLEEP -> getSleepSessionRecord(date.randomInstant())
175 else ->
176 throw IllegalArgumentException(
177 "HealthPermissionType $fitnessPermissionType not supported"
178 )
179 }
180 }
181
getSamplePlannedExerciseSessionRecordnull182 fun getSamplePlannedExerciseSessionRecord(): PlannedExerciseSessionRecord {
183 val exerciseBlock1 =
184 getPlannedExerciseBlock(
185 repetitions = 1,
186 description = "Warm up",
187 exerciseSteps =
188 listOf(
189 getPlannedExerciseStep(
190 exerciseSegmentType = ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
191 completionGoal =
192 ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(1000.0)),
193 performanceGoals =
194 listOf(
195 ExercisePerformanceGoal.HeartRateGoal(100, 150),
196 ExercisePerformanceGoal.SpeedGoal(
197 Velocity.fromMetersPerSecond(25.0),
198 Velocity.fromMetersPerSecond(15.0),
199 ),
200 ),
201 )
202 ),
203 )
204 val exerciseBlock2 =
205 getPlannedExerciseBlock(
206 repetitions = 1,
207 description = "Main set",
208 exerciseSteps =
209 listOf(
210 getPlannedExerciseStep(
211 exerciseSegmentType = ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
212 completionGoal =
213 ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(4000.0)),
214 performanceGoals =
215 listOf(
216 ExercisePerformanceGoal.HeartRateGoal(150, 180),
217 ExercisePerformanceGoal.SpeedGoal(
218 Velocity.fromMetersPerSecond(50.0),
219 Velocity.fromMetersPerSecond(25.0),
220 ),
221 ),
222 )
223 ),
224 )
225 val exerciseBlocks = listOf(exerciseBlock1, exerciseBlock2)
226
227 return getPlannedExerciseSessionRecord(
228 title = "Morning Run",
229 note = "Morning quick run by the park",
230 exerciseBlocks = exerciseBlocks,
231 )
232 }
233
getPlannedExerciseSessionRecordnull234 fun getPlannedExerciseSessionRecord(
235 title: String?,
236 note: String?,
237 exerciseBlocks: List<PlannedExerciseBlock>,
238 ): PlannedExerciseSessionRecord {
239 return basePlannedExerciseSession(ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING)
240 .setTitle(title)
241 .setNotes(note)
242 .setBlocks(exerciseBlocks)
243 .build()
244 }
245
basePlannedExerciseSessionnull246 private fun basePlannedExerciseSession(exerciseType: Int): PlannedExerciseSessionRecord.Builder {
247 val builder: PlannedExerciseSessionRecord.Builder =
248 PlannedExerciseSessionRecord.Builder(
249 getMetaData(),
250 exerciseType,
251 NOW,
252 NOW.plusSeconds(3600),
253 )
254 builder.setNotes("Sample training plan notes")
255 builder.setTitle("Training plan title")
256 builder.setStartZoneOffset(ZoneOffset.UTC)
257 builder.setEndZoneOffset(ZoneOffset.UTC)
258 return builder
259 }
260
getPlannedExerciseBlocknull261 fun getPlannedExerciseBlock(
262 repetitions: Int,
263 description: String?,
264 exerciseSteps: List<PlannedExerciseStep>,
265 ): PlannedExerciseBlock {
266 return PlannedExerciseBlock.Builder(repetitions)
267 .setDescription(description)
268 .setSteps(exerciseSteps)
269 .build()
270 }
271
getPlannedExerciseStepnull272 fun getPlannedExerciseStep(
273 exerciseSegmentType: Int,
274 completionGoal: ExerciseCompletionGoal,
275 performanceGoals: List<ExercisePerformanceGoal>,
276 ): PlannedExerciseStep {
277 return PlannedExerciseStep.Builder(
278 exerciseSegmentType,
279 PlannedExerciseStep.EXERCISE_CATEGORY_ACTIVE,
280 completionGoal,
281 )
282 .setPerformanceGoals(performanceGoals)
283 .build()
284 }
285
getMetaDatanull286 fun getMetaData(): Metadata {
287 return getMetaData(TEST_APP_PACKAGE_NAME)
288 }
289
getMetaDataWithUniqueIdsnull290 fun getMetaDataWithUniqueIds(): Metadata {
291 return getMetaDataWithUniqueIds(TEST_APP_PACKAGE_NAME)
292 }
293
getMetaDatanull294 fun getMetaData(packageName: String): Metadata {
295 val device: Device =
296 Device.Builder().setManufacturer("google").setModel("Pixel4a").setType(2).build()
297 val dataOrigin = DataOrigin.Builder().setPackageName(packageName).build()
298 return Metadata.Builder()
299 .setId("test_id")
300 .setDevice(device)
301 .setDataOrigin(dataOrigin)
302 .setClientRecordId("BMR" + Math.random().toString())
303 .build()
304 }
305
getMetaDataWithUniqueIdsnull306 fun getMetaDataWithUniqueIds(packageName: String): Metadata {
307 val device: Device =
308 Device.Builder().setManufacturer("google").setModel("Pixel4a").setType(2).build()
309 val dataOrigin = DataOrigin.Builder().setPackageName(packageName).build()
310 return Metadata.Builder()
311 .setId(getUniqueId())
312 .setDevice(device)
313 .setDataOrigin(dataOrigin)
314 .setClientRecordId("BMR" + Math.random().toString())
315 .build()
316 }
317
getUniqueIdnull318 fun getUniqueId(): String {
319 return UUID.randomUUID().toString()
320 }
321
getDataOriginnull322 fun getDataOrigin(packageName: String): DataOrigin =
323 DataOrigin.Builder().setPackageName(packageName).build()
324
325 fun getSleepSessionRecords(inputDates: List<Pair<Instant, Instant>>): List<SleepSessionRecord> {
326 val result = arrayListOf<SleepSessionRecord>()
327 inputDates.forEach { (startTime, endTime) ->
328 result.add(SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build())
329 }
330
331 return result
332 }
333
verifySleepSessionListsEqualnull334 fun verifySleepSessionListsEqual(actual: List<Record>, expected: List<SleepSessionRecord>) {
335 assertThat(actual.size).isEqualTo(expected.size)
336 for ((index, element) in actual.withIndex()) {
337 assertThat(element is SleepSessionRecord).isTrue()
338 val expectedElement = expected[index]
339 val actualElement = element as SleepSessionRecord
340
341 assertThat(actualElement.startTime).isEqualTo(expectedElement.startTime)
342 assertThat(actualElement.endTime).isEqualTo(expectedElement.endTime)
343 assertThat(actualElement.notes).isEqualTo(expectedElement.notes)
344 assertThat(actualElement.title).isEqualTo(expectedElement.title)
345 assertThat(actualElement.stages).isEqualTo(expectedElement.stages)
346 }
347 }
348
verifyOxygenSaturationListsEqualnull349 fun verifyOxygenSaturationListsEqual(actual: List<Record>, expected: List<OxygenSaturationRecord>) {
350 assertThat(actual.size).isEqualTo(expected.size)
351 for ((index, element) in actual.withIndex()) {
352 assertThat(element is OxygenSaturationRecord).isTrue()
353 val expectedElement = expected[index]
354 val actualElement = element as OxygenSaturationRecord
355
356 assertThat(actualElement.time).isEqualTo(expectedElement.time)
357 assertThat(actualElement.percentage).isEqualTo(expectedElement.percentage)
358 }
359 }
360
verifyHydrationListsEqualnull361 fun verifyHydrationListsEqual(actual: List<Record>, expected: List<HydrationRecord>) {
362 assertThat(actual.size).isEqualTo(expected.size)
363 for ((index, element) in actual.withIndex()) {
364 assertThat(element is HydrationRecord).isTrue()
365 val expectedElement = expected[index]
366 val actualElement = element as HydrationRecord
367
368 assertThat(actualElement.startTime).isEqualTo(expectedElement.startTime)
369 assertThat(actualElement.endTime).isEqualTo(expectedElement.endTime)
370 assertThat(actualElement.volume).isEqualTo(expectedElement.volume)
371 }
372 }
373
verifyBodyWaterMassListsEqualnull374 fun verifyBodyWaterMassListsEqual(actual: List<Record>, expected: List<Record>) {
375 assertThat(actual.size).isEqualTo(expected.size)
376 for ((index, element) in actual.withIndex()) {
377 assertThat(element is BodyWaterMassRecord).isTrue()
378 val expectedElement = expected[index] as BodyWaterMassRecord
379 val actualElement = element as BodyWaterMassRecord
380
381 assertThat(actualElement.time).isEqualTo(expectedElement.time)
382 assertThat(actualElement.bodyWaterMass).isEqualTo(expectedElement.bodyWaterMass)
383 }
384 }
385
386 // test data constants - start
387
388 val START_TIME = Instant.parse("2023-06-12T22:30:00Z")
389
390 // pre-defined Instants within a day, week, and month of the START_TIME Instant
391 val INSTANT_DAY: Instant = Instant.parse("2023-06-11T23:30:00Z")
392 val INSTANT_DAY2: Instant = Instant.parse("2023-06-12T02:00:00Z")
393 val INSTANT_WEEK: Instant = Instant.parse("2023-06-14T11:15:00Z")
394 val INSTANT_MONTH1: Instant = Instant.parse("2023-06-26T23:10:00Z")
395 val INSTANT_MONTH2: Instant = Instant.parse("2023-06-30T11:30:00Z")
396 val INSTANT_MONTH3: Instant = Instant.parse("2023-07-01T07:45:00Z")
397 val INSTANT_MONTH4: Instant = Instant.parse("2023-07-01T19:15:00Z")
398 val INSTANT_MONTH5: Instant = Instant.parse("2023-07-05T03:45:00Z")
399 val INSTANT_MONTH6: Instant = Instant.parse("2023-07-07T07:05:00Z")
400
401 val SLEEP_DAY_0H20 =
402 getSleepSessionRecord(
403 Instant.parse("2023-06-12T21:00:00Z"),
404 Instant.parse("2023-06-12T21:20:00Z"),
405 )
406 val SLEEP_DAY_1H45 =
407 getSleepSessionRecord(
408 Instant.parse("2023-06-12T16:00:00Z"),
409 Instant.parse("2023-06-12T17:45:00Z"),
410 )
411 val SLEEP_DAY_9H15 =
412 getSleepSessionRecord(
413 Instant.parse("2023-06-12T22:30:00Z"),
414 Instant.parse("2023-06-13T07:45:00Z"),
415 )
416 val SLEEP_WEEK_9H15 =
417 getSleepSessionRecord(
418 Instant.parse("2023-06-14T22:30:00Z"),
419 Instant.parse("2023-06-15T07:45:00Z"),
420 )
421 val SLEEP_WEEK_33H15 =
422 getSleepSessionRecord(
423 Instant.parse("2023-06-11T22:30:00Z"),
424 Instant.parse("2023-06-13T07:45:00Z"),
425 )
426 val SLEEP_MONTH_81H15 =
427 getSleepSessionRecord(
428 Instant.parse("2023-07-09T22:30:00Z"),
429 Instant.parse("2023-07-13T07:45:00Z"),
430 )
431
432 val HYDRATION_MONTH: HydrationRecord =
433 getHydrationRecord(INSTANT_MONTH1, INSTANT_MONTH2, Volume.fromLiters(2.0))
434 val HYDRATION_MONTH2: HydrationRecord =
435 getHydrationRecord(INSTANT_MONTH3, INSTANT_MONTH4, Volume.fromLiters(0.3))
436 val HYDRATION_MONTH3: HydrationRecord =
437 getHydrationRecord(INSTANT_MONTH5, INSTANT_MONTH6, Volume.fromLiters(1.5))
438
439 val OXYGENSATURATION_DAY: OxygenSaturationRecord =
440 getOxygenSaturationRecord(INSTANT_DAY, Percentage.fromValue(98.0))
441 val OXYGENSATURATION_DAY2: OxygenSaturationRecord =
442 getOxygenSaturationRecord(INSTANT_DAY2, Percentage.fromValue(95.0))
443
444 val DISTANCE_STARTDATE_1500: DistanceRecord =
445 getDistanceRecord(Length.fromMeters(1500.0), START_TIME)
446
447 val WEIGHT_DAY_100: WeightRecord = getWeightRecord(INSTANT_DAY, Mass.fromGrams(100000.0))
448 val WEIGHT_WEEK_100: WeightRecord = getWeightRecord(INSTANT_WEEK, Mass.fromGrams(100000.0))
449 val WEIGHT_MONTH_100: WeightRecord = getWeightRecord(INSTANT_MONTH3, Mass.fromGrams(100000.0))
450 val WEIGHT_STARTDATE_100: WeightRecord = getWeightRecord(START_TIME, Mass.fromGrams(100000.0))
451
452 val INTERMENSTRUAL_BLEEDING_DAY: IntermenstrualBleedingRecord =
453 getIntermenstrualBleedingRecord(INSTANT_DAY)
454
455 val BODYTEMPERATURE_MONTH: BodyTemperatureRecord =
456 getBodyTemperatureRecord(
457 INSTANT_MONTH3,
458 BodyTemperatureMeasurementLocation.MEASUREMENT_LOCATION_MOUTH,
459 Temperature.fromCelsius(100.0),
460 )
461
462 val BODYWATERMASS_WEEK: BodyWaterMassRecord =
463 getBodyWaterMassRecord(INSTANT_WEEK, Mass.fromGrams(1000.0))
464
465 // records using today's date, yesterday's date, and the date two days ago - for header testing
getMixedRecordsAcrossTwoDaysnull466 fun getMixedRecordsAcrossTwoDays(timeSource: TimeSource): List<Record> {
467 val instantToday: Instant = timeSource.currentLocalDateTime().toInstant()
468 val instantYesterday: Instant = timeSource.currentLocalDateTime().minusDays(1).toInstant()
469 return listOf(
470 getHydrationRecord(instantToday, instantToday.plusSeconds(900), Volume.fromLiters(2.0)),
471 getSleepSessionRecord(instantToday, instantToday.plusSeconds(1800)),
472 getDistanceRecord(Length.fromMeters(2500.0), instantYesterday),
473 getOxygenSaturationRecord(instantYesterday, Percentage.fromValue(99.0)),
474 )
475 }
476
getMixedRecordsAcrossThreeDaysnull477 fun getMixedRecordsAcrossThreeDays(timeSource: TimeSource): List<Record> {
478 val instantTwoDaysAgo: Instant = timeSource.currentLocalDateTime().minusDays(2).toInstant()
479 return getMixedRecordsAcrossTwoDays(timeSource)
480 .plus(
481 listOf(
482 getWeightRecord(instantTwoDaysAgo, Mass.fromGrams(95000.0)),
483 getDistanceRecord(Length.fromMeters(2000.0), instantTwoDaysAgo),
484 )
485 )
486 }
487
488 // test data constants - end
489
490 // Enables or disables animations in a test
toggleAnimationnull491 fun toggleAnimation(isEnabled: Boolean) {
492 with(UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())) {
493 executeShellCommand(
494 "settings put global transition_animation_scale ${if (isEnabled) 1 else 0}"
495 )
496 executeShellCommand("settings put global window_animation_scale ${if (isEnabled) 1 else 0}")
497 executeShellCommand(
498 "settings put global animator_duration_scale ${if (isEnabled) 1 else 0}"
499 )
500 }
501 }
502
503 // Used for matching arguments for [RequestPermissionViewModel]
anynull504 fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
505
506 /** Utility function to turn an array of permission strings to a list of [HealthPermission]s */
507 fun Array<String>.toPermissionsList(): List<HealthPermission> {
508 return this.map { HealthPermission.fromPermissionString(it) }.toList()
509 }
510
511 // region apps
512
513 const val TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app"
514 const val TEST_APP_PACKAGE_NAME_2 = "android.healthconnect.controller.test.app2"
515 const val TEST_APP_PACKAGE_NAME_3 = "package.name.3"
516 const val UNSUPPORTED_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app3"
517 const val OLD_PERMISSIONS_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app4"
518 const val MEDICAL_PERMISSIONS_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app5"
519 const val BODY_SENSORS_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app6"
520 const val WEAR_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app7"
521 const val BODY_SENSORS_AND_HEALTH_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app8"
522 const val TEST_APP_NAME = "Health Connect test app"
523 const val TEST_APP_NAME_2 = "Health Connect test app 2"
524 const val TEST_APP_NAME_3 = "Health Connect test app 3"
525 const val OLD_APP_NAME = "Old permissions test app"
526 const val MEDICAL_APP_NAME = "Medical permissions HC app"
527 const val BODY_SENSORS_TEST_APP_NAME = "Body Sensors Test App"
528
529 val TEST_APP =
530 AppMetadata(packageName = TEST_APP_PACKAGE_NAME, appName = TEST_APP_NAME, icon = null)
531 val TEST_APP_2 =
532 AppMetadata(packageName = TEST_APP_PACKAGE_NAME_2, appName = TEST_APP_NAME_2, icon = null)
533 val TEST_APP_3 =
534 AppMetadata(packageName = TEST_APP_PACKAGE_NAME_3, appName = TEST_APP_NAME_3, icon = null)
535 val OLD_TEST_APP =
536 AppMetadata(
537 packageName = OLD_PERMISSIONS_TEST_APP_PACKAGE_NAME,
538 appName = OLD_APP_NAME,
539 icon = null,
540 )
541 // endregion
542
543 // PHR
544 val TEST_DATASOURCE_ID = getUniqueId()
545 val TEST_FHIR_VERSION: FhirVersion = FhirVersion.parseFhirVersion("4.0.1")
546 val TEST_FHIR_RESOURCE_IMMUNIZATION: FhirResource =
547 FhirResource.Builder(
548 FHIR_RESOURCE_TYPE_IMMUNIZATION,
549 "Immunization1",
550 "{\"resourceType\":\"Immunization\",\"id\":\"immunization-1\",\"status\":\"completed\",\"vaccineCode\":{\"coding\":[{\"system\":\"http://hl7.org/fhir/sid/cvx\",\"code\":\"115\"},{\"system\":\"http://hl7.org/fhir/sid/ndc\",\"code\":\"58160-842-11\"}],\"text\":\"Tdap\"},\"patient\":{\"reference\":\"Patient/patient_1\",\"display\":\"Example, Anne\"},\"occurrenceDateTime\":\"2018-05-21\"}",
551 )
552 .build()
553 val TEST_FHIR_RESOURCE_IMMUNIZATION_2: FhirResource =
554 FhirResource.Builder(
555 FHIR_RESOURCE_TYPE_IMMUNIZATION,
556 "Immunization2",
557 "{\"resourceType\":\"Immunization\",\"id\":\"immunization-2\",\"status\":\"completed\",\"vaccineCode\":{\"coding\":[{\"system\":\"http://hl7.org/fhir/sid/cvx\",\"code\":\"115\"},{\"system\":\"http://hl7.org/fhir/sid/ndc\",\"code\":\"58160-842-11\"}],\"text\":\"Tdap\"},\"patient\":{\"reference\":\"Patient/patient_1\",\"display\":\"Example, Anne\"},\"occurrenceDateTime\":\"2018-05-21\"}",
558 )
559 .build()
560 val TEST_FHIR_RESOURCE_IMMUNIZATION_3: FhirResource =
561 FhirResource.Builder(
562 FHIR_RESOURCE_TYPE_IMMUNIZATION,
563 "Immunization3",
564 "{\"resourceType\":\"Immunization\",\"id\":\"immunization-3\",\"status\":\"completed\",\"vaccineCode\":{\"coding\":[{\"system\":\"http://hl7.org/fhir/sid/cvx\",\"code\":\"115\"},{\"system\":\"http://hl7.org/fhir/sid/ndc\",\"code\":\"58160-842-11\"}],\"text\":\"Tdap\"},\"patient\":{\"reference\":\"Patient/patient_1\",\"display\":\"Example, Anne\"},\"occurrenceDateTime\":\"2018-05-21\"}",
565 )
566 .build()
567 val TEST_FHIR_RESOURCE_IMMUNIZATION_LONG: FhirResource =
568 FhirResource.Builder(
569 FHIR_RESOURCE_TYPE_IMMUNIZATION,
570 "Immunization11",
571 "{\"resourceType\":\"Immunization\",\"id\":\"immunization-1\",\"status\":\"completed\",\"vaccineCode\":{\"coding\":[{\"system\":\"http://hl7.org/fhir/sid/cvx\",\"code\":\"115\"},{\"system\":\"http://hl7.org/fhir/sid/ndc\",\"code\":\"58160-842-11\"}],\"text\":\"Tdap\"},\"patient\":{\"reference\":\"Patient/patient_1\",\"display\":\"Example, Anne\"},\"encounter\":{\"reference\":\"Encounter/encounter_unk\",\"display\":\"GP Visit\"},\"occurrenceDateTime\":\"2018-05-21\",\"primarySource\":true,\"manufacturer\":{\"display\":\"Sanofi Pasteur\"},\"lotNumber\":\"1\",\"site\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-ActSite\",\"code\":\"LA\",\"display\":\"Left Arm\"}],\"text\":\"Left Arm\"},\"route\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration\",\"code\":\"IM\",\"display\":\"Injection, intramuscular\"}],\"text\":\"Injection, intramuscular\"},\"doseQuantity\":{\"value\":0.5,\"unit\":\"mL\"},\"performer\":[{\"function\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v2-0443\",\"code\":\"AP\",\"display\":\"Administering Provider\"}],\"text\":\"Administering Provider\"},\"actor\":{\"reference\":\"Practitioner/practitioner_1\",\"type\":\"Practitioner\",\"display\":\"Dr Maria Hernandez\"}}]}",
572 )
573 .build()
574 val TEST_FHIR_RESOURCE_INVALID_JSON: FhirResource =
575 FhirResource.Builder(
576 FHIR_RESOURCE_TYPE_IMMUNIZATION,
577 "invalid_json",
578 "{\"resourceType\" : \"Immunization\", {{{\"id\"\" : \"Immunization-3\"}",
579 )
580 .build()
581 val TEST_MEDICAL_RESOURCE_IMMUNIZATION: MedicalResource =
582 MedicalResource.Builder(
583 MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
584 TEST_DATASOURCE_ID,
585 TEST_FHIR_VERSION,
586 TEST_FHIR_RESOURCE_IMMUNIZATION,
587 )
588 .build()
589 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_2: MedicalResource =
590 MedicalResource.Builder(
591 MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
592 TEST_DATASOURCE_ID,
593 TEST_FHIR_VERSION,
594 TEST_FHIR_RESOURCE_IMMUNIZATION_2,
595 )
596 .build()
597 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_3: MedicalResource =
598 MedicalResource.Builder(
599 MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
600 TEST_DATASOURCE_ID,
601 TEST_FHIR_VERSION,
602 TEST_FHIR_RESOURCE_IMMUNIZATION_3,
603 )
604 .build()
605 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_LONG: MedicalResource =
606 MedicalResource.Builder(
607 MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
608 TEST_DATASOURCE_ID,
609 TEST_FHIR_VERSION,
610 TEST_FHIR_RESOURCE_IMMUNIZATION_LONG,
611 )
612 .build()
613 val TEST_MEDICAL_RESOURCE_INVALID_JSON: MedicalResource =
614 MedicalResource.Builder(
615 MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
616 TEST_DATASOURCE_ID,
617 TEST_FHIR_VERSION,
618 TEST_FHIR_RESOURCE_INVALID_JSON,
619 )
620 .build()
621 val TEST_MEDICAL_DATA_SOURCE: MedicalDataSource =
622 MedicalDataSource.Builder(
623 /* id= */ TEST_DATASOURCE_ID,
624 TEST_APP_PACKAGE_NAME,
625 /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
626 /* displayName= */ "App A Data Source",
627 /* fhirVersion= */ TEST_FHIR_VERSION,
628 )
629 .build()
630 val TEST_MEDICAL_DATA_SOURCE_2: MedicalDataSource =
631 MedicalDataSource.Builder(
632 /* id= */ getUniqueId(),
633 TEST_APP_PACKAGE_NAME,
634 /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
635 /* displayName= */ "App A Data Source 2",
636 /* fhirVersion= */ TEST_FHIR_VERSION,
637 )
638 .build()
639 val TEST_MEDICAL_DATA_SOURCE_DIFFERENT_APP: MedicalDataSource =
640 MedicalDataSource.Builder(
641 /* id= */ getUniqueId(),
642 TEST_APP_PACKAGE_NAME_2,
643 /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
644 /* displayName= */ "App B Data Source",
645 /* fhirVersion= */ TEST_FHIR_VERSION,
646 )
647 .build()
648 // endregion
649