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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.health.connect.datatypes.AggregationType;
22 import android.health.connect.datatypes.DataOrigin;
23 import android.health.connect.internal.datatypes.utils.AggregationTypeIdMapper;
24 import android.util.ArrayMap;
25 
26 import java.time.ZoneOffset;
27 import java.util.Collections;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
31 
32 /** A class representing response for {@link HealthConnectManager#aggregate} */
33 public final class AggregateRecordsResponse<T> {
34     private final Map<AggregationType<T>, AggregateResult<T>> mAggregateResults;
35 
36     /**
37      * We only support querying and fetching same type of aggregations, so we can cast blindly
38      *
39      * @hide
40      */
41     @SuppressWarnings("unchecked")
AggregateRecordsResponse(@onNull Map<Integer, AggregateResult<?>> aggregateResults)42     public AggregateRecordsResponse(@NonNull Map<Integer, AggregateResult<?>> aggregateResults) {
43         Objects.requireNonNull(aggregateResults);
44 
45         mAggregateResults = new ArrayMap<>(aggregateResults.size());
46         aggregateResults.forEach(
47                 (key, value) ->
48                         mAggregateResults.put(
49                                 (AggregationType<T>)
50                                         AggregationTypeIdMapper.getInstance()
51                                                 .getAggregationTypeFor(key),
52                                 (AggregateResult<T>) value));
53     }
54 
55     /** @hide */
56     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
getZoneOffsetInternal( @onNull AggregationType<U> aggregationType, Map<AggregationType<U>, AggregateResult<U>> mAggregateResults)57     public static <U> ZoneOffset getZoneOffsetInternal(
58             @NonNull AggregationType<U> aggregationType,
59             Map<AggregationType<U>, AggregateResult<U>> mAggregateResults) {
60         Objects.requireNonNull(aggregationType);
61 
62         AggregateResult<U> result = mAggregateResults.get(aggregationType);
63 
64         if (result == null) {
65             return null;
66         }
67 
68         return result.getZoneOffset();
69     }
70 
71     /** @hide */
getDataOriginsInternal( @onNull AggregationType<U> aggregationType, Map<AggregationType<U>, AggregateResult<U>> mAggregateResults)72     public static <U> Set<DataOrigin> getDataOriginsInternal(
73             @NonNull AggregationType<U> aggregationType,
74             Map<AggregationType<U>, AggregateResult<U>> mAggregateResults) {
75         Objects.requireNonNull(aggregationType);
76 
77         AggregateResult<U> result = mAggregateResults.get(aggregationType);
78 
79         if (result == null) {
80             return Collections.emptySet();
81         }
82 
83         return result.getDataOrigins();
84     }
85 
86     /** @hide */
87     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
getInternal( @onNull AggregationType<U> aggregationType, Map<AggregationType<U>, AggregateResult<U>> mAggregateResults)88     public static <U> U getInternal(
89             @NonNull AggregationType<U> aggregationType,
90             Map<AggregationType<U>, AggregateResult<U>> mAggregateResults) {
91         Objects.requireNonNull(aggregationType);
92 
93         AggregateResult<U> result = mAggregateResults.get(aggregationType);
94 
95         if (result == null) {
96             return null;
97         }
98 
99         return result.getResult();
100     }
101 
102     /**
103      * @return a map of {@link AggregationType} -> {@link AggregateResult}
104      * @hide
105      */
106     @NonNull
getAggregateResults()107     public Map<Integer, AggregateResult<?>> getAggregateResults() {
108         Map<Integer, AggregateResult<?>> aggregateResultMap = new ArrayMap<>();
109         mAggregateResults.forEach(
110                 (key, value) -> {
111                     aggregateResultMap.put(
112                             AggregationTypeIdMapper.getInstance().getIdFor(key), value);
113                 });
114         return aggregateResultMap;
115     }
116 
117     /**
118      * @return an aggregation result for {@code aggregationType}. *
119      * @param aggregationType {@link AggregationType} for which to get the result
120      */
121     @Nullable
get(@onNull AggregationType<T> aggregationType)122     public T get(@NonNull AggregationType<T> aggregationType) {
123         return getInternal(aggregationType, mAggregateResults);
124     }
125 
126     /**
127      * @return {@link ZoneOffset} for the underlying aggregation record, null if the corresponding
128      *     aggregation doesn't exist and or if multiple records were present.
129      */
130     @Nullable
getZoneOffset(@onNull AggregationType<T> aggregationType)131     public ZoneOffset getZoneOffset(@NonNull AggregationType<T> aggregationType) {
132         return getZoneOffsetInternal(aggregationType, mAggregateResults);
133     }
134 
135     /**
136      * Returns {@link ZoneOffset} of the first {@link AggregationType}.
137      *
138      * @hide
139      */
140     @Nullable
getFirstZoneOffset()141     public ZoneOffset getFirstZoneOffset() {
142         AggregationType<T> firstAggregationType = getFirstAggregationType();
143         return firstAggregationType != null ? getZoneOffset(firstAggregationType) : null;
144     }
145 
146     @Nullable
getFirstAggregationType()147     private AggregationType<T> getFirstAggregationType() {
148         return mAggregateResults.keySet().stream().findFirst().orElse(null);
149     }
150 
151     /**
152      * Returns a set of {@link DataOrigin}s for the underlying aggregation record, empty set if the
153      * corresponding aggregation doesn't exist and or if multiple records were present.
154      */
155     @NonNull
getDataOrigins(@onNull AggregationType<T> aggregationType)156     public Set<DataOrigin> getDataOrigins(@NonNull AggregationType<T> aggregationType) {
157         return getDataOriginsInternal(aggregationType, mAggregateResults);
158     }
159 }
160