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