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; 18 19 import static android.health.connect.datatypes.ExerciseSessionRecord.EXERCISE_DURATION_TOTAL; 20 import static android.healthconnect.cts.TestUtils.SESSION_END_TIME; 21 import static android.healthconnect.cts.TestUtils.SESSION_START_TIME; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import android.health.connect.AggregateRecordsGroupedByDurationResponse; 26 import android.health.connect.AggregateRecordsRequest; 27 import android.health.connect.AggregateRecordsResponse; 28 import android.health.connect.LocalTimeRangeFilter; 29 import android.health.connect.TimeInstantRangeFilter; 30 import android.health.connect.datatypes.ExerciseSegment; 31 import android.health.connect.datatypes.ExerciseSegmentType; 32 import android.health.connect.datatypes.ExerciseSessionRecord; 33 import android.health.connect.datatypes.ExerciseSessionType; 34 35 import org.junit.After; 36 import org.junit.Test; 37 38 import java.time.Duration; 39 import java.time.Instant; 40 import java.time.LocalDateTime; 41 import java.time.ZoneOffset; 42 import java.time.temporal.ChronoUnit; 43 import java.util.List; 44 45 public class ExerciseDurationAggregationTest { 46 private final TimeInstantRangeFilter mFilterAllSession = 47 new TimeInstantRangeFilter.Builder() 48 .setStartTime(Instant.EPOCH) 49 .setEndTime(Instant.now().plusSeconds(1000)) 50 .build(); 51 52 private final TimeInstantRangeFilter mFilterSmallWindow = 53 new TimeInstantRangeFilter.Builder() 54 .setStartTime(SESSION_START_TIME) 55 .setEndTime(SESSION_END_TIME) 56 .build(); 57 58 private final AggregateRecordsRequest<Long> mAggregateAllRecordsRequest = 59 new AggregateRecordsRequest.Builder<Long>(mFilterAllSession) 60 .addAggregationType(EXERCISE_DURATION_TOTAL) 61 .build(); 62 63 private final AggregateRecordsRequest<Long> mAggregateInSmallWindow = 64 new AggregateRecordsRequest.Builder<Long>(mFilterSmallWindow) 65 .addAggregationType(EXERCISE_DURATION_TOTAL) 66 .build(); 67 68 @After tearDown()69 public void tearDown() throws InterruptedException { 70 TestUtils.verifyDeleteRecords( 71 ExerciseSessionRecord.class, 72 new TimeInstantRangeFilter.Builder() 73 .setStartTime(Instant.EPOCH) 74 .setEndTime(Instant.now()) 75 .build()); 76 } 77 78 @Test testSimpleAggregation_oneSession_returnsItsDuration()79 public void testSimpleAggregation_oneSession_returnsItsDuration() throws InterruptedException { 80 ExerciseSessionRecord session = 81 new ExerciseSessionRecord.Builder( 82 TestUtils.generateMetadata(), 83 SESSION_START_TIME, 84 SESSION_END_TIME, 85 ExerciseSessionType 86 .EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING) 87 .build(); 88 AggregateRecordsResponse<Long> response = 89 TestUtils.getAggregateResponse(mAggregateAllRecordsRequest, List.of(session)); 90 91 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isNotNull(); 92 assertThat(response.get(EXERCISE_DURATION_TOTAL)) 93 .isEqualTo( 94 session.getEndTime().toEpochMilli() 95 - session.getStartTime().toEpochMilli()); 96 assertThat(response.getZoneOffset(EXERCISE_DURATION_TOTAL)) 97 .isEqualTo(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())); 98 } 99 100 @Test testSimpleAggregation_oneSessionStartEarlierThanWindow_returnsOverlapDuration()101 public void testSimpleAggregation_oneSessionStartEarlierThanWindow_returnsOverlapDuration() 102 throws InterruptedException { 103 ExerciseSessionRecord session = 104 new ExerciseSessionRecord.Builder( 105 TestUtils.generateMetadata(), 106 SESSION_START_TIME.minusSeconds(10), 107 SESSION_END_TIME, 108 ExerciseSessionType 109 .EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING) 110 .build(); 111 AggregateRecordsResponse<Long> response = 112 TestUtils.getAggregateResponse(mAggregateInSmallWindow, List.of(session)); 113 114 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isNotNull(); 115 assertThat(response.get(EXERCISE_DURATION_TOTAL)) 116 .isEqualTo(SESSION_END_TIME.toEpochMilli() - SESSION_START_TIME.toEpochMilli()); 117 assertThat(response.getZoneOffset(EXERCISE_DURATION_TOTAL)) 118 .isEqualTo(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())); 119 } 120 121 @Test testSimpleAggregation_oneSessionBiggerThanWindow_returnsOverlapDuration()122 public void testSimpleAggregation_oneSessionBiggerThanWindow_returnsOverlapDuration() 123 throws InterruptedException { 124 ExerciseSessionRecord session = 125 new ExerciseSessionRecord.Builder( 126 TestUtils.generateMetadata(), 127 SESSION_START_TIME.minusSeconds(100), 128 SESSION_END_TIME.plusSeconds(100), 129 ExerciseSessionType 130 .EXERCISE_SESSION_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING) 131 .build(); 132 AggregateRecordsResponse<Long> response = 133 TestUtils.getAggregateResponse(mAggregateInSmallWindow, List.of(session)); 134 135 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isNotNull(); 136 assertThat(response.get(EXERCISE_DURATION_TOTAL)) 137 .isEqualTo(SESSION_END_TIME.toEpochMilli() - SESSION_START_TIME.toEpochMilli()); 138 assertThat(response.getZoneOffset(EXERCISE_DURATION_TOTAL)) 139 .isEqualTo(ZoneOffset.systemDefault().getRules().getOffset(Instant.now())); 140 } 141 142 @Test testSimpleAggregation_oneSessionWithRest_returnsDurationMinusRest()143 public void testSimpleAggregation_oneSessionWithRest_returnsDurationMinusRest() 144 throws InterruptedException { 145 ExerciseSegment restSegment = 146 new ExerciseSegment.Builder( 147 SESSION_START_TIME, 148 SESSION_START_TIME.plusSeconds(100), 149 ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_REST) 150 .build(); 151 ExerciseSessionRecord session = 152 new ExerciseSessionRecord.Builder( 153 TestUtils.generateMetadata(), 154 SESSION_START_TIME, 155 SESSION_END_TIME, 156 ExerciseSessionType.EXERCISE_SESSION_TYPE_CALISTHENICS) 157 .setSegments( 158 List.of( 159 restSegment, 160 new ExerciseSegment.Builder( 161 SESSION_START_TIME.plusSeconds(200), 162 SESSION_START_TIME.plusSeconds(600), 163 ExerciseSegmentType 164 .EXERCISE_SEGMENT_TYPE_BURPEE) 165 .build())) 166 .build(); 167 168 AggregateRecordsResponse<Long> response = 169 TestUtils.getAggregateResponse(mAggregateAllRecordsRequest, List.of(session)); 170 171 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isNotNull(); 172 173 long restDuration = 174 restSegment.getEndTime().toEpochMilli() - restSegment.getStartTime().toEpochMilli(); 175 assertThat(response.get(EXERCISE_DURATION_TOTAL)) 176 .isEqualTo( 177 session.getEndTime().toEpochMilli() 178 - session.getStartTime().toEpochMilli() 179 - restDuration); 180 } 181 182 @Test testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups()183 public void testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups() 184 throws InterruptedException { 185 Instant endTime = SESSION_START_TIME.plus(10, ChronoUnit.HOURS); 186 ExerciseSessionRecord session = 187 new ExerciseSessionRecord.Builder( 188 TestUtils.generateMetadata(), 189 SESSION_START_TIME, 190 endTime, 191 ExerciseSessionType.EXERCISE_SESSION_TYPE_BADMINTON) 192 .build(); 193 TestUtils.insertRecords(List.of(session)); 194 195 List<AggregateRecordsGroupedByDurationResponse<Long>> responses = 196 TestUtils.getAggregateResponseGroupByDuration( 197 new AggregateRecordsRequest.Builder<Long>( 198 new TimeInstantRangeFilter.Builder() 199 .setStartTime(SESSION_START_TIME) 200 .setEndTime(endTime) 201 .build()) 202 .addAggregationType(EXERCISE_DURATION_TOTAL) 203 .build(), 204 Duration.of(1, ChronoUnit.HOURS)); 205 206 assertThat(responses).isNotEmpty(); 207 assertThat(responses.size()).isEqualTo(10); 208 for (AggregateRecordsGroupedByDurationResponse<Long> response : responses) { 209 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isEqualTo(3600000); 210 } 211 } 212 213 @Test testAggregation_oneSessionLocalTimeFilter_findsSessionWithMinOffset()214 public void testAggregation_oneSessionLocalTimeFilter_findsSessionWithMinOffset() 215 throws InterruptedException { 216 Instant endTime = Instant.now(); 217 LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTime, ZoneOffset.UTC); 218 219 long sessionDurationSeconds = 3600; 220 ExerciseSessionRecord session = 221 new ExerciseSessionRecord.Builder( 222 TestUtils.generateMetadata(), 223 endTime.minusSeconds(sessionDurationSeconds), 224 endTime, 225 ExerciseSessionType.EXERCISE_SESSION_TYPE_BADMINTON) 226 .setStartZoneOffset(ZoneOffset.MIN) 227 .setEndZoneOffset(ZoneOffset.MIN) 228 .build(); 229 230 AggregateRecordsResponse<Long> response = 231 TestUtils.getAggregateResponse( 232 new AggregateRecordsRequest.Builder<Long>( 233 new LocalTimeRangeFilter.Builder() 234 .setStartTime(endTimeLocal.minusHours(25)) 235 .setEndTime(endTimeLocal.minusHours(15)) 236 .build()) 237 .addAggregationType(EXERCISE_DURATION_TOTAL) 238 .build(), 239 List.of(session)); 240 241 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isEqualTo(sessionDurationSeconds * 1000); 242 } 243 244 @Test testAggregation_oneSessionLocalTimeFilterExcludeSegment_substractsExcludeInterval()245 public void testAggregation_oneSessionLocalTimeFilterExcludeSegment_substractsExcludeInterval() 246 throws InterruptedException { 247 Instant endTime = SESSION_START_TIME.plus(1, ChronoUnit.HOURS); 248 ExerciseSessionRecord session = 249 new ExerciseSessionRecord.Builder( 250 TestUtils.generateMetadata(), 251 SESSION_START_TIME, 252 endTime, 253 ExerciseSessionType.EXERCISE_SESSION_TYPE_BADMINTON) 254 .setStartZoneOffset(ZoneOffset.MIN) 255 .setEndZoneOffset(ZoneOffset.MIN) 256 .setSegments( 257 List.of( 258 new ExerciseSegment.Builder( 259 SESSION_START_TIME.plusSeconds(10), 260 endTime.minusSeconds(10), 261 ExerciseSegmentType 262 .EXERCISE_SEGMENT_TYPE_PAUSE) 263 .build())) 264 .build(); 265 266 LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTime, ZoneOffset.UTC); 267 AggregateRecordsResponse<Long> response = 268 TestUtils.getAggregateResponse( 269 new AggregateRecordsRequest.Builder<Long>( 270 new LocalTimeRangeFilter.Builder() 271 .setStartTime(endTimeLocal.minusHours(25)) 272 .setEndTime(endTimeLocal.minusHours(15)) 273 .build()) 274 .addAggregationType(EXERCISE_DURATION_TOTAL) 275 .build(), 276 List.of(session)); 277 278 assertThat(response.get(EXERCISE_DURATION_TOTAL)).isEqualTo(20000); 279 } 280 } 281