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