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.internal.datatypes.utils;
18 
19 import static android.health.connect.datatypes.ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL;
20 import static android.health.connect.datatypes.BasalMetabolicRateRecord.BASAL_CALORIES_TOTAL;
21 import static android.health.connect.datatypes.BloodPressureRecord.DIASTOLIC_AVG;
22 import static android.health.connect.datatypes.BloodPressureRecord.DIASTOLIC_MAX;
23 import static android.health.connect.datatypes.BloodPressureRecord.DIASTOLIC_MIN;
24 import static android.health.connect.datatypes.BloodPressureRecord.SYSTOLIC_AVG;
25 import static android.health.connect.datatypes.BloodPressureRecord.SYSTOLIC_MAX;
26 import static android.health.connect.datatypes.BloodPressureRecord.SYSTOLIC_MIN;
27 import static android.health.connect.datatypes.CyclingPedalingCadenceRecord.RPM_AVG;
28 import static android.health.connect.datatypes.CyclingPedalingCadenceRecord.RPM_MAX;
29 import static android.health.connect.datatypes.CyclingPedalingCadenceRecord.RPM_MIN;
30 import static android.health.connect.datatypes.DistanceRecord.DISTANCE_TOTAL;
31 import static android.health.connect.datatypes.ElevationGainedRecord.ELEVATION_GAINED_TOTAL;
32 import static android.health.connect.datatypes.ExerciseSessionRecord.EXERCISE_DURATION_TOTAL;
33 import static android.health.connect.datatypes.FloorsClimbedRecord.FLOORS_CLIMBED_TOTAL;
34 import static android.health.connect.datatypes.HeartRateRecord.BPM_AVG;
35 import static android.health.connect.datatypes.HeartRateRecord.BPM_MAX;
36 import static android.health.connect.datatypes.HeartRateRecord.BPM_MIN;
37 import static android.health.connect.datatypes.HeartRateRecord.HEART_MEASUREMENTS_COUNT;
38 import static android.health.connect.datatypes.HeightRecord.HEIGHT_AVG;
39 import static android.health.connect.datatypes.HeightRecord.HEIGHT_MAX;
40 import static android.health.connect.datatypes.HeightRecord.HEIGHT_MIN;
41 import static android.health.connect.datatypes.HydrationRecord.VOLUME_TOTAL;
42 import static android.health.connect.datatypes.NutritionRecord.BIOTIN_TOTAL;
43 import static android.health.connect.datatypes.NutritionRecord.CAFFEINE_TOTAL;
44 import static android.health.connect.datatypes.NutritionRecord.CALCIUM_TOTAL;
45 import static android.health.connect.datatypes.NutritionRecord.CHLORIDE_TOTAL;
46 import static android.health.connect.datatypes.NutritionRecord.CHOLESTEROL_TOTAL;
47 import static android.health.connect.datatypes.NutritionRecord.CHROMIUM_TOTAL;
48 import static android.health.connect.datatypes.NutritionRecord.COPPER_TOTAL;
49 import static android.health.connect.datatypes.NutritionRecord.DIETARY_FIBER_TOTAL;
50 import static android.health.connect.datatypes.NutritionRecord.ENERGY_FROM_FAT_TOTAL;
51 import static android.health.connect.datatypes.NutritionRecord.ENERGY_TOTAL;
52 import static android.health.connect.datatypes.NutritionRecord.FOLATE_TOTAL;
53 import static android.health.connect.datatypes.NutritionRecord.FOLIC_ACID_TOTAL;
54 import static android.health.connect.datatypes.NutritionRecord.IODINE_TOTAL;
55 import static android.health.connect.datatypes.NutritionRecord.IRON_TOTAL;
56 import static android.health.connect.datatypes.NutritionRecord.MAGNESIUM_TOTAL;
57 import static android.health.connect.datatypes.NutritionRecord.MANGANESE_TOTAL;
58 import static android.health.connect.datatypes.NutritionRecord.MOLYBDENUM_TOTAL;
59 import static android.health.connect.datatypes.NutritionRecord.MONOUNSATURATED_FAT_TOTAL;
60 import static android.health.connect.datatypes.NutritionRecord.NIACIN_TOTAL;
61 import static android.health.connect.datatypes.NutritionRecord.PANTOTHENIC_ACID_TOTAL;
62 import static android.health.connect.datatypes.NutritionRecord.PHOSPHORUS_TOTAL;
63 import static android.health.connect.datatypes.NutritionRecord.POLYUNSATURATED_FAT_TOTAL;
64 import static android.health.connect.datatypes.NutritionRecord.POTASSIUM_TOTAL;
65 import static android.health.connect.datatypes.NutritionRecord.PROTEIN_TOTAL;
66 import static android.health.connect.datatypes.NutritionRecord.RIBOFLAVIN_TOTAL;
67 import static android.health.connect.datatypes.NutritionRecord.SATURATED_FAT_TOTAL;
68 import static android.health.connect.datatypes.NutritionRecord.SELENIUM_TOTAL;
69 import static android.health.connect.datatypes.NutritionRecord.SODIUM_TOTAL;
70 import static android.health.connect.datatypes.NutritionRecord.SUGAR_TOTAL;
71 import static android.health.connect.datatypes.NutritionRecord.THIAMIN_TOTAL;
72 import static android.health.connect.datatypes.NutritionRecord.TOTAL_CARBOHYDRATE_TOTAL;
73 import static android.health.connect.datatypes.NutritionRecord.TOTAL_FAT_TOTAL;
74 import static android.health.connect.datatypes.NutritionRecord.TRANS_FAT_TOTAL;
75 import static android.health.connect.datatypes.NutritionRecord.UNSATURATED_FAT_TOTAL;
76 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_A_TOTAL;
77 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_B12_TOTAL;
78 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_B6_TOTAL;
79 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_C_TOTAL;
80 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_D_TOTAL;
81 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_E_TOTAL;
82 import static android.health.connect.datatypes.NutritionRecord.VITAMIN_K_TOTAL;
83 import static android.health.connect.datatypes.NutritionRecord.ZINC_TOTAL;
84 import static android.health.connect.datatypes.PowerRecord.POWER_AVG;
85 import static android.health.connect.datatypes.PowerRecord.POWER_MAX;
86 import static android.health.connect.datatypes.PowerRecord.POWER_MIN;
87 import static android.health.connect.datatypes.SkinTemperatureRecord.SKIN_TEMPERATURE_DELTA_AVG;
88 import static android.health.connect.datatypes.SkinTemperatureRecord.SKIN_TEMPERATURE_DELTA_MAX;
89 import static android.health.connect.datatypes.SkinTemperatureRecord.SKIN_TEMPERATURE_DELTA_MIN;
90 import static android.health.connect.datatypes.SleepSessionRecord.SLEEP_DURATION_TOTAL;
91 import static android.health.connect.datatypes.SpeedRecord.SPEED_AVG;
92 import static android.health.connect.datatypes.SpeedRecord.SPEED_MAX;
93 import static android.health.connect.datatypes.SpeedRecord.SPEED_MIN;
94 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_AVG;
95 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_MAX;
96 import static android.health.connect.datatypes.StepsCadenceRecord.STEPS_CADENCE_RATE_MIN;
97 import static android.health.connect.datatypes.StepsRecord.STEPS_COUNT_TOTAL;
98 import static android.health.connect.datatypes.WeightRecord.WEIGHT_AVG;
99 import static android.health.connect.datatypes.WeightRecord.WEIGHT_MAX;
100 import static android.health.connect.datatypes.WeightRecord.WEIGHT_MIN;
101 import static android.health.connect.datatypes.WheelchairPushesRecord.WHEEL_CHAIR_PUSHES_COUNT_TOTAL;
102 
103 import android.annotation.NonNull;
104 import android.health.connect.AggregateResult;
105 import android.health.connect.datatypes.AggregationType;
106 import android.health.connect.datatypes.RestingHeartRateRecord;
107 import android.health.connect.datatypes.TotalCaloriesBurnedRecord;
108 import android.health.connect.datatypes.units.Energy;
109 import android.health.connect.datatypes.units.Length;
110 import android.health.connect.datatypes.units.Mass;
111 import android.health.connect.datatypes.units.Power;
112 import android.health.connect.datatypes.units.Pressure;
113 import android.health.connect.datatypes.units.TemperatureDelta;
114 import android.health.connect.datatypes.units.Velocity;
115 import android.health.connect.datatypes.units.Volume;
116 import android.os.Parcel;
117 
118 import java.util.Arrays;
119 import java.util.HashMap;
120 import java.util.List;
121 import java.util.Map;
122 
123 /**
124  * Creates and maintains a map of {@link AggregationType.AggregationTypeIdentifier} to {@link
125  * AggregationType} and its result creator {@link AggregationResultCreator}
126  *
127  * @hide
128  */
129 public final class AggregationTypeIdMapper {
130     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
131     private static volatile AggregationTypeIdMapper sAggregationTypeIdMapper;
132 
133     private final Map<Integer, AggregationResultCreator> mIdToAggregateResult;
134     private final Map<Integer, AggregationType<?>> mIdDataAggregationTypeMap;
135     private final Map<AggregationType<?>, Integer> mDataAggregationTypeIdMap;
136 
AggregationTypeIdMapper()137     private AggregationTypeIdMapper() {
138         mIdToAggregateResult = new HashMap<>();
139         mIdDataAggregationTypeMap = new HashMap<>();
140         mDataAggregationTypeIdMap = new HashMap<>();
141 
142         addLongIdsToAggregateResultMap(
143                 Arrays.asList(
144                         BPM_MAX,
145                         BPM_MIN,
146                         STEPS_COUNT_TOTAL,
147                         BPM_AVG,
148                         RestingHeartRateRecord.BPM_MAX,
149                         RestingHeartRateRecord.BPM_MIN,
150                         RestingHeartRateRecord.BPM_AVG,
151                         WHEEL_CHAIR_PUSHES_COUNT_TOTAL,
152                         HEART_MEASUREMENTS_COUNT,
153                         SLEEP_DURATION_TOTAL,
154                         EXERCISE_DURATION_TOTAL));
155         addDoubleIdsToAggregateResultMap(
156                 Arrays.asList(
157                         FLOORS_CLIMBED_TOTAL,
158                         RPM_AVG,
159                         RPM_MAX,
160                         RPM_MIN,
161                         STEPS_CADENCE_RATE_MAX,
162                         STEPS_CADENCE_RATE_AVG,
163                         STEPS_CADENCE_RATE_MIN));
164         addPowerIdsToAggregateResultMap(Arrays.asList(POWER_MIN, POWER_MAX, POWER_AVG));
165         addTemperatureDeltaIdsToAggregateResultMap(
166                 Arrays.asList(
167                         SKIN_TEMPERATURE_DELTA_AVG,
168                         SKIN_TEMPERATURE_DELTA_MIN,
169                         SKIN_TEMPERATURE_DELTA_MAX));
170         addEnergyIdsToAggregateResultMap(
171                 Arrays.asList(
172                         ACTIVE_CALORIES_TOTAL,
173                         BASAL_CALORIES_TOTAL,
174                         ENERGY_TOTAL,
175                         ENERGY_FROM_FAT_TOTAL,
176                         TotalCaloriesBurnedRecord.ENERGY_TOTAL));
177         addVolumeIdsToAggregateResultMap(Arrays.asList(VOLUME_TOTAL));
178         addLengthIdsToAggregateResultMap(
179                 Arrays.asList(
180                         DISTANCE_TOTAL,
181                         ELEVATION_GAINED_TOTAL,
182                         HEIGHT_AVG,
183                         HEIGHT_MAX,
184                         HEIGHT_MIN));
185         addMassIdsToAggregateResultMap(
186                 Arrays.asList(
187                         BIOTIN_TOTAL,
188                         CAFFEINE_TOTAL,
189                         CALCIUM_TOTAL,
190                         CHLORIDE_TOTAL,
191                         CHOLESTEROL_TOTAL,
192                         CHROMIUM_TOTAL,
193                         COPPER_TOTAL,
194                         DIETARY_FIBER_TOTAL,
195                         FOLATE_TOTAL,
196                         FOLIC_ACID_TOTAL,
197                         IODINE_TOTAL,
198                         IRON_TOTAL,
199                         MAGNESIUM_TOTAL,
200                         MANGANESE_TOTAL,
201                         MOLYBDENUM_TOTAL,
202                         MONOUNSATURATED_FAT_TOTAL,
203                         NIACIN_TOTAL,
204                         PANTOTHENIC_ACID_TOTAL,
205                         PHOSPHORUS_TOTAL,
206                         POLYUNSATURATED_FAT_TOTAL,
207                         POTASSIUM_TOTAL,
208                         PROTEIN_TOTAL,
209                         RIBOFLAVIN_TOTAL,
210                         SATURATED_FAT_TOTAL,
211                         SELENIUM_TOTAL,
212                         SODIUM_TOTAL,
213                         SUGAR_TOTAL,
214                         THIAMIN_TOTAL,
215                         TOTAL_CARBOHYDRATE_TOTAL,
216                         TOTAL_FAT_TOTAL,
217                         UNSATURATED_FAT_TOTAL,
218                         VITAMIN_A_TOTAL,
219                         VITAMIN_B12_TOTAL,
220                         VITAMIN_B6_TOTAL,
221                         VITAMIN_C_TOTAL,
222                         VITAMIN_D_TOTAL,
223                         VITAMIN_E_TOTAL,
224                         VITAMIN_K_TOTAL,
225                         ZINC_TOTAL,
226                         WEIGHT_AVG,
227                         WEIGHT_MAX,
228                         WEIGHT_MIN,
229                         TRANS_FAT_TOTAL));
230         addVelocityIdsToAggregateResultMap(Arrays.asList(SPEED_MAX, SPEED_AVG, SPEED_MIN));
231         addPressureIdsToAggregateResultMap(
232                 Arrays.asList(
233                         DIASTOLIC_AVG,
234                         DIASTOLIC_MAX,
235                         DIASTOLIC_MIN,
236                         SYSTOLIC_AVG,
237                         SYSTOLIC_MAX,
238                         SYSTOLIC_MIN));
239     }
240 
241     @NonNull
getInstance()242     public static synchronized AggregationTypeIdMapper getInstance() {
243         if (sAggregationTypeIdMapper == null) {
244             sAggregationTypeIdMapper = new AggregationTypeIdMapper();
245         }
246 
247         return sAggregationTypeIdMapper;
248     }
249 
250     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
251     @NonNull
getAggregateResultFor( @ggregationType.AggregationTypeIdentifier.Id int id, @NonNull Parcel parcel)252     public AggregateResult<?> getAggregateResultFor(
253             @AggregationType.AggregationTypeIdentifier.Id int id, @NonNull Parcel parcel) {
254         return mIdToAggregateResult.get(id).getAggregateResult(parcel);
255     }
256 
257     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
258     @NonNull
getAggregationTypeFor( @ggregationType.AggregationTypeIdentifier.Id int id)259     public AggregationType<?> getAggregationTypeFor(
260             @AggregationType.AggregationTypeIdentifier.Id int id) {
261         return mIdDataAggregationTypeMap.get(id);
262     }
263 
264     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
265     @NonNull
266     @AggregationType.AggregationTypeIdentifier.Id
getIdFor(AggregationType<?> aggregationType)267     public int getIdFor(AggregationType<?> aggregationType) {
268         return mDataAggregationTypeIdMap.get(aggregationType);
269     }
270 
271     @NonNull
getLongResult(long result)272     private AggregateResult<Long> getLongResult(long result) {
273         return new AggregateResult<>(result);
274     }
275 
276     @NonNull
getDoubleResult(double result)277     private AggregateResult<Double> getDoubleResult(double result) {
278         return new AggregateResult<>(result);
279     }
280 
281     @NonNull
getEnergyResult(double result)282     private AggregateResult<Energy> getEnergyResult(double result) {
283         return new AggregateResult<>(Energy.fromCalories(result));
284     }
285 
286     @NonNull
getPowerResult(double result)287     private AggregateResult<Power> getPowerResult(double result) {
288         return new AggregateResult<>(Power.fromWatts(result));
289     }
290 
291     @NonNull
getTemperatureDeltaResult(double result)292     private AggregateResult<TemperatureDelta> getTemperatureDeltaResult(double result) {
293         return new AggregateResult<>(TemperatureDelta.fromCelsius(result));
294     }
295 
296     @NonNull
getPressureResult(double result)297     private AggregateResult<Pressure> getPressureResult(double result) {
298         return new AggregateResult<>(Pressure.fromMillimetersOfMercury(result));
299     }
300 
301     @NonNull
getLengthResult(double result)302     private AggregateResult<Length> getLengthResult(double result) {
303         return new AggregateResult<>(Length.fromMeters(result));
304     }
305 
306     @NonNull
getVolumeResult(double result)307     private AggregateResult<Volume> getVolumeResult(double result) {
308         return new AggregateResult<>(Volume.fromLiters(result));
309     }
310 
311     @NonNull
getMassResult(double result)312     private AggregateResult<Mass> getMassResult(double result) {
313         return new AggregateResult<>(Mass.fromGrams(result));
314     }
315 
316     @NonNull
getVelocityResult(double result)317     private AggregateResult<Velocity> getVelocityResult(double result) {
318         return new AggregateResult<>(Velocity.fromMetersPerSecond(result));
319     }
320 
addLongIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)321     private void addLongIdsToAggregateResultMap(
322             @NonNull List<AggregationType<?>> aggregationTypeList) {
323         for (AggregationType<?> aggregationType : aggregationTypeList) {
324             mIdToAggregateResult.put(
325                     aggregationType.getAggregationTypeIdentifier(),
326                     result -> getLongResult(result.readLong()));
327             populateIdDataAggregationType(aggregationType);
328         }
329     }
330 
addDoubleIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)331     private void addDoubleIdsToAggregateResultMap(
332             @NonNull List<AggregationType<?>> aggregationTypeList) {
333         for (AggregationType<?> aggregationType : aggregationTypeList) {
334             mIdToAggregateResult.put(
335                     aggregationType.getAggregationTypeIdentifier(),
336                     result -> getDoubleResult(result.readDouble()));
337             populateIdDataAggregationType(aggregationType);
338         }
339     }
340 
addEnergyIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)341     private void addEnergyIdsToAggregateResultMap(
342             @NonNull List<AggregationType<?>> aggregationTypeList) {
343         for (AggregationType<?> aggregationType : aggregationTypeList) {
344             mIdToAggregateResult.put(
345                     aggregationType.getAggregationTypeIdentifier(),
346                     result -> getEnergyResult(result.readDouble()));
347             populateIdDataAggregationType(aggregationType);
348         }
349     }
350 
addPowerIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)351     private void addPowerIdsToAggregateResultMap(
352             @NonNull List<AggregationType<?>> aggregationTypeList) {
353         for (AggregationType<?> aggregationType : aggregationTypeList) {
354             mIdToAggregateResult.put(
355                     aggregationType.getAggregationTypeIdentifier(),
356                     result -> getPowerResult(result.readDouble()));
357             populateIdDataAggregationType(aggregationType);
358         }
359     }
360 
addTemperatureDeltaIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)361     private void addTemperatureDeltaIdsToAggregateResultMap(
362             @NonNull List<AggregationType<?>> aggregationTypeList) {
363         for (AggregationType<?> aggregationType : aggregationTypeList) {
364             mIdToAggregateResult.put(
365                     aggregationType.getAggregationTypeIdentifier(),
366                     result -> getTemperatureDeltaResult(result.readDouble()));
367             populateIdDataAggregationType(aggregationType);
368         }
369     }
370 
addPressureIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)371     private void addPressureIdsToAggregateResultMap(
372             @NonNull List<AggregationType<?>> aggregationTypeList) {
373         for (AggregationType<?> aggregationType : aggregationTypeList) {
374             mIdToAggregateResult.put(
375                     aggregationType.getAggregationTypeIdentifier(),
376                     result -> getPressureResult(result.readDouble()));
377             populateIdDataAggregationType(aggregationType);
378         }
379     }
380 
addLengthIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)381     private void addLengthIdsToAggregateResultMap(
382             @NonNull List<AggregationType<?>> aggregationTypeList) {
383         for (AggregationType<?> aggregationType : aggregationTypeList) {
384             mIdToAggregateResult.put(
385                     aggregationType.getAggregationTypeIdentifier(),
386                     result -> getLengthResult(result.readDouble()));
387             populateIdDataAggregationType(aggregationType);
388         }
389     }
390 
addVolumeIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)391     private void addVolumeIdsToAggregateResultMap(
392             @NonNull List<AggregationType<?>> aggregationTypeList) {
393         for (AggregationType<?> aggregationType : aggregationTypeList) {
394             mIdToAggregateResult.put(
395                     aggregationType.getAggregationTypeIdentifier(),
396                     result -> getVolumeResult(result.readDouble()));
397             populateIdDataAggregationType(aggregationType);
398         }
399     }
400 
addMassIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)401     private void addMassIdsToAggregateResultMap(
402             @NonNull List<AggregationType<?>> aggregationTypeList) {
403         for (AggregationType<?> aggregationType : aggregationTypeList) {
404             mIdToAggregateResult.put(
405                     aggregationType.getAggregationTypeIdentifier(),
406                     result -> getMassResult(result.readDouble()));
407             populateIdDataAggregationType(aggregationType);
408         }
409     }
410 
addVelocityIdsToAggregateResultMap( @onNull List<AggregationType<?>> aggregationTypeList)411     private void addVelocityIdsToAggregateResultMap(
412             @NonNull List<AggregationType<?>> aggregationTypeList) {
413         for (AggregationType<?> aggregationType : aggregationTypeList) {
414             mIdToAggregateResult.put(
415                     aggregationType.getAggregationTypeIdentifier(),
416                     result -> getVelocityResult(result.readDouble()));
417             populateIdDataAggregationType(aggregationType);
418         }
419     }
420 
populateIdDataAggregationType(AggregationType<?> aggregationType)421     private void populateIdDataAggregationType(AggregationType<?> aggregationType) {
422         mIdDataAggregationTypeMap.put(
423                 aggregationType.getAggregationTypeIdentifier(), aggregationType);
424         mDataAggregationTypeIdMap.put(
425                 aggregationType, aggregationType.getAggregationTypeIdentifier());
426     }
427 
428     /**
429      * Implementation should get and covert result to appropriate type (such as long, double etc.)
430      * using {@code result}
431      */
432     private interface AggregationResultCreator {
getAggregateResult(Parcel result)433         AggregateResult<?> getAggregateResult(Parcel result);
434     }
435 }
436