• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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