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; 18 19 import android.annotation.FloatRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.health.connect.Constants; 23 import android.health.connect.datatypes.ExerciseRoute; 24 import android.health.connect.datatypes.units.Length; 25 import android.os.Parcel; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.time.Instant; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Objects; 33 34 /** 35 * @see ExerciseRoute 36 * @hide 37 */ 38 public class ExerciseRouteInternal { 39 private final List<LocationInternal> mRouteExerciseRouteLocations; 40 ExerciseRouteInternal(@onNull List<LocationInternal> routeExerciseRouteLocations)41 public ExerciseRouteInternal(@NonNull List<LocationInternal> routeExerciseRouteLocations) { 42 Objects.requireNonNull(routeExerciseRouteLocations); 43 mRouteExerciseRouteLocations = new ArrayList<>(routeExerciseRouteLocations); 44 } 45 46 @NonNull getRouteLocations()47 public List<LocationInternal> getRouteLocations() { 48 return mRouteExerciseRouteLocations; 49 } 50 51 /** Read the route from parcel. */ 52 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 53 @Nullable readFromParcel(@onNull Parcel parcel)54 public static ExerciseRouteInternal readFromParcel(@NonNull Parcel parcel) { 55 boolean routeIsNull = parcel.readBoolean(); 56 if (routeIsNull) { 57 return null; 58 } 59 60 int routeSize = parcel.readInt(); 61 ArrayList<LocationInternal> routeLocations = new ArrayList<>(routeSize); 62 for (int i = 0; i < routeSize; i++) { 63 routeLocations.add(LocationInternal.readFromParcel(parcel)); 64 } 65 return new ExerciseRouteInternal(routeLocations); 66 } 67 68 /** Write the route to parcel. */ 69 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) writeToParcel( @ullable ExerciseRouteInternal route, @NonNull Parcel parcel)70 public static void writeToParcel( 71 @Nullable ExerciseRouteInternal route, @NonNull Parcel parcel) { 72 // Write if the route is null first to restore correctly. 73 if (route == null) { 74 parcel.writeBoolean(true); 75 } else { 76 parcel.writeBoolean(false); 77 route.writeToParcel(parcel); 78 } 79 } 80 writeToParcel(@onNull Parcel parcel)81 private void writeToParcel(@NonNull Parcel parcel) { 82 parcel.writeInt(mRouteExerciseRouteLocations.size()); 83 for (LocationInternal location : mRouteExerciseRouteLocations) { 84 location.writeToParcel(parcel); 85 } 86 } 87 88 /** Convert internal route to external route object. */ 89 @VisibleForTesting toExternalRoute()90 public ExerciseRoute toExternalRoute() { 91 List<ExerciseRoute.Location> routeLocations = 92 new ArrayList<>(mRouteExerciseRouteLocations.size()); 93 for (LocationInternal location : mRouteExerciseRouteLocations) { 94 routeLocations.add(location.toExternalExerciseRouteLocation()); 95 } 96 return new ExerciseRoute(routeLocations); 97 } 98 99 @Override equals(Object o)100 public boolean equals(Object o) { 101 if (this == o) return true; 102 if (!(o instanceof ExerciseRouteInternal)) return false; 103 ExerciseRouteInternal that = (ExerciseRouteInternal) o; 104 return getRouteLocations().equals(that.getRouteLocations()); 105 } 106 107 /** Add location to the route */ addLocation(LocationInternal location)108 void addLocation(LocationInternal location) { 109 mRouteExerciseRouteLocations.add(location); 110 } 111 112 @Override hashCode()113 public int hashCode() { 114 return Objects.hash(mRouteExerciseRouteLocations); 115 } 116 /** 117 * @see ExerciseRoute.Location 118 * @hide 119 */ 120 public static final class LocationInternal { 121 private long mTime = Constants.DEFAULT_LONG; 122 private double mLatitude = Constants.DEFAULT_DOUBLE; 123 private double mLongitude = Constants.DEFAULT_DOUBLE; 124 private double mHorizontalAccuracy = Constants.DEFAULT_DOUBLE; 125 private double mVerticalAccuracy = Constants.DEFAULT_DOUBLE; 126 private double mAltitude = Constants.DEFAULT_DOUBLE; 127 128 /** 129 * @return time of this location time point 130 */ getTime()131 public long getTime() { 132 return mTime; 133 } 134 135 /** returns this object with the specified time */ 136 @NonNull setTime(long time)137 public LocationInternal setTime(long time) { 138 this.mTime = time; 139 return this; 140 } 141 142 /** 143 * @return longitude of this location time point 144 */ getLongitude()145 public double getLongitude() { 146 return mLongitude; 147 } 148 149 /** returns this object with the specified longitude */ 150 @NonNull setLongitude( @loatRangefrom = -180.0, to = 180.0) double longitude)151 public LocationInternal setLongitude( 152 @FloatRange(from = -180.0, to = 180.0) double longitude) { 153 this.mLongitude = longitude; 154 return this; 155 } 156 157 /** 158 * @return latitude of this location time point 159 */ getLatitude()160 public double getLatitude() { 161 return mLatitude; 162 } 163 164 /** returns this object with the specified latitude */ 165 @NonNull setLatitude(@loatRangefrom = -90.0, to = 90.0) double latitude)166 public LocationInternal setLatitude(@FloatRange(from = -90.0, to = 90.0) double latitude) { 167 this.mLatitude = latitude; 168 return this; 169 } 170 171 /** 172 * @return horizontal accuracy of this location time point 173 */ getHorizontalAccuracy()174 public double getHorizontalAccuracy() { 175 return mHorizontalAccuracy; 176 } 177 178 /** returns this object with the specified horizontal accuracy */ 179 @NonNull setHorizontalAccuracy(double horizontalAccuracy)180 public LocationInternal setHorizontalAccuracy(double horizontalAccuracy) { 181 this.mHorizontalAccuracy = horizontalAccuracy; 182 return this; 183 } 184 185 /** 186 * @return vertical accuracy of this location time point 187 */ getVerticalAccuracy()188 public double getVerticalAccuracy() { 189 return mVerticalAccuracy; 190 } 191 192 /** returns this object with the specified vertical accuracy */ 193 @NonNull setVerticalAccuracy(double verticalAccuracy)194 public LocationInternal setVerticalAccuracy(double verticalAccuracy) { 195 this.mVerticalAccuracy = verticalAccuracy; 196 return this; 197 } 198 199 /** 200 * @return altitude of this location time point 201 */ getAltitude()202 public double getAltitude() { 203 return mAltitude; 204 } 205 206 /** returns this object with the specified altitude */ 207 @NonNull setAltitude(double altitude)208 public LocationInternal setAltitude(double altitude) { 209 this.mAltitude = altitude; 210 return this; 211 } 212 213 /** Read Location object from Parcel. */ 214 @VisibleForTesting readFromParcel(@onNull Parcel parcel)215 public static LocationInternal readFromParcel(@NonNull Parcel parcel) { 216 return new LocationInternal() 217 .setTime(parcel.readLong()) 218 .setLatitude(parcel.readDouble()) 219 .setLongitude(parcel.readDouble()) 220 .setHorizontalAccuracy(parcel.readDouble()) 221 .setVerticalAccuracy(parcel.readDouble()) 222 .setAltitude(parcel.readDouble()); 223 } 224 225 /** Write Location object from Parcel. */ 226 @VisibleForTesting writeToParcel(@onNull Parcel parcel)227 public void writeToParcel(@NonNull Parcel parcel) { 228 parcel.writeLong(getTime()); 229 parcel.writeDouble(getLatitude()); 230 parcel.writeDouble(getLongitude()); 231 parcel.writeDouble(getHorizontalAccuracy()); 232 parcel.writeDouble(getVerticalAccuracy()); 233 parcel.writeDouble(getAltitude()); 234 } 235 236 /** Convert LocationInternal to Location external object. */ 237 @VisibleForTesting toExternalExerciseRouteLocation()238 public ExerciseRoute.Location toExternalExerciseRouteLocation() { 239 ExerciseRoute.Location.Builder builder = 240 new ExerciseRoute.Location.Builder( 241 Instant.ofEpochMilli(getTime()), getLatitude(), getLongitude()); 242 243 if (getHorizontalAccuracy() != Constants.DEFAULT_DOUBLE) { 244 builder.setHorizontalAccuracy(Length.fromMeters(getHorizontalAccuracy())); 245 } 246 247 if (getVerticalAccuracy() != Constants.DEFAULT_DOUBLE) { 248 builder.setVerticalAccuracy(Length.fromMeters(getVerticalAccuracy())); 249 } 250 251 if (getAltitude() != Constants.DEFAULT_DOUBLE) { 252 builder.setAltitude(Length.fromMeters(getAltitude())); 253 } 254 return builder.buildWithoutValidation(); 255 } 256 257 @Override equals(Object o)258 public boolean equals(Object o) { 259 if (this == o) return true; 260 if (!(o instanceof LocationInternal)) return false; 261 LocationInternal that = (LocationInternal) o; 262 return (getLatitude() == that.getLatitude()) 263 && (getLongitude() == that.getLongitude()) 264 && (getTime() == that.getTime()) 265 && (getHorizontalAccuracy() == that.getHorizontalAccuracy()) 266 && (getVerticalAccuracy() == that.getVerticalAccuracy()) 267 && (getAltitude() == that.getAltitude()); 268 } 269 270 @Override hashCode()271 public int hashCode() { 272 return Objects.hash( 273 getTime(), 274 getLatitude(), 275 getLongitude(), 276 getHorizontalAccuracy(), 277 getVerticalAccuracy(), 278 getAltitude()); 279 } 280 } 281 } 282