• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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