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 com.android.server.healthconnect.storage.datatypehelpers.aggregation; 18 19 import static android.health.connect.datatypes.ActivityIntensityRecord.ACTIVITY_INTENSITY_TYPE_MODERATE; 20 import static android.health.connect.datatypes.ActivityIntensityRecord.ACTIVITY_INTENSITY_TYPE_VIGOROUS; 21 import static android.health.connect.datatypes.AggregationType.AggregationTypeIdentifier.ACTIVITY_INTENSITY_DURATION_TOTAL; 22 import static android.health.connect.datatypes.AggregationType.AggregationTypeIdentifier.ACTIVITY_INTENSITY_MINUTES_TOTAL; 23 import static android.health.connect.datatypes.AggregationType.AggregationTypeIdentifier.ACTIVITY_INTENSITY_MODERATE_DURATION_TOTAL; 24 import static android.health.connect.datatypes.AggregationType.AggregationTypeIdentifier.ACTIVITY_INTENSITY_VIGOROUS_DURATION_TOTAL; 25 26 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt; 27 28 import android.database.Cursor; 29 import android.health.connect.datatypes.ActivityIntensityRecord; 30 import android.health.connect.datatypes.AggregationType; 31 32 import com.android.server.healthconnect.storage.datatypehelpers.ActivityIntensityRecordHelper; 33 34 import java.util.concurrent.TimeUnit; 35 36 /** 37 * Helper class to aggregate {@link ActivityIntensityRecord} data. 38 * 39 * <p>Calculates the duration of the overlap between the underlying {@link ActivityIntensityRecord} 40 * and the requested window, and applies the multiplier depending on intensity type of the record. 41 * 42 * @hide 43 */ 44 final class ActivityIntensityAggregationData extends AggregationRecordData { 45 46 private static final long MILLIS_IN_A_MINUTE = TimeUnit.MINUTES.toMillis(1); 47 48 @AggregationType.AggregationTypeIdentifier private final int mAggregationType; 49 @ActivityIntensityRecord.ActivityIntensityType private int mActivityIntensityType; 50 ActivityIntensityAggregationData( @ggregationType.AggregationTypeIdentifier int aggregationType)51 ActivityIntensityAggregationData( 52 @AggregationType.AggregationTypeIdentifier int aggregationType) { 53 mAggregationType = aggregationType; 54 } 55 56 @Override getResultOnInterval(AggregationTimestamp windowStart, AggregationTimestamp windowEnd)57 double getResultOnInterval(AggregationTimestamp windowStart, AggregationTimestamp windowEnd) { 58 double overlapDurationMillis = 59 calculateIntervalOverlapDuration( 60 getStartTime(), windowStart.getTime(), getEndTime(), windowEnd.getTime()); 61 62 // Zero multiplier is used for cases when the intensity type of the underlying record is 63 // different to the intensity type being aggregated, in which case the record duration must 64 // not be included in the aggregation. 65 int multiplier = 66 switch (mAggregationType) { 67 case ACTIVITY_INTENSITY_MODERATE_DURATION_TOTAL -> 68 mActivityIntensityType == ACTIVITY_INTENSITY_TYPE_MODERATE ? 1 : 0; 69 case ACTIVITY_INTENSITY_VIGOROUS_DURATION_TOTAL -> 70 mActivityIntensityType == ACTIVITY_INTENSITY_TYPE_VIGOROUS ? 1 : 0; 71 case ACTIVITY_INTENSITY_DURATION_TOTAL -> 1; 72 case ACTIVITY_INTENSITY_MINUTES_TOTAL -> 73 mActivityIntensityType == ACTIVITY_INTENSITY_TYPE_MODERATE ? 1 : 2; 74 default -> 75 throw new IllegalStateException( 76 "Unsupported aggregation type: " + mAggregationType); 77 }; 78 79 // TODO(b/373585917): round the resulting INTENSITY_MINUTES to the nearest minute instead 80 // of rounding down to avoid floating point inaccuracy. 81 return overlapDurationMillis 82 * multiplier 83 / (mAggregationType == ACTIVITY_INTENSITY_MINUTES_TOTAL ? MILLIS_IN_A_MINUTE : 1); 84 } 85 86 @Override populateSpecificAggregationData(Cursor cursor, boolean useLocalTime)87 void populateSpecificAggregationData(Cursor cursor, boolean useLocalTime) { 88 mActivityIntensityType = 89 getCursorInt(cursor, ActivityIntensityRecordHelper.TYPE_COLUMN_NAME); 90 } 91 } 92