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 com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.END_TIME_COLUMN_NAME; 20 import static com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.LOCAL_DATE_TIME_END_TIME_COLUMN_NAME; 21 import static com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.LOCAL_DATE_TIME_START_TIME_COLUMN_NAME; 22 import static com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.START_TIME_COLUMN_NAME; 23 import static com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.START_ZONE_OFFSET_COLUMN_NAME; 24 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.APP_INFO_ID_COLUMN_NAME; 25 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.LAST_MODIFIED_TIME_COLUMN_NAME; 26 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.UUID_COLUMN_NAME; 27 28 import android.database.Cursor; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.server.healthconnect.storage.utils.StorageUtils; 32 33 import java.time.ZoneOffset; 34 import java.util.Map; 35 import java.util.UUID; 36 37 /** 38 * Represents priority aggregation data. 39 * 40 * @hide 41 */ 42 public abstract class AggregationRecordData implements Comparable<AggregationRecordData> { 43 private long mRecordStartTime; 44 private long mRecordEndTime; 45 private int mPriority; 46 private long mLastModifiedTime; 47 private ZoneOffset mStartTimeZoneOffset; 48 getStartTime()49 long getStartTime() { 50 return mRecordStartTime; 51 } 52 getEndTime()53 long getEndTime() { 54 return mRecordEndTime; 55 } 56 getPriority()57 int getPriority() { 58 return mPriority; 59 } 60 getLastModifiedTime()61 long getLastModifiedTime() { 62 return mLastModifiedTime; 63 } 64 getStartTimeZoneOffset()65 ZoneOffset getStartTimeZoneOffset() { 66 return mStartTimeZoneOffset; 67 } 68 readUuid(Cursor cursor)69 protected UUID readUuid(Cursor cursor) { 70 return StorageUtils.getCursorUUID(cursor, UUID_COLUMN_NAME); 71 } 72 populateAggregationData( Cursor cursor, boolean useLocalTime, Map<Long, Integer> appIdToPriority)73 void populateAggregationData( 74 Cursor cursor, boolean useLocalTime, Map<Long, Integer> appIdToPriority) { 75 mRecordStartTime = 76 StorageUtils.getCursorLong( 77 cursor, 78 useLocalTime 79 ? LOCAL_DATE_TIME_START_TIME_COLUMN_NAME 80 : START_TIME_COLUMN_NAME); 81 mRecordEndTime = 82 StorageUtils.getCursorLong( 83 cursor, 84 useLocalTime ? LOCAL_DATE_TIME_END_TIME_COLUMN_NAME : END_TIME_COLUMN_NAME); 85 mLastModifiedTime = StorageUtils.getCursorLong(cursor, LAST_MODIFIED_TIME_COLUMN_NAME); 86 mStartTimeZoneOffset = StorageUtils.getZoneOffset(cursor, START_ZONE_OFFSET_COLUMN_NAME); 87 mPriority = 88 appIdToPriority.getOrDefault( 89 StorageUtils.getCursorLong(cursor, APP_INFO_ID_COLUMN_NAME), 90 Integer.MIN_VALUE); 91 populateSpecificAggregationData(cursor, useLocalTime); 92 } 93 getStartTimestamp()94 AggregationTimestamp getStartTimestamp() { 95 return new AggregationTimestamp(AggregationTimestamp.INTERVAL_START, getStartTime()) 96 .setParentData(this); 97 } 98 getEndTimestamp()99 AggregationTimestamp getEndTimestamp() { 100 return new AggregationTimestamp(AggregationTimestamp.INTERVAL_END, getEndTime()) 101 .setParentData(this); 102 } 103 104 @VisibleForTesting setData( long startTime, long endTime, int priority, long lastModifiedTime)105 AggregationRecordData setData( 106 long startTime, long endTime, int priority, long lastModifiedTime) { 107 mRecordStartTime = startTime; 108 mRecordEndTime = endTime; 109 mPriority = priority; 110 mLastModifiedTime = lastModifiedTime; 111 return this; 112 } 113 114 /** 115 * Calculates aggregation result given start and end time of the target interval. Implementation 116 * may assume that it's will be called with non overlapping intervals. So (start time, end time) 117 * input intervals of all calls will not overlap. 118 */ getResultOnInterval(long startTime, long endTime)119 abstract double getResultOnInterval(long startTime, long endTime); 120 populateSpecificAggregationData(Cursor cursor, boolean useLocalTime)121 abstract void populateSpecificAggregationData(Cursor cursor, boolean useLocalTime); 122 123 @Override toString()124 public String toString() { 125 return "AggregData{startTime=" + mRecordStartTime + ", endTime=" + mRecordEndTime + "}"; 126 } 127 128 /** Calculates overlap between two intervals */ calculateIntervalOverlapDuration( long intervalStart1, long intervalStart2, long intervalEnd1, long intervalEnd2)129 static long calculateIntervalOverlapDuration( 130 long intervalStart1, long intervalStart2, long intervalEnd1, long intervalEnd2) { 131 return Math.max( 132 Math.min(intervalEnd1, intervalEnd2) - Math.max(intervalStart1, intervalStart2), 0); 133 } 134 135 @Override compareTo(AggregationRecordData o)136 public int compareTo(AggregationRecordData o) { 137 if (this.equals(o)) { 138 return 0; 139 } 140 141 if (mPriority != o.getPriority()) { 142 return Integer.compare(mPriority, o.getPriority()); 143 } 144 145 // The later the last modified time, the higher priority this record has. 146 if (getLastModifiedTime() != o.getLastModifiedTime()) { 147 return Long.compare(getLastModifiedTime(), o.getLastModifiedTime()); 148 } 149 150 if (getStartTime() != o.getStartTime()) { 151 return Long.compare(getStartTime(), o.getStartTime()); 152 } 153 154 if (getEndTime() != o.getEndTime()) { 155 return Long.compare(getEndTime(), o.getEndTime()); 156 } 157 158 return Double.compare( 159 getResultOnInterval(getStartTime(), getEndTime()), 160 o.getResultOnInterval(o.getStartTime(), o.getEndTime())); 161 } 162 } 163