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.health.connect.aidl; 18 19 import static android.health.connect.Constants.DEFAULT_INT; 20 import static android.health.connect.Constants.DEFAULT_LONG; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.health.connect.AggregateRecordsGroupedByDurationResponse; 25 import android.health.connect.AggregateRecordsGroupedByPeriodResponse; 26 import android.health.connect.AggregateRecordsResponse; 27 import android.health.connect.AggregateResult; 28 import android.health.connect.LocalTimeRangeFilter; 29 import android.health.connect.TimeInstantRangeFilter; 30 import android.health.connect.TimeRangeFilter; 31 import android.health.connect.TimeRangeFilterHelper; 32 import android.health.connect.datatypes.DataOrigin; 33 import android.health.connect.internal.datatypes.utils.AggregationTypeIdMapper; 34 import android.os.Parcel; 35 import android.os.Parcelable; 36 import android.util.ArrayMap; 37 38 import java.time.Duration; 39 import java.time.Instant; 40 import java.time.LocalDateTime; 41 import java.time.Period; 42 import java.time.ZoneOffset; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Objects; 47 import java.util.Set; 48 49 /** @hide */ 50 public class AggregateDataResponseParcel implements Parcelable { 51 public static final Creator<AggregateDataResponseParcel> CREATOR = 52 new Creator<>() { 53 @Override 54 public AggregateDataResponseParcel createFromParcel(Parcel in) { 55 return new AggregateDataResponseParcel(in); 56 } 57 58 @Override 59 public AggregateDataResponseParcel[] newArray(int size) { 60 return new AggregateDataResponseParcel[size]; 61 } 62 }; 63 private final List<AggregateRecordsResponse<?>> mAggregateRecordsResponses; 64 private Duration mDuration; 65 private Period mPeriod; 66 private TimeRangeFilter mTimeRangeFilter; 67 AggregateDataResponseParcel(List<AggregateRecordsResponse<?>> aggregateRecordsResponse)68 public AggregateDataResponseParcel(List<AggregateRecordsResponse<?>> aggregateRecordsResponse) { 69 mAggregateRecordsResponses = aggregateRecordsResponse; 70 } 71 AggregateDataResponseParcel(Parcel in)72 protected AggregateDataResponseParcel(Parcel in) { 73 final int size = in.readInt(); 74 mAggregateRecordsResponses = new ArrayList<>(size); 75 76 for (int i = 0; i < size; i++) { 77 final int mapSize = in.readInt(); 78 Map<Integer, AggregateResult<?>> result = new ArrayMap<>(mapSize); 79 80 for (int mapI = 0; mapI < mapSize; mapI++) { 81 int id = in.readInt(); 82 boolean hasValue = in.readBoolean(); 83 if (hasValue) { 84 result.put( 85 id, 86 AggregationTypeIdMapper.getInstance() 87 .getAggregateResultFor(id, in) 88 .setZoneOffset(parseZoneOffset(in)) 89 .setDataOrigins(in.createStringArrayList())); 90 } else { 91 result.put(id, null); 92 } 93 } 94 95 mAggregateRecordsResponses.add(new AggregateRecordsResponse<>(result)); 96 } 97 98 int period = in.readInt(); 99 if (period != DEFAULT_INT) { 100 mPeriod = Period.ofDays(period); 101 } 102 103 long duration = in.readLong(); 104 if (duration != DEFAULT_LONG) { 105 mDuration = Duration.ofMillis(duration); 106 } 107 108 boolean isLocaltimeFilter = in.readBoolean(); 109 long startTime = in.readLong(); 110 long endTime = in.readLong(); 111 if (startTime != DEFAULT_LONG && endTime != DEFAULT_LONG) { 112 if (isLocaltimeFilter) { 113 mTimeRangeFilter = 114 new LocalTimeRangeFilter.Builder() 115 .setStartTime( 116 TimeRangeFilterHelper.getLocalTimeFromMillis(startTime)) 117 .setEndTime(TimeRangeFilterHelper.getLocalTimeFromMillis(endTime)) 118 .build(); 119 } else { 120 mTimeRangeFilter = 121 new TimeInstantRangeFilter.Builder() 122 .setStartTime(Instant.ofEpochMilli(startTime)) 123 .setEndTime(Instant.ofEpochMilli(endTime)) 124 .build(); 125 } 126 } 127 } 128 setDuration( @ullable Duration duration, @Nullable TimeRangeFilter timeRangeFilter)129 public AggregateDataResponseParcel setDuration( 130 @Nullable Duration duration, @Nullable TimeRangeFilter timeRangeFilter) { 131 mDuration = duration; 132 mTimeRangeFilter = timeRangeFilter; 133 134 return this; 135 } 136 setPeriod( @ullable Period period, @Nullable TimeRangeFilter timeRangeFilter)137 public AggregateDataResponseParcel setPeriod( 138 @Nullable Period period, @Nullable TimeRangeFilter timeRangeFilter) { 139 mPeriod = period; 140 mTimeRangeFilter = timeRangeFilter; 141 142 return this; 143 } 144 145 /** 146 * @return the first response from {@code mAggregateRecordsResponses} 147 */ getAggregateDataResponse()148 public AggregateRecordsResponse<?> getAggregateDataResponse() { 149 return mAggregateRecordsResponses.get(0); 150 } 151 152 /** 153 * @return responses from {@code mAggregateRecordsResponses} grouped as per the {@code 154 * mDuration} 155 */ 156 public List<AggregateRecordsGroupedByDurationResponse<?>> getAggregateDataResponseGroupedByDuration()157 getAggregateDataResponseGroupedByDuration() { 158 Objects.requireNonNull(mDuration); 159 160 List<AggregateRecordsGroupedByDurationResponse<?>> 161 aggregateRecordsGroupedByDurationResponse = new ArrayList<>(); 162 long mStartTime = TimeRangeFilterHelper.getFilterStartTimeMillis(mTimeRangeFilter); 163 long mEndTime = TimeRangeFilterHelper.getFilterEndTimeMillis(mTimeRangeFilter); 164 long mDelta = getDurationDelta(mDuration); 165 for (AggregateRecordsResponse<?> aggregateRecordsResponse : mAggregateRecordsResponses) { 166 aggregateRecordsGroupedByDurationResponse.add( 167 new AggregateRecordsGroupedByDurationResponse<>( 168 getDurationInstant(mStartTime), 169 getDurationInstant(Math.min(mStartTime + mDelta, mEndTime)), 170 aggregateRecordsResponse.getAggregateResults())); 171 mStartTime += mDelta; 172 } 173 174 return aggregateRecordsGroupedByDurationResponse; 175 } 176 177 /** 178 * @return responses from {@code mAggregateRecordsResponses} grouped as per the {@code mPeriod} 179 */ 180 public List<AggregateRecordsGroupedByPeriodResponse<?>> getAggregateDataResponseGroupedByPeriod()181 getAggregateDataResponseGroupedByPeriod() { 182 Objects.requireNonNull(mPeriod); 183 184 List<AggregateRecordsGroupedByPeriodResponse<?>> aggregateRecordsGroupedByPeriodResponses = 185 new ArrayList<>(); 186 187 LocalDateTime groupBoundary = ((LocalTimeRangeFilter) mTimeRangeFilter).getStartTime(); 188 long mDelta = getPeriodDeltaInDays(mPeriod); 189 for (AggregateRecordsResponse<?> aggregateRecordsResponse : mAggregateRecordsResponses) { 190 aggregateRecordsGroupedByPeriodResponses.add( 191 new AggregateRecordsGroupedByPeriodResponse<>( 192 groupBoundary, 193 groupBoundary.plusDays(mDelta), 194 aggregateRecordsResponse.getAggregateResults())); 195 groupBoundary = groupBoundary.plusDays(mDelta); 196 } 197 198 if (!aggregateRecordsGroupedByPeriodResponses.isEmpty()) { 199 aggregateRecordsGroupedByPeriodResponses 200 .get(aggregateRecordsGroupedByPeriodResponses.size() - 1) 201 .setEndTime(getPeriodEndLocalDateTime(mTimeRangeFilter)); 202 } 203 204 return aggregateRecordsGroupedByPeriodResponses; 205 } 206 207 @Override describeContents()208 public int describeContents() { 209 return 0; 210 } 211 212 @Override writeToParcel(@onNull Parcel dest, int flags)213 public void writeToParcel(@NonNull Parcel dest, int flags) { 214 dest.writeInt(mAggregateRecordsResponses.size()); 215 for (AggregateRecordsResponse<?> aggregateRecordsResponse : mAggregateRecordsResponses) { 216 dest.writeInt(aggregateRecordsResponse.getAggregateResults().size()); 217 aggregateRecordsResponse 218 .getAggregateResults() 219 .forEach( 220 (key, val) -> { 221 dest.writeInt(key); 222 // to represent if the value is present or not 223 dest.writeBoolean(val != null); 224 if (val != null) { 225 val.putToParcel(dest); 226 ZoneOffset zoneOffset = val.getZoneOffset(); 227 if (zoneOffset != null) { 228 dest.writeInt(val.getZoneOffset().getTotalSeconds()); 229 } else { 230 dest.writeInt(DEFAULT_INT); 231 } 232 Set<DataOrigin> dataOrigins = val.getDataOrigins(); 233 List<String> packageNames = new ArrayList<>(); 234 for (DataOrigin dataOrigin : dataOrigins) { 235 packageNames.add(dataOrigin.getPackageName()); 236 } 237 dest.writeStringList(packageNames); 238 } 239 }); 240 } 241 242 if (mPeriod != null) { 243 dest.writeInt(mPeriod.getDays()); 244 } else { 245 dest.writeInt(DEFAULT_INT); 246 } 247 248 if (mDuration != null) { 249 dest.writeLong(mDuration.toMillis()); 250 } else { 251 dest.writeLong(DEFAULT_LONG); 252 } 253 254 if (mTimeRangeFilter != null) { 255 dest.writeBoolean(TimeRangeFilterHelper.isLocalTimeFilter(mTimeRangeFilter)); 256 dest.writeLong(TimeRangeFilterHelper.getFilterStartTimeMillis(mTimeRangeFilter)); 257 dest.writeLong(TimeRangeFilterHelper.getFilterEndTimeMillis(mTimeRangeFilter)); 258 } else { 259 dest.writeBoolean(false); 260 dest.writeLong(DEFAULT_LONG); 261 dest.writeLong(DEFAULT_LONG); 262 } 263 } 264 parseZoneOffset(Parcel in)265 private ZoneOffset parseZoneOffset(Parcel in) { 266 int zoneOffsetInSecs = in.readInt(); 267 ZoneOffset zoneOffset = null; 268 if (zoneOffsetInSecs != DEFAULT_INT) { 269 zoneOffset = ZoneOffset.ofTotalSeconds(zoneOffsetInSecs); 270 } 271 272 return zoneOffset; 273 } 274 getPeriodEndLocalDateTime(TimeRangeFilter timeRangeFilter)275 private LocalDateTime getPeriodEndLocalDateTime(TimeRangeFilter timeRangeFilter) { 276 if (timeRangeFilter instanceof TimeInstantRangeFilter) { 277 return LocalDateTime.ofInstant( 278 ((TimeInstantRangeFilter) timeRangeFilter).getEndTime(), 279 ZoneOffset.systemDefault()); 280 } else if (timeRangeFilter instanceof LocalTimeRangeFilter) { 281 return ((LocalTimeRangeFilter) timeRangeFilter).getEndTime(); 282 } else { 283 throw new IllegalArgumentException( 284 "Invalid time filter object. Object should be either " 285 + "TimeInstantRangeFilter or LocalTimeRangeFilter."); 286 } 287 } 288 getPeriodDeltaInDays(Period period)289 private long getPeriodDeltaInDays(Period period) { 290 return period.getDays(); 291 } 292 getDurationInstant(long duration)293 private Instant getDurationInstant(long duration) { 294 return Instant.ofEpochMilli(duration); 295 } 296 getDurationDelta(Duration duration)297 private long getDurationDelta(Duration duration) { 298 return duration.toMillis(); 299 } 300 } 301