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