• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.datatypes;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.health.connect.datatypes.units.Length;
23 import android.health.connect.datatypes.validation.ValidationUtils;
24 import android.health.connect.internal.datatypes.ExerciseRouteInternal;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import java.time.Instant;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /** Route of the exercise session. Contains sequence of location points with timestamps. */
34 public final class ExerciseRoute implements Parcelable {
35     private final List<Location> mRouteLocations;
36 
37     @NonNull
38     public static final Creator<ExerciseRoute> CREATOR =
39             new Creator<>() {
40 
41                 @Override
42                 public ExerciseRoute createFromParcel(Parcel source) {
43                     int size = source.readInt();
44                     List<Location> locations = new ArrayList<>(size);
45                     for (int i = 0; i < size; i++) {
46                         locations.add(i, Location.CREATOR.createFromParcel(source));
47                     }
48                     return new ExerciseRoute(locations);
49                 }
50 
51                 @Override
52                 public ExerciseRoute[] newArray(int size) {
53                     return new ExerciseRoute[size];
54                 }
55             };
56 
57     /**
58      * Creates {@link ExerciseRoute} instance
59      *
60      * @param routeLocations list of locations with timestamps that make up the route
61      */
ExerciseRoute(@onNull List<Location> routeLocations)62     public ExerciseRoute(@NonNull List<Location> routeLocations) {
63         Objects.requireNonNull(routeLocations);
64         mRouteLocations = routeLocations;
65     }
66 
67     @NonNull
getRouteLocations()68     public List<Location> getRouteLocations() {
69         return mRouteLocations;
70     }
71 
72     @Override
equals(Object o)73     public boolean equals(Object o) {
74         if (this == o) return true;
75         if (!(o instanceof ExerciseRoute)) return false;
76         ExerciseRoute that = (ExerciseRoute) o;
77         return getRouteLocations().equals(that.getRouteLocations());
78     }
79 
80     @Override
hashCode()81     public int hashCode() {
82         return Objects.hash(getRouteLocations());
83     }
84 
85     /** @hide */
toRouteInternal()86     public ExerciseRouteInternal toRouteInternal() {
87         List<ExerciseRouteInternal.LocationInternal> routeLocations =
88                 new ArrayList<>(getRouteLocations().size());
89         for (ExerciseRoute.Location location : getRouteLocations()) {
90             routeLocations.add(location.toExerciseRouteLocationInternal());
91         }
92         return new ExerciseRouteInternal(routeLocations);
93     }
94 
95     @Override
describeContents()96     public int describeContents() {
97         return 0;
98     }
99 
100     @Override
writeToParcel(@onNull Parcel dest, int flags)101     public void writeToParcel(@NonNull Parcel dest, int flags) {
102         dest.writeInt(mRouteLocations.size());
103         for (int i = 0; i < mRouteLocations.size(); i++) {
104             mRouteLocations.get(i).writeToParcel(dest, flags);
105         }
106     }
107 
108     /** Point in the time and space. Used in {@link ExerciseRoute}. */
109     public static final class Location implements Parcelable {
110         // Values are used for FloatRange annotation in latitude/longitude getters and constructor.
111         private static final double MIN_LONGITUDE = -180;
112         private static final double MAX_LONGITUDE = 180;
113         private static final double MIN_LATITUDE = -90;
114         private static final double MAX_LATITUDE = 90;
115 
116         private final Instant mTime;
117         private final double mLatitude;
118         private final double mLongitude;
119         private final Length mHorizontalAccuracy;
120         private final Length mVerticalAccuracy;
121         private final Length mAltitude;
122 
123         @NonNull
124         public static final Creator<Location> CREATOR =
125                 new Creator<>() {
126                     @Override
127                     public Location createFromParcel(Parcel source) {
128                         Instant timestamp = Instant.ofEpochMilli(source.readLong());
129                         double lat = source.readDouble();
130                         double lon = source.readDouble();
131                         Builder builder = new Builder(timestamp, lat, lon);
132                         if (source.readBoolean()) {
133                             builder.setHorizontalAccuracy(Length.fromMeters(source.readDouble()));
134                         }
135                         if (source.readBoolean()) {
136                             builder.setVerticalAccuracy(Length.fromMeters(source.readDouble()));
137                         }
138                         if (source.readBoolean()) {
139                             builder.setAltitude(Length.fromMeters(source.readDouble()));
140                         }
141                         return builder.build();
142                     }
143 
144                     @Override
145                     public Location[] newArray(int size) {
146                         return new Location[size];
147                     }
148                 };
149 
150         /**
151          * Represents a single location in an exercise route.
152          *
153          * @param time The point in time when the measurement was taken.
154          * @param latitude Latitude of a location represented as a double, in degrees. Valid range:
155          *     from -90 to 90 degrees.
156          * @param longitude Longitude of a location represented as a double, in degrees. Valid
157          *     range: from -180 to 180 degrees.
158          * @param horizontalAccuracy The radius of uncertainty for the location, in [Length] unit.
159          *     Must be non-negative value.
160          * @param verticalAccuracy The validity of the altitude values, and their estimated
161          *     uncertainty, in [Length] unit. Must be non-negative value.
162          * @param altitude An altitude of a location represented as a float, in [Length] unit above
163          *     sea level.
164          * @param skipValidation Boolean flag to skip validation of record values.
165          * @see ExerciseRoute
166          */
Location( @onNull Instant time, @FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latitude, @FloatRange(from = MIN_LONGITUDE, to = MAX_LONGITUDE) double longitude, @Nullable Length horizontalAccuracy, @Nullable Length verticalAccuracy, @Nullable Length altitude, boolean skipValidation)167         private Location(
168                 @NonNull Instant time,
169                 @FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latitude,
170                 @FloatRange(from = MIN_LONGITUDE, to = MAX_LONGITUDE) double longitude,
171                 @Nullable Length horizontalAccuracy,
172                 @Nullable Length verticalAccuracy,
173                 @Nullable Length altitude,
174                 boolean skipValidation) {
175             Objects.requireNonNull(time);
176 
177             if (!skipValidation) {
178                 ValidationUtils.requireInRange(latitude, MIN_LATITUDE, MAX_LATITUDE, "Latitude");
179                 ValidationUtils.requireInRange(
180                         longitude, MIN_LONGITUDE, MAX_LONGITUDE, "Longitude");
181 
182                 if (horizontalAccuracy != null) {
183                     ValidationUtils.requireNonNegative(
184                             horizontalAccuracy.getInMeters(), "Horizontal accuracy");
185                 }
186 
187                 if (verticalAccuracy != null && verticalAccuracy.getInMeters() < 0) {
188                     ValidationUtils.requireNonNegative(
189                             verticalAccuracy.getInMeters(), "Vertical accuracy");
190                 }
191             }
192 
193             mTime = time;
194             mLatitude = latitude;
195             mLongitude = longitude;
196             mHorizontalAccuracy = horizontalAccuracy;
197             mVerticalAccuracy = verticalAccuracy;
198             mAltitude = altitude;
199         }
200 
201         /** Returns time when this location has been recorded */
202         @NonNull
getTime()203         public Instant getTime() {
204             return mTime;
205         }
206 
207         /** Returns longitude of this location */
208         @FloatRange(from = -180.0, to = 180.0)
getLongitude()209         public double getLongitude() {
210             return mLongitude;
211         }
212 
213         /** Returns latitude of this location */
214         @FloatRange(from = -90.0, to = 90.0)
getLatitude()215         public double getLatitude() {
216             return mLatitude;
217         }
218 
219         /**
220          * Returns horizontal accuracy of this location time point. Returns null if no horizontal
221          * accuracy was specified.
222          */
223         @Nullable
getHorizontalAccuracy()224         public Length getHorizontalAccuracy() {
225             return mHorizontalAccuracy;
226         }
227 
228         /**
229          * Returns vertical accuracy of this location time point. Returns null if no vertical
230          * accuracy was specified.
231          */
232         @Nullable
getVerticalAccuracy()233         public Length getVerticalAccuracy() {
234             return mVerticalAccuracy;
235         }
236 
237         /**
238          * Returns altitude of this location time point. Returns null if no altitude was specified.
239          */
240         @Nullable
getAltitude()241         public Length getAltitude() {
242             return mAltitude;
243         }
244 
245         /** @hide */
toExerciseRouteLocationInternal()246         public ExerciseRouteInternal.LocationInternal toExerciseRouteLocationInternal() {
247             ExerciseRouteInternal.LocationInternal locationInternal =
248                     new ExerciseRouteInternal.LocationInternal()
249                             .setTime(getTime().toEpochMilli())
250                             .setLatitude(getLatitude())
251                             .setLongitude(getLongitude());
252 
253             if (getHorizontalAccuracy() != null) {
254                 locationInternal.setHorizontalAccuracy(getHorizontalAccuracy().getInMeters());
255             }
256 
257             if (getVerticalAccuracy() != null) {
258                 locationInternal.setVerticalAccuracy(getVerticalAccuracy().getInMeters());
259             }
260 
261             if (getAltitude() != null) {
262                 locationInternal.setAltitude(getAltitude().getInMeters());
263             }
264             return locationInternal;
265         }
266 
267         @Override
equals(Object o)268         public boolean equals(Object o) {
269             if (this == o) return true;
270             if (!(o instanceof Location)) return false;
271             Location that = (Location) o;
272             return Objects.equals(getAltitude(), that.getAltitude())
273                     && getTime().equals(that.getTime())
274                     && (getLatitude() == that.getLatitude())
275                     && (getLongitude() == that.getLongitude())
276                     && Objects.equals(getHorizontalAccuracy(), that.getHorizontalAccuracy())
277                     && Objects.equals(getVerticalAccuracy(), that.getVerticalAccuracy());
278         }
279 
280         @Override
hashCode()281         public int hashCode() {
282             return Objects.hash(
283                     getTime(),
284                     getLatitude(),
285                     getLongitude(),
286                     getHorizontalAccuracy(),
287                     getVerticalAccuracy(),
288                     getAltitude());
289         }
290 
291         @Override
describeContents()292         public int describeContents() {
293             return 0;
294         }
295 
296         @Override
writeToParcel(@onNull Parcel dest, int flags)297         public void writeToParcel(@NonNull Parcel dest, int flags) {
298             dest.writeLong(mTime.toEpochMilli());
299             dest.writeDouble(mLatitude);
300             dest.writeDouble(mLongitude);
301             dest.writeBoolean(mHorizontalAccuracy != null);
302             if (mHorizontalAccuracy != null) {
303                 dest.writeDouble(mHorizontalAccuracy.getInMeters());
304             }
305             dest.writeBoolean(mVerticalAccuracy != null);
306             if (mVerticalAccuracy != null) {
307                 dest.writeDouble(mVerticalAccuracy.getInMeters());
308             }
309             dest.writeBoolean(mAltitude != null);
310             if (mAltitude != null) {
311                 dest.writeDouble(mAltitude.getInMeters());
312             }
313         }
314 
315         /** Builder class for {@link Location} */
316         public static final class Builder {
317             @NonNull private final Instant mTime;
318 
319             @FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE)
320             private final double mLatitude;
321 
322             @FloatRange(from = MIN_LONGITUDE, to = MAX_LONGITUDE)
323             private final double mLongitude;
324 
325             @Nullable private Length mHorizontalAccuracy;
326             @Nullable private Length mVerticalAccuracy;
327             @Nullable private Length mAltitude;
328 
329             /** Sets time, longitude and latitude to the point. */
Builder( @onNull Instant time, @FloatRange(from = -90.0, to = 90.0) double latitude, @FloatRange(from = -180.0, to = 180.0) double longitude)330             public Builder(
331                     @NonNull Instant time,
332                     @FloatRange(from = -90.0, to = 90.0) double latitude,
333                     @FloatRange(from = -180.0, to = 180.0) double longitude) {
334                 Objects.requireNonNull(time);
335                 mTime = time;
336                 mLatitude = latitude;
337                 mLongitude = longitude;
338             }
339 
340             /** Sets horizontal accuracy to the point. */
341             @NonNull
setHorizontalAccuracy(@onNull Length horizontalAccuracy)342             public Builder setHorizontalAccuracy(@NonNull Length horizontalAccuracy) {
343                 Objects.requireNonNull(horizontalAccuracy);
344                 mHorizontalAccuracy = horizontalAccuracy;
345                 return this;
346             }
347 
348             /** Sets vertical accuracy to the point. */
349             @NonNull
setVerticalAccuracy(@onNull Length verticalAccuracy)350             public Builder setVerticalAccuracy(@NonNull Length verticalAccuracy) {
351                 Objects.requireNonNull(verticalAccuracy);
352                 mVerticalAccuracy = verticalAccuracy;
353                 return this;
354             }
355 
356             /** Sets altitude to the point. */
357             @NonNull
setAltitude(@onNull Length altitude)358             public Builder setAltitude(@NonNull Length altitude) {
359                 Objects.requireNonNull(altitude);
360                 mAltitude = altitude;
361                 return this;
362             }
363 
364             /**
365              * @return Object of {@link Location} without validating the values.
366              * @hide
367              */
368             @NonNull
buildWithoutValidation()369             public Location buildWithoutValidation() {
370                 return new Location(
371                         mTime,
372                         mLatitude,
373                         mLongitude,
374                         mHorizontalAccuracy,
375                         mVerticalAccuracy,
376                         mAltitude,
377                         true);
378             }
379 
380             /** Builds {@link Location} */
381             @NonNull
build()382             public Location build() {
383                 return new Location(
384                         mTime,
385                         mLatitude,
386                         mLongitude,
387                         mHorizontalAccuracy,
388                         mVerticalAccuracy,
389                         mAltitude,
390                         false);
391             }
392         }
393     }
394 }
395