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.StepsRecord
45 import android.health.connect.datatypes.TotalCaloriesBurnedRecord
46 import android.health.connect.datatypes.WeightRecord
47 import android.health.connect.datatypes.units.Energy
48 import android.health.connect.datatypes.units.Length
49 import android.health.connect.datatypes.units.Mass
50 import android.health.connect.datatypes.units.Percentage
51 import android.health.connect.datatypes.units.Power
52 import android.health.connect.datatypes.units.Temperature
53 import android.health.connect.datatypes.units.Velocity
54 import android.health.connect.datatypes.units.Volume
55 import android.net.Uri
56 import androidx.test.platform.app.InstrumentationRegistry
57 import androidx.test.uiautomator.UiDevice
58 import com.android.healthconnect.controller.dataentries.units.PowerConverter
59 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType
60 import com.android.healthconnect.controller.permissions.data.HealthPermission
61 import com.android.healthconnect.controller.shared.app.AppMetadata
62 import com.android.healthconnect.controller.utils.TimeSource
63 import com.android.healthconnect.controller.utils.randomInstant
64 import com.android.healthconnect.controller.utils.toInstant
65 import com.android.healthconnect.controller.utils.toLocalDateTime
66 import com.google.common.truth.Truth.assertThat
67 import java.time.Instant
68 import java.time.LocalDate
69 import java.time.ZoneOffset
70 import java.util.UUID
71 import kotlin.random.Random
72 import org.mockito.Mockito
73 
74 val NOW: Instant = Instant.parse("2022-10-20T07:06:05.432Z")
75 val MIDNIGHT: Instant = Instant.parse("2022-10-20T00:00:00.000Z")
76 
77 fun getHeartRateRecord(heartRateValues: List<Long>, startTime: Instant = NOW): HeartRateRecord {
78     return HeartRateRecord.Builder(
79             getMetaData(),
80             startTime,
81             startTime.plusSeconds(2),
82             heartRateValues.map { HeartRateRecord.HeartRateSample(it, NOW) },
83         )
84         .build()
85 }
86 
getStepsRecordnull87 fun getStepsRecord(steps: Long, time: Instant = NOW): StepsRecord {
88     return StepsRecord.Builder(getMetaData(), time, time.plusSeconds(2), steps).build()
89 }
90 
getStepsRecordWithUniqueIdsnull91 fun getStepsRecordWithUniqueIds(steps: Long, time: Instant = NOW): StepsRecord {
92     return StepsRecord.Builder(getMetaDataWithUniqueIds(), time, time.plusSeconds(2), steps).build()
93 }
94 
getBasalMetabolicRateRecordnull95 fun getBasalMetabolicRateRecord(calories: Long): BasalMetabolicRateRecord {
96     val watts = PowerConverter.convertWattsFromCalories(calories)
97     return BasalMetabolicRateRecord.Builder(getMetaData(), NOW, Power.fromWatts(watts)).build()
98 }
99 
getDistanceRecordnull100 fun getDistanceRecord(distance: Length, time: Instant = NOW): DistanceRecord {
101     return DistanceRecord.Builder(getMetaData(), time, time.plusSeconds(2), distance).build()
102 }
103 
getTotalCaloriesBurnedRecordnull104 fun getTotalCaloriesBurnedRecord(calories: Energy, time: Instant = NOW): TotalCaloriesBurnedRecord {
105     return TotalCaloriesBurnedRecord.Builder(getMetaData(), time, time.plusSeconds(2), calories)
106         .build()
107 }
108 
getSleepSessionRecordnull109 fun getSleepSessionRecord(startTime: Instant = NOW): SleepSessionRecord {
110     val endTime = startTime.toLocalDateTime().plusHours(8).toInstant()
111     return SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build()
112 }
113 
getSleepSessionRecordnull114 fun getSleepSessionRecord(startTime: Instant, endTime: Instant): SleepSessionRecord {
115     return SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build()
116 }
117 
getWeightRecordnull118 fun getWeightRecord(time: Instant = NOW, weight: Mass): WeightRecord {
119     return WeightRecord.Builder(getMetaData(), time, weight).build()
120 }
121 
getIntermenstrualBleedingRecordnull122 fun getIntermenstrualBleedingRecord(time: Instant): IntermenstrualBleedingRecord {
123     return IntermenstrualBleedingRecord.Builder(getMetaData(), time).build()
124 }
125 
getBodyTemperatureRecordnull126 fun getBodyTemperatureRecord(
127     time: Instant,
128     location: Int,
129     temperature: Temperature,
130 ): BodyTemperatureRecord {
131     return BodyTemperatureRecord.Builder(getMetaData(), time, location, temperature).build()
132 }
133 
getOxygenSaturationRecordnull134 fun getOxygenSaturationRecord(time: Instant, percentage: Percentage): OxygenSaturationRecord {
135     return OxygenSaturationRecord.Builder(getMetaData(), time, percentage).build()
136 }
137 
getHydrationRecordnull138 fun getHydrationRecord(startTime: Instant, endTime: Instant, volume: Volume): HydrationRecord {
139     return HydrationRecord.Builder(getMetaData(), startTime, endTime, volume).build()
140 }
141 
getBodyWaterMassRecordnull142 fun getBodyWaterMassRecord(time: Instant, bodyWaterMass: Mass): BodyWaterMassRecord {
143     return BodyWaterMassRecord.Builder(getMetaData(), time, bodyWaterMass).build()
144 }
145 
getRandomRecordnull146 fun getRandomRecord(fitnessPermissionType: FitnessPermissionType, date: LocalDate): Record {
147     return when (fitnessPermissionType) {
148         FitnessPermissionType.STEPS ->
149             getStepsRecord(Random.nextLong(0, 5000), date.randomInstant())
150         FitnessPermissionType.DISTANCE ->
151             getDistanceRecord(
152                 Length.fromMeters(Random.nextDouble(0.0, 5000.0)),
153                 date.randomInstant(),
154             )
155         FitnessPermissionType.TOTAL_CALORIES_BURNED ->
156             getTotalCaloriesBurnedRecord(
157                 Energy.fromCalories(Random.nextDouble(1500.0, 5000.0)),
158                 date.randomInstant(),
159             )
160         FitnessPermissionType.SLEEP -> getSleepSessionRecord(date.randomInstant())
161         else ->
162             throw IllegalArgumentException(
163                 "HealthPermissionType $fitnessPermissionType not supported"
164             )
165     }
166 }
167 
getSamplePlannedExerciseSessionRecordnull168 fun getSamplePlannedExerciseSessionRecord(): PlannedExerciseSessionRecord {
169     val exerciseBlock1 =
170         getPlannedExerciseBlock(
171             repetitions = 1,
172             description = "Warm up",
173             exerciseSteps =
174                 listOf(
175                     getPlannedExerciseStep(
176                         exerciseSegmentType = ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
177                         completionGoal =
178                             ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(1000.0)),
179                         performanceGoals =
180                             listOf(
181                                 ExercisePerformanceGoal.HeartRateGoal(100, 150),
182                                 ExercisePerformanceGoal.SpeedGoal(
183                                     Velocity.fromMetersPerSecond(25.0),
184                                     Velocity.fromMetersPerSecond(15.0),
185                                 ),
186                             ),
187                     )
188                 ),
189         )
190     val exerciseBlock2 =
191         getPlannedExerciseBlock(
192             repetitions = 1,
193             description = "Main set",
194             exerciseSteps =
195                 listOf(
196                     getPlannedExerciseStep(
197                         exerciseSegmentType = ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_RUNNING,
198                         completionGoal =
199                             ExerciseCompletionGoal.DistanceGoal(Length.fromMeters(4000.0)),
200                         performanceGoals =
201                             listOf(
202                                 ExercisePerformanceGoal.HeartRateGoal(150, 180),
203                                 ExercisePerformanceGoal.SpeedGoal(
204                                     Velocity.fromMetersPerSecond(50.0),
205                                     Velocity.fromMetersPerSecond(25.0),
206                                 ),
207                             ),
208                     )
209                 ),
210         )
211     val exerciseBlocks = listOf(exerciseBlock1, exerciseBlock2)
212 
213     return getPlannedExerciseSessionRecord(
214         title = "Morning Run",
215         note = "Morning quick run by the park",
216         exerciseBlocks = exerciseBlocks,
217     )
218 }
219 
getPlannedExerciseSessionRecordnull220 fun getPlannedExerciseSessionRecord(
221     title: String,
222     note: String,
223     exerciseBlocks: List<PlannedExerciseBlock>,
224 ): PlannedExerciseSessionRecord {
225     return basePlannedExerciseSession(ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING)
226         .setTitle(title)
227         .setNotes(note)
228         .setBlocks(exerciseBlocks)
229         .build()
230 }
231 
basePlannedExerciseSessionnull232 private fun basePlannedExerciseSession(exerciseType: Int): PlannedExerciseSessionRecord.Builder {
233     val builder: PlannedExerciseSessionRecord.Builder =
234         PlannedExerciseSessionRecord.Builder(
235             getMetaData(),
236             exerciseType,
237             NOW,
238             NOW.plusSeconds(3600),
239         )
240     builder.setNotes("Sample training plan notes")
241     builder.setTitle("Training plan title")
242     builder.setStartZoneOffset(ZoneOffset.UTC)
243     builder.setEndZoneOffset(ZoneOffset.UTC)
244     return builder
245 }
246 
getPlannedExerciseBlocknull247 fun getPlannedExerciseBlock(
248     repetitions: Int,
249     description: String,
250     exerciseSteps: List<PlannedExerciseStep>,
251 ): PlannedExerciseBlock {
252     return PlannedExerciseBlock.Builder(repetitions)
253         .setDescription(description)
254         .setSteps(exerciseSteps)
255         .build()
256 }
257 
getPlannedExerciseStepnull258 fun getPlannedExerciseStep(
259     exerciseSegmentType: Int,
260     completionGoal: ExerciseCompletionGoal,
261     performanceGoals: List<ExercisePerformanceGoal>,
262 ): PlannedExerciseStep {
263     return PlannedExerciseStep.Builder(
264             exerciseSegmentType,
265             PlannedExerciseStep.EXERCISE_CATEGORY_ACTIVE,
266             completionGoal,
267         )
268         .setPerformanceGoals(performanceGoals)
269         .build()
270 }
271 
getMetaDatanull272 fun getMetaData(): Metadata {
273     return getMetaData(TEST_APP_PACKAGE_NAME)
274 }
275 
getMetaDataWithUniqueIdsnull276 fun getMetaDataWithUniqueIds(): Metadata {
277     return getMetaDataWithUniqueIds(TEST_APP_PACKAGE_NAME)
278 }
279 
getMetaDatanull280 fun getMetaData(packageName: String): Metadata {
281     val device: Device =
282         Device.Builder().setManufacturer("google").setModel("Pixel4a").setType(2).build()
283     val dataOrigin = DataOrigin.Builder().setPackageName(packageName).build()
284     return Metadata.Builder()
285         .setId("test_id")
286         .setDevice(device)
287         .setDataOrigin(dataOrigin)
288         .setClientRecordId("BMR" + Math.random().toString())
289         .build()
290 }
291 
getMetaDataWithUniqueIdsnull292 fun getMetaDataWithUniqueIds(packageName: String): Metadata {
293     val device: Device =
294         Device.Builder().setManufacturer("google").setModel("Pixel4a").setType(2).build()
295     val dataOrigin = DataOrigin.Builder().setPackageName(packageName).build()
296     return Metadata.Builder()
297         .setId(getUniqueId())
298         .setDevice(device)
299         .setDataOrigin(dataOrigin)
300         .setClientRecordId("BMR" + Math.random().toString())
301         .build()
302 }
303 
getUniqueIdnull304 fun getUniqueId(): String {
305     return UUID.randomUUID().toString()
306 }
307 
getDataOriginnull308 fun getDataOrigin(packageName: String): DataOrigin =
309     DataOrigin.Builder().setPackageName(packageName).build()
310 
311 fun getSleepSessionRecords(inputDates: List<Pair<Instant, Instant>>): List<SleepSessionRecord> {
312     val result = arrayListOf<SleepSessionRecord>()
313     inputDates.forEach { (startTime, endTime) ->
314         result.add(SleepSessionRecord.Builder(getMetaData(), startTime, endTime).build())
315     }
316 
317     return result
318 }
319 
verifySleepSessionListsEqualnull320 fun verifySleepSessionListsEqual(actual: List<Record>, expected: List<SleepSessionRecord>) {
321     assertThat(actual.size).isEqualTo(expected.size)
322     for ((index, element) in actual.withIndex()) {
323         assertThat(element is SleepSessionRecord).isTrue()
324         val expectedElement = expected[index]
325         val actualElement = element as SleepSessionRecord
326 
327         assertThat(actualElement.startTime).isEqualTo(expectedElement.startTime)
328         assertThat(actualElement.endTime).isEqualTo(expectedElement.endTime)
329         assertThat(actualElement.notes).isEqualTo(expectedElement.notes)
330         assertThat(actualElement.title).isEqualTo(expectedElement.title)
331         assertThat(actualElement.stages).isEqualTo(expectedElement.stages)
332     }
333 }
334 
verifyOxygenSaturationListsEqualnull335 fun verifyOxygenSaturationListsEqual(actual: List<Record>, expected: List<OxygenSaturationRecord>) {
336     assertThat(actual.size).isEqualTo(expected.size)
337     for ((index, element) in actual.withIndex()) {
338         assertThat(element is OxygenSaturationRecord).isTrue()
339         val expectedElement = expected[index]
340         val actualElement = element as OxygenSaturationRecord
341 
342         assertThat(actualElement.time).isEqualTo(expectedElement.time)
343         assertThat(actualElement.percentage).isEqualTo(expectedElement.percentage)
344     }
345 }
346 
verifyHydrationListsEqualnull347 fun verifyHydrationListsEqual(actual: List<Record>, expected: List<HydrationRecord>) {
348     assertThat(actual.size).isEqualTo(expected.size)
349     for ((index, element) in actual.withIndex()) {
350         assertThat(element is HydrationRecord).isTrue()
351         val expectedElement = expected[index]
352         val actualElement = element as HydrationRecord
353 
354         assertThat(actualElement.startTime).isEqualTo(expectedElement.startTime)
355         assertThat(actualElement.endTime).isEqualTo(expectedElement.endTime)
356         assertThat(actualElement.volume).isEqualTo(expectedElement.volume)
357     }
358 }
359 
verifyBodyWaterMassListsEqualnull360 fun verifyBodyWaterMassListsEqual(actual: List<Record>, expected: List<Record>) {
361     assertThat(actual.size).isEqualTo(expected.size)
362     for ((index, element) in actual.withIndex()) {
363         assertThat(element is BodyWaterMassRecord).isTrue()
364         val expectedElement = expected[index] as BodyWaterMassRecord
365         val actualElement = element as BodyWaterMassRecord
366 
367         assertThat(actualElement.time).isEqualTo(expectedElement.time)
368         assertThat(actualElement.bodyWaterMass).isEqualTo(expectedElement.bodyWaterMass)
369     }
370 }
371 
372 // test data constants - start
373 
374 val START_TIME = Instant.parse("2023-06-12T22:30:00Z")
375 
376 // pre-defined Instants within a day, week, and month of the START_TIME Instant
377 val INSTANT_DAY: Instant = Instant.parse("2023-06-11T23:30:00Z")
378 val INSTANT_DAY2: Instant = Instant.parse("2023-06-12T02:00:00Z")
379 val INSTANT_WEEK: Instant = Instant.parse("2023-06-14T11:15:00Z")
380 val INSTANT_MONTH1: Instant = Instant.parse("2023-06-26T23:10:00Z")
381 val INSTANT_MONTH2: Instant = Instant.parse("2023-06-30T11:30:00Z")
382 val INSTANT_MONTH3: Instant = Instant.parse("2023-07-01T07:45:00Z")
383 val INSTANT_MONTH4: Instant = Instant.parse("2023-07-01T19:15:00Z")
384 val INSTANT_MONTH5: Instant = Instant.parse("2023-07-05T03:45:00Z")
385 val INSTANT_MONTH6: Instant = Instant.parse("2023-07-07T07:05:00Z")
386 
387 val SLEEP_DAY_0H20 =
388     getSleepSessionRecord(
389         Instant.parse("2023-06-12T21:00:00Z"),
390         Instant.parse("2023-06-12T21:20:00Z"),
391     )
392 val SLEEP_DAY_1H45 =
393     getSleepSessionRecord(
394         Instant.parse("2023-06-12T16:00:00Z"),
395         Instant.parse("2023-06-12T17:45:00Z"),
396     )
397 val SLEEP_DAY_9H15 =
398     getSleepSessionRecord(
399         Instant.parse("2023-06-12T22:30:00Z"),
400         Instant.parse("2023-06-13T07:45:00Z"),
401     )
402 val SLEEP_WEEK_9H15 =
403     getSleepSessionRecord(
404         Instant.parse("2023-06-14T22:30:00Z"),
405         Instant.parse("2023-06-15T07:45:00Z"),
406     )
407 val SLEEP_WEEK_33H15 =
408     getSleepSessionRecord(
409         Instant.parse("2023-06-11T22:30:00Z"),
410         Instant.parse("2023-06-13T07:45:00Z"),
411     )
412 val SLEEP_MONTH_81H15 =
413     getSleepSessionRecord(
414         Instant.parse("2023-07-09T22:30:00Z"),
415         Instant.parse("2023-07-13T07:45:00Z"),
416     )
417 
418 val HYDRATION_MONTH: HydrationRecord =
419     getHydrationRecord(INSTANT_MONTH1, INSTANT_MONTH2, Volume.fromLiters(2.0))
420 val HYDRATION_MONTH2: HydrationRecord =
421     getHydrationRecord(INSTANT_MONTH3, INSTANT_MONTH4, Volume.fromLiters(0.3))
422 val HYDRATION_MONTH3: HydrationRecord =
423     getHydrationRecord(INSTANT_MONTH5, INSTANT_MONTH6, Volume.fromLiters(1.5))
424 
425 val OXYGENSATURATION_DAY: OxygenSaturationRecord =
426     getOxygenSaturationRecord(INSTANT_DAY, Percentage.fromValue(98.0))
427 val OXYGENSATURATION_DAY2: OxygenSaturationRecord =
428     getOxygenSaturationRecord(INSTANT_DAY2, Percentage.fromValue(95.0))
429 
430 val DISTANCE_STARTDATE_1500: DistanceRecord =
431     getDistanceRecord(Length.fromMeters(1500.0), START_TIME)
432 
433 val WEIGHT_DAY_100: WeightRecord = getWeightRecord(INSTANT_DAY, Mass.fromGrams(100000.0))
434 val WEIGHT_WEEK_100: WeightRecord = getWeightRecord(INSTANT_WEEK, Mass.fromGrams(100000.0))
435 val WEIGHT_MONTH_100: WeightRecord = getWeightRecord(INSTANT_MONTH3, Mass.fromGrams(100000.0))
436 val WEIGHT_STARTDATE_100: WeightRecord = getWeightRecord(START_TIME, Mass.fromGrams(100000.0))
437 
438 val INTERMENSTRUAL_BLEEDING_DAY: IntermenstrualBleedingRecord =
439     getIntermenstrualBleedingRecord(INSTANT_DAY)
440 
441 val BODYTEMPERATURE_MONTH: BodyTemperatureRecord =
442     getBodyTemperatureRecord(
443         INSTANT_MONTH3,
444         BodyTemperatureMeasurementLocation.MEASUREMENT_LOCATION_MOUTH,
445         Temperature.fromCelsius(100.0),
446     )
447 
448 val BODYWATERMASS_WEEK: BodyWaterMassRecord =
449     getBodyWaterMassRecord(INSTANT_WEEK, Mass.fromGrams(1000.0))
450 
451 // records using today's date, yesterday's date, and the date two days ago - for header testing
getMixedRecordsAcrossTwoDaysnull452 fun getMixedRecordsAcrossTwoDays(timeSource: TimeSource): List<Record> {
453     val instantToday: Instant = timeSource.currentLocalDateTime().toInstant()
454     val instantYesterday: Instant = timeSource.currentLocalDateTime().minusDays(1).toInstant()
455     return listOf(
456         getHydrationRecord(instantToday, instantToday.plusSeconds(900), Volume.fromLiters(2.0)),
457         getSleepSessionRecord(instantToday, instantToday.plusSeconds(1800)),
458         getDistanceRecord(Length.fromMeters(2500.0), instantYesterday),
459         getOxygenSaturationRecord(instantYesterday, Percentage.fromValue(99.0)),
460     )
461 }
462 
getMixedRecordsAcrossThreeDaysnull463 fun getMixedRecordsAcrossThreeDays(timeSource: TimeSource): List<Record> {
464     val instantTwoDaysAgo: Instant = timeSource.currentLocalDateTime().minusDays(2).toInstant()
465     return getMixedRecordsAcrossTwoDays(timeSource)
466         .plus(
467             listOf(
468                 getWeightRecord(instantTwoDaysAgo, Mass.fromGrams(95000.0)),
469                 getDistanceRecord(Length.fromMeters(2000.0), instantTwoDaysAgo),
470             )
471         )
472 }
473 
474 // test data constants - end
475 
476 // Enables or disables animations in a test
toggleAnimationnull477 fun toggleAnimation(isEnabled: Boolean) {
478     with(UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())) {
479         executeShellCommand(
480             "settings put global transition_animation_scale ${if (isEnabled) 1 else 0}"
481         )
482         executeShellCommand("settings put global window_animation_scale ${if (isEnabled) 1 else 0}")
483         executeShellCommand(
484             "settings put global animator_duration_scale ${if (isEnabled) 1 else 0}"
485         )
486     }
487 }
488 
489 // Used for matching arguments for [RequestPermissionViewModel]
anynull490 fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
491 
492 /** Utility function to turn an array of permission strings to a list of [HealthPermission]s */
493 fun Array<String>.toPermissionsList(): List<HealthPermission> {
494     return this.map { HealthPermission.fromPermissionString(it) }.toList()
495 }
496 
497 // region apps
498 
499 const val TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app"
500 const val TEST_APP_PACKAGE_NAME_2 = "android.healthconnect.controller.test.app2"
501 const val TEST_APP_PACKAGE_NAME_3 = "package.name.3"
502 const val UNSUPPORTED_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app3"
503 const val OLD_PERMISSIONS_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app4"
504 const val MEDICAL_PERMISSIONS_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app5"
505 const val WEAR_LEGACY_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app6"
506 const val WEAR_TEST_APP_PACKAGE_NAME = "android.healthconnect.controller.test.app7"
507 const val TEST_APP_NAME = "Health Connect test app"
508 const val TEST_APP_NAME_2 = "Health Connect test app 2"
509 const val TEST_APP_NAME_3 = "Health Connect test app 3"
510 const val OLD_APP_NAME = "Old permissions test app"
511 const val MEDICAL_APP_NAME = "Medical permissions HC app"
512 
513 val TEST_APP =
514     AppMetadata(packageName = TEST_APP_PACKAGE_NAME, appName = TEST_APP_NAME, icon = null)
515 val TEST_APP_2 =
516     AppMetadata(packageName = TEST_APP_PACKAGE_NAME_2, appName = TEST_APP_NAME_2, icon = null)
517 val TEST_APP_3 =
518     AppMetadata(packageName = TEST_APP_PACKAGE_NAME_3, appName = TEST_APP_NAME_3, icon = null)
519 val OLD_TEST_APP =
520     AppMetadata(
521         packageName = OLD_PERMISSIONS_TEST_APP_PACKAGE_NAME,
522         appName = OLD_APP_NAME,
523         icon = null,
524     )
525 // endregion
526 
527 // PHR
528 val TEST_DATASOURCE_ID = getUniqueId()
529 val TEST_FHIR_VERSION: FhirVersion = FhirVersion.parseFhirVersion("4.0.1")
530 val TEST_FHIR_RESOURCE_IMMUNIZATION: FhirResource =
531     FhirResource.Builder(
532             FHIR_RESOURCE_TYPE_IMMUNIZATION,
533             "Immunization1",
534         "{\"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\"}"
535         )
536         .build()
537 val TEST_FHIR_RESOURCE_IMMUNIZATION_2: FhirResource =
538     FhirResource.Builder(
539             FHIR_RESOURCE_TYPE_IMMUNIZATION,
540             "Immunization2",
541         "{\"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\"}"
542         )
543         .build()
544 val TEST_FHIR_RESOURCE_IMMUNIZATION_3: FhirResource =
545     FhirResource.Builder(
546             FHIR_RESOURCE_TYPE_IMMUNIZATION,
547             "Immunization3",
548               "{\"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\"}"
549         )
550         .build()
551 val TEST_FHIR_RESOURCE_IMMUNIZATION_LONG: FhirResource =
552     FhirResource.Builder(
553             FHIR_RESOURCE_TYPE_IMMUNIZATION,
554             "Immunization11",
555             "{\"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\"}}]}",
556         )
557         .build()
558 val TEST_FHIR_RESOURCE_INVALID_JSON: FhirResource =
559     FhirResource.Builder(
560             FHIR_RESOURCE_TYPE_IMMUNIZATION,
561             "invalid_json",
562             "{\"resourceType\" : \"Immunization\", {{{\"id\"\" : \"Immunization-3\"}",
563         )
564         .build()
565 val TEST_MEDICAL_RESOURCE_IMMUNIZATION: MedicalResource =
566     MedicalResource.Builder(
567             MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
568             TEST_DATASOURCE_ID,
569             TEST_FHIR_VERSION,
570             TEST_FHIR_RESOURCE_IMMUNIZATION,
571         )
572         .build()
573 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_2: MedicalResource =
574     MedicalResource.Builder(
575             MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
576             TEST_DATASOURCE_ID,
577             TEST_FHIR_VERSION,
578             TEST_FHIR_RESOURCE_IMMUNIZATION_2,
579         )
580         .build()
581 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_3: MedicalResource =
582     MedicalResource.Builder(
583             MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
584             TEST_DATASOURCE_ID,
585             TEST_FHIR_VERSION,
586             TEST_FHIR_RESOURCE_IMMUNIZATION_3,
587         )
588         .build()
589 val TEST_MEDICAL_RESOURCE_IMMUNIZATION_LONG: MedicalResource =
590     MedicalResource.Builder(
591             MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
592             TEST_DATASOURCE_ID,
593             TEST_FHIR_VERSION,
594             TEST_FHIR_RESOURCE_IMMUNIZATION_LONG,
595         )
596         .build()
597 val TEST_MEDICAL_RESOURCE_INVALID_JSON: MedicalResource =
598     MedicalResource.Builder(
599             MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES,
600             TEST_DATASOURCE_ID,
601             TEST_FHIR_VERSION,
602             TEST_FHIR_RESOURCE_INVALID_JSON,
603         )
604         .build()
605 val TEST_MEDICAL_DATA_SOURCE: MedicalDataSource =
606     MedicalDataSource.Builder(
607             /* id= */ TEST_DATASOURCE_ID,
608             TEST_APP_PACKAGE_NAME,
609             /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
610             /* displayName= */ "App A Data Source",
611             /* fhirVersion= */ TEST_FHIR_VERSION,
612         )
613         .build()
614 val TEST_MEDICAL_DATA_SOURCE_2: MedicalDataSource =
615     MedicalDataSource.Builder(
616             /* id= */ getUniqueId(),
617             TEST_APP_PACKAGE_NAME,
618             /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
619             /* displayName= */ "App A Data Source 2",
620             /* fhirVersion= */ TEST_FHIR_VERSION,
621         )
622         .build()
623 val TEST_MEDICAL_DATA_SOURCE_DIFFERENT_APP: MedicalDataSource =
624     MedicalDataSource.Builder(
625             /* id= */ getUniqueId(),
626             TEST_APP_PACKAGE_NAME_2,
627             /* fhirBaseUri= */ Uri.parse("fhir.base.uri"),
628             /* displayName= */ "App B Data Source",
629             /* fhirVersion= */ TEST_FHIR_VERSION,
630         )
631         .build()
632 // endregion
633