1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.healthconnect.cts.aggregation;
18 
19 import static android.health.connect.datatypes.SleepSessionRecord.SLEEP_DURATION_TOTAL;
20 import static android.healthconnect.cts.utils.DataFactory.SESSION_END_TIME;
21 import static android.healthconnect.cts.utils.DataFactory.SESSION_START_TIME;
22 import static android.healthconnect.cts.utils.DataFactory.generateMetadata;
23 import static android.healthconnect.cts.utils.TestUtils.getAggregateResponse;
24 import static android.healthconnect.cts.utils.TestUtils.getAggregateResponseGroupByDuration;
25 import static android.healthconnect.cts.utils.TestUtils.insertRecord;
26 import static android.healthconnect.cts.utils.TestUtils.insertRecords;
27 import static android.healthconnect.cts.utils.TestUtils.setupAggregation;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import android.health.connect.AggregateRecordsGroupedByDurationResponse;
32 import android.health.connect.AggregateRecordsRequest;
33 import android.health.connect.AggregateRecordsResponse;
34 import android.health.connect.HealthDataCategory;
35 import android.health.connect.TimeInstantRangeFilter;
36 import android.health.connect.datatypes.SleepSessionRecord;
37 import android.healthconnect.cts.utils.AssumptionCheckerRule;
38 import android.healthconnect.cts.utils.TestUtils;
39 
40 import org.junit.After;
41 import org.junit.Before;
42 import org.junit.Rule;
43 import org.junit.Test;
44 
45 import java.time.Duration;
46 import java.time.Instant;
47 import java.time.temporal.ChronoUnit;
48 import java.util.List;
49 
50 public class SleepDurationAggregationTest {
51     private final TimeInstantRangeFilter mFilterAllSession =
52             new TimeInstantRangeFilter.Builder()
53                     .setStartTime(Instant.EPOCH)
54                     .setEndTime(Instant.now().plusSeconds(1000))
55                     .build();
56 
57     private final AggregateRecordsRequest<Long> mAggregateAllRecordsRequest =
58             new AggregateRecordsRequest.Builder<Long>(mFilterAllSession)
59                     .addAggregationType(SLEEP_DURATION_TOTAL)
60                     .build();
61 
62     private static final String PACKAGE_NAME = "android.healthconnect.cts";
63 
64     @Rule
65     public AssumptionCheckerRule mSupportedHardwareRule =
66             new AssumptionCheckerRule(
67                     TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
68 
69     @Before
setUp()70     public void setUp() throws InterruptedException {
71         TestUtils.deleteAllStagedRemoteData();
72     }
73 
74     @After
tearDown()75     public void tearDown() throws InterruptedException {
76         TestUtils.verifyDeleteRecords(
77                 SleepSessionRecord.class,
78                 new TimeInstantRangeFilter.Builder()
79                         .setStartTime(Instant.EPOCH)
80                         .setEndTime(Instant.now())
81                         .build());
82     }
83 
84     @Test
testSimpleAggregation_oneSession_returnsItsDuration()85     public void testSimpleAggregation_oneSession_returnsItsDuration() throws InterruptedException {
86         setupAggregation(PACKAGE_NAME, HealthDataCategory.SLEEP);
87         SleepSessionRecord session =
88                 new SleepSessionRecord.Builder(
89                                 generateMetadata(), SESSION_START_TIME, SESSION_END_TIME)
90                         .build();
91         insertRecord(session);
92         AggregateRecordsResponse<Long> response = getAggregateResponse(mAggregateAllRecordsRequest);
93 
94         assertThat(response.get(SLEEP_DURATION_TOTAL)).isNotNull();
95         assertThat(response.get(SLEEP_DURATION_TOTAL))
96                 .isEqualTo(
97                         session.getEndTime().toEpochMilli()
98                                 - session.getStartTime().toEpochMilli());
99         assertThat(response.getZoneOffset(SLEEP_DURATION_TOTAL))
100                 .isEqualTo(session.getStartZoneOffset());
101     }
102 
103     @Test
testSimpleAggregation_oneSessionWithAwake_returnsDurationMinusAwake()104     public void testSimpleAggregation_oneSessionWithAwake_returnsDurationMinusAwake()
105             throws InterruptedException {
106         setupAggregation(PACKAGE_NAME, HealthDataCategory.SLEEP);
107         SleepSessionRecord.Stage awakeStage =
108                 new SleepSessionRecord.Stage(
109                         SESSION_START_TIME,
110                         SESSION_START_TIME.plusSeconds(100),
111                         SleepSessionRecord.StageType.STAGE_TYPE_AWAKE);
112         SleepSessionRecord session =
113                 new SleepSessionRecord.Builder(
114                                 generateMetadata(), SESSION_START_TIME, SESSION_END_TIME)
115                         .setStages(
116                                 List.of(
117                                         awakeStage,
118                                         new SleepSessionRecord.Stage(
119                                                 SESSION_START_TIME.plusSeconds(200),
120                                                 SESSION_START_TIME.plusSeconds(1400),
121                                                 SleepSessionRecord.StageType
122                                                         .STAGE_TYPE_SLEEPING_DEEP),
123                                         new SleepSessionRecord.Stage(
124                                                 SESSION_START_TIME.plusSeconds(1500),
125                                                 SESSION_START_TIME.plusSeconds(2000),
126                                                 SleepSessionRecord.StageType
127                                                         .STAGE_TYPE_SLEEPING_LIGHT),
128                                         new SleepSessionRecord.Stage(
129                                                 SESSION_START_TIME.plusSeconds(2100),
130                                                 SESSION_START_TIME.plusSeconds(3000),
131                                                 SleepSessionRecord.StageType
132                                                         .STAGE_TYPE_SLEEPING_REM)))
133                         .build();
134 
135         insertRecords(session);
136         AggregateRecordsResponse<Long> response = getAggregateResponse(mAggregateAllRecordsRequest);
137 
138         assertThat(response.get(SLEEP_DURATION_TOTAL)).isNotNull();
139 
140         long awakeDuration =
141                 awakeStage.getEndTime().toEpochMilli() - awakeStage.getStartTime().toEpochMilli();
142         assertThat(response.get(SLEEP_DURATION_TOTAL))
143                 .isEqualTo(
144                         session.getEndTime().toEpochMilli()
145                                 - session.getStartTime().toEpochMilli()
146                                 - awakeDuration);
147     }
148 
149     @Test
testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups()150     public void testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups()
151             throws InterruptedException {
152         setupAggregation(PACKAGE_NAME, HealthDataCategory.SLEEP);
153         Instant endTime = SESSION_START_TIME.plus(10, ChronoUnit.HOURS);
154         SleepSessionRecord session =
155                 new SleepSessionRecord.Builder(generateMetadata(), SESSION_START_TIME, endTime)
156                         .build();
157         insertRecord(session);
158 
159         List<AggregateRecordsGroupedByDurationResponse<Long>> responses =
160                 getAggregateResponseGroupByDuration(
161                         new AggregateRecordsRequest.Builder<Long>(
162                                         new TimeInstantRangeFilter.Builder()
163                                                 .setStartTime(SESSION_START_TIME)
164                                                 .setEndTime(endTime)
165                                                 .build())
166                                 .addAggregationType(SLEEP_DURATION_TOTAL)
167                                 .build(),
168                         Duration.of(1, ChronoUnit.HOURS));
169 
170         assertThat(responses).isNotEmpty();
171         assertThat(responses.size()).isEqualTo(10);
172         for (AggregateRecordsGroupedByDurationResponse<Long> response : responses) {
173             assertThat(response.get(SLEEP_DURATION_TOTAL)).isEqualTo(3600000);
174         }
175     }
176 }
177