1 /*
2  * Copyright (C) 2024 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.datatypes;
18 
19 import static android.health.connect.datatypes.ExerciseSegmentType.ExerciseSegmentTypes;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.health.connect.internal.datatypes.ExerciseCompletionGoalInternal;
26 import android.health.connect.internal.datatypes.ExercisePerformanceGoalInternal;
27 import android.health.connect.internal.datatypes.PlannedExerciseStepInternal;
28 import android.util.ArraySet;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.Set;
36 import java.util.stream.Collectors;
37 
38 /** A single step within an {@link PlannedExerciseBlock} e.g. 8x 60kg barbell squats. */
39 @FlaggedApi("com.android.healthconnect.flags.training_plans")
40 public final class PlannedExerciseStep {
41     @ExerciseSegmentTypes private final int mExerciseType;
42     @ExerciseCategory private final int mExerciseCategory;
43     @Nullable private final CharSequence mDescription;
44     private final ExerciseCompletionGoal mCompletionGoal;
45     private final List<ExercisePerformanceGoal> mPerformanceGoals;
46 
PlannedExerciseStep( @xerciseSegmentTypes int exerciseType, @Nullable CharSequence description, @ExerciseCategory int exerciseCategory, ExerciseCompletionGoal completionGoal, List<ExercisePerformanceGoal> performanceGoals)47     private PlannedExerciseStep(
48             @ExerciseSegmentTypes int exerciseType,
49             @Nullable CharSequence description,
50             @ExerciseCategory int exerciseCategory,
51             ExerciseCompletionGoal completionGoal,
52             List<ExercisePerformanceGoal> performanceGoals) {
53         this.mExerciseType = exerciseType;
54         this.mDescription = description;
55         this.mExerciseCategory = exerciseCategory;
56         this.mCompletionGoal = completionGoal;
57         this.mPerformanceGoals = performanceGoals;
58     }
59 
60     /** Returns the exercise type of this step. */
61     @ExerciseSegmentTypes
getExerciseType()62     public int getExerciseType() {
63         return mExerciseType;
64     }
65 
66     /** Returns the description of this step. */
67     @Nullable
getDescription()68     public CharSequence getDescription() {
69         return mDescription;
70     }
71 
72     /** Returns the exercise category of this step. */
73     @ExerciseCategory
getExerciseCategory()74     public int getExerciseCategory() {
75         return mExerciseCategory;
76     }
77 
78     /** Returns the exercise completion goal for this step. */
79     @NonNull
getCompletionGoal()80     public ExerciseCompletionGoal getCompletionGoal() {
81         return mCompletionGoal;
82     }
83 
84     /**
85      * Returns the exercise performance goals for this step.
86      *
87      * @return An unmodifiable list of {@link ExercisePerformanceGoal}.
88      */
89     @NonNull
getPerformanceGoals()90     public List<ExercisePerformanceGoal> getPerformanceGoals() {
91         return mPerformanceGoals;
92     }
93 
94     @Override
equals(@ullable Object o)95     public boolean equals(@Nullable Object o) {
96         if (this == o) return true;
97         if (!(o instanceof PlannedExerciseStep)) return false;
98         PlannedExerciseStep that = (PlannedExerciseStep) o;
99         return RecordUtils.isEqualNullableCharSequences(getDescription(), that.getDescription())
100                 && this.getExerciseCategory() == that.getExerciseCategory()
101                 && this.getExerciseType() == that.getExerciseType()
102                 && Objects.equals(this.getCompletionGoal(), that.getCompletionGoal())
103                 && Objects.equals(this.getPerformanceGoals(), that.getPerformanceGoals());
104     }
105 
106     @Override
hashCode()107     public int hashCode() {
108         return Objects.hash(
109                 getDescription(),
110                 getExerciseCategory(),
111                 getExerciseType(),
112                 getCompletionGoal(),
113                 getPerformanceGoals());
114     }
115 
116     /** Builder of {@link PlannedExerciseStep}. */
117     public static final class Builder {
118         @ExerciseSegmentTypes private int mExerciseType;
119         @ExerciseCategory private int mExerciseCategory;
120         @Nullable private CharSequence mDescription;
121         private ExerciseCompletionGoal mCompletionGoal;
122         private List<ExercisePerformanceGoal> mPerformanceGoals = new ArrayList<>();
123 
124         /**
125          * @param exerciseType The type of exercise to be carried out in this step, e.g. running.
126          * @param exerciseCategory The category of exercise to be carried out in this step, e.g.
127          *     warmup.
128          * @param completionGoal The goal to be met to complete this step.
129          */
Builder( @xerciseSegmentTypes int exerciseType, @ExerciseCategory int exerciseCategory, @NonNull ExerciseCompletionGoal completionGoal)130         public Builder(
131                 @ExerciseSegmentTypes int exerciseType,
132                 @ExerciseCategory int exerciseCategory,
133                 @NonNull ExerciseCompletionGoal completionGoal) {
134             this.mExerciseType = exerciseType;
135             this.mExerciseCategory = exerciseCategory;
136             this.mCompletionGoal = completionGoal;
137         }
138 
139         /** Sets the exercise type. */
140         @NonNull
setExerciseType(@xerciseSegmentTypes int exerciseType)141         public Builder setExerciseType(@ExerciseSegmentTypes int exerciseType) {
142             this.mExerciseType = exerciseType;
143             return this;
144         }
145 
146         /** Sets the exercise category. */
147         @NonNull
setExerciseCategory(@xerciseCategory int category)148         public Builder setExerciseCategory(@ExerciseCategory int category) {
149             this.mExerciseCategory = category;
150             return this;
151         }
152 
153         /** Sets the description. */
154         @NonNull
setDescription(@ullable CharSequence description)155         public Builder setDescription(@Nullable CharSequence description) {
156             this.mDescription = description;
157             return this;
158         }
159 
160         /** Sets the {@link ExerciseCompletionGoal}. */
161         @NonNull
setCompletionGoal(@onNull ExerciseCompletionGoal completionGoal)162         public Builder setCompletionGoal(@NonNull ExerciseCompletionGoal completionGoal) {
163             Objects.requireNonNull(completionGoal);
164             this.mCompletionGoal = completionGoal;
165             return this;
166         }
167 
168         /** Adds a {@link ExercisePerformanceGoal}. */
169         @NonNull
addPerformanceGoal(@onNull ExercisePerformanceGoal performanceGoal)170         public Builder addPerformanceGoal(@NonNull ExercisePerformanceGoal performanceGoal) {
171             Objects.requireNonNull(performanceGoal);
172             this.mPerformanceGoals.add(performanceGoal);
173             return this;
174         }
175 
176         /** Sets {@link ExercisePerformanceGoal} entries. */
177         @NonNull
setPerformanceGoals( @onNull List<ExercisePerformanceGoal> performanceGoals)178         public Builder setPerformanceGoals(
179                 @NonNull List<ExercisePerformanceGoal> performanceGoals) {
180             Objects.requireNonNull(performanceGoals);
181             this.mPerformanceGoals.clear();
182             this.mPerformanceGoals.addAll(performanceGoals);
183             return this;
184         }
185 
186         /** Clears {@link ExercisePerformanceGoal} entries. */
187         @NonNull
clearPerformanceGoals()188         public Builder clearPerformanceGoals() {
189             this.mPerformanceGoals.clear();
190             return this;
191         }
192 
193         /** Returns {@link PlannedExerciseStep} instance. */
194         @NonNull
build()195         public PlannedExerciseStep build() {
196             Set<Class> classes = new ArraySet<>();
197             for (ExercisePerformanceGoal goal : mPerformanceGoals) {
198                 classes.add(goal.getClass());
199             }
200             if (classes.size() != mPerformanceGoals.size()) {
201                 throw new IllegalArgumentException(
202                         "At most one of each type of performance goal is permitted.");
203             }
204             if (!isValidExerciseCategory(mExerciseCategory)) {
205                 throw new IllegalArgumentException("Invalid exercise category.");
206             }
207             return new PlannedExerciseStep(
208                     mExerciseType,
209                     mDescription,
210                     mExerciseCategory,
211                     mCompletionGoal,
212                     List.copyOf(mPerformanceGoals));
213         }
214     }
215 
216     /** @hide */
toInternalObject()217     public PlannedExerciseStepInternal toInternalObject() {
218         PlannedExerciseStepInternal result =
219                 new PlannedExerciseStepInternal(
220                         getExerciseType(),
221                         getExerciseCategory(),
222                         ExerciseCompletionGoalInternal.fromExternalObject(getCompletionGoal()));
223         if (mDescription != null) {
224             result.setDescription(mDescription.toString());
225         }
226         result.setPerformanceGoals(
227                 getPerformanceGoals().stream()
228                         .map(ExercisePerformanceGoalInternal::fromExternalObject)
229                         .collect(Collectors.toList()));
230         return result;
231     }
232 
233     /** An unknown category of exercise. */
234     public static final int EXERCISE_CATEGORY_UNKNOWN = 0;
235 
236     /** A warmup. */
237     public static final int EXERCISE_CATEGORY_WARMUP = 1;
238 
239     /** A rest. */
240     public static final int EXERCISE_CATEGORY_REST = 2;
241 
242     /** Active exercise. */
243     public static final int EXERCISE_CATEGORY_ACTIVE = 3;
244 
245     /** Cooldown exercise, typically at the end of a workout. */
246     public static final int EXERCISE_CATEGORY_COOLDOWN = 4;
247 
248     /** Lower intensity, active exercise. */
249     public static final int EXERCISE_CATEGORY_RECOVERY = 5;
250 
251     /** @hide */
252     @IntDef({
253         EXERCISE_CATEGORY_UNKNOWN,
254         EXERCISE_CATEGORY_WARMUP,
255         EXERCISE_CATEGORY_REST,
256         EXERCISE_CATEGORY_ACTIVE,
257         EXERCISE_CATEGORY_COOLDOWN,
258         EXERCISE_CATEGORY_RECOVERY,
259     })
260     @Retention(RetentionPolicy.SOURCE)
261     public @interface ExerciseCategory {}
262 
263     private static final Set<Integer> VALID_EXERCISE_CATEGORIES =
264             Set.of(
265                     EXERCISE_CATEGORY_UNKNOWN,
266                     EXERCISE_CATEGORY_WARMUP,
267                     EXERCISE_CATEGORY_REST,
268                     EXERCISE_CATEGORY_ACTIVE,
269                     EXERCISE_CATEGORY_COOLDOWN,
270                     EXERCISE_CATEGORY_RECOVERY);
271 
272     /**
273      * Returns whether given exercise category is known by current module version.
274      *
275      * @hide
276      */
isValidExerciseCategory(int exerciseCategory)277     public static boolean isValidExerciseCategory(int exerciseCategory) {
278         return VALID_EXERCISE_CATEGORIES.contains(exerciseCategory);
279     }
280 }
281