• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.location;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.location.flags.Flags;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.SystemClock;
26 import android.util.Log;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.function.Function;
35 import java.util.function.Predicate;
36 
37 /**
38  * A location result representing a list of locations, ordered from earliest to latest.
39  *
40  * @hide
41  */
42 public final class LocationResult implements Parcelable {
43     private static final String TAG = "LocationResult";
44 
45     // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
46     // could likely be lower, but we only want to throw out really absurd values.
47     private static final float MAX_ACCURACY_M = 1000000;
48 
49     // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
50     // current fastest private jet). Higher speed than the value is considered as a malfunction
51     // than a correct reading.
52     private static final float MAX_SPEED_MPS = 343;
53 
54     /** Exception representing an invalid location within a {@link LocationResult}. */
55     public static class BadLocationException extends Exception {
BadLocationException(String message)56         public BadLocationException(String message) {
57             super(message);
58         }
59     }
60 
61     /**
62      * Creates a new LocationResult from the given locations, making a copy of each location.
63      * Locations must be ordered in the same order they were derived (earliest to latest).
64      */
create(@onNull List<Location> locations)65     public static @NonNull LocationResult create(@NonNull List<Location> locations) {
66         Preconditions.checkArgument(!locations.isEmpty());
67         ArrayList<Location> locationsCopy = new ArrayList<>(locations.size());
68         for (Location location : locations) {
69             locationsCopy.add(new Location(Objects.requireNonNull(location)));
70         }
71         return new LocationResult(locationsCopy);
72     }
73 
74     /**
75      * Creates a new LocationResult from the given locations, making a copy of each location.
76      * Locations must be ordered in the same order they were derived (earliest to latest).
77      */
create(@onNull Location... locations)78     public static @NonNull LocationResult create(@NonNull Location... locations) {
79         Preconditions.checkArgument(locations.length > 0);
80         ArrayList<Location> locationsCopy = new ArrayList<>(locations.length);
81         for (Location location : locations) {
82             locationsCopy.add(new Location(Objects.requireNonNull(location)));
83         }
84         return new LocationResult(locationsCopy);
85     }
86 
87     /**
88      * Creates a new LocationResult that takes ownership of the given locations without copying
89      * them. Callers must ensure the given locations are never mutated after this method is called.
90      * Locations must be ordered in the same order they were derived (earliest to latest).
91      */
wrap(@onNull List<Location> locations)92     public static @NonNull LocationResult wrap(@NonNull List<Location> locations) {
93         Preconditions.checkArgument(!locations.isEmpty());
94         return new LocationResult(new ArrayList<>(locations));
95     }
96 
97     /**
98      * Creates a new LocationResult that takes ownership of the given locations without copying
99      * them. Callers must ensure the given locations are never mutated after this method is called.
100      * Locations must be ordered in the same order they were derived (earliest to latest).
101      */
wrap(@onNull Location... locations)102     public static @NonNull LocationResult wrap(@NonNull Location... locations) {
103         Preconditions.checkArgument(locations.length > 0);
104         ArrayList<Location> newLocations = new ArrayList<>(locations.length);
105         for (Location location : locations) {
106             newLocations.add(Objects.requireNonNull(location));
107         }
108         return new LocationResult(newLocations);
109     }
110 
111     private final ArrayList<Location> mLocations;
112 
LocationResult(ArrayList<Location> locations)113     private LocationResult(ArrayList<Location> locations) {
114         Preconditions.checkArgument(!locations.isEmpty());
115         mLocations = locations;
116     }
117 
118     /**
119      * Throws an IllegalArgumentException if the ordering of locations does not appear to generally
120      * be from earliest to latest, or if any individual location is incomplete.
121      *
122      * @hide
123      */
validate()124     public @NonNull LocationResult validate() throws BadLocationException {
125         long prevElapsedRealtimeNs = 0;
126         final int size = mLocations.size();
127         for (int i = 0; i < size; ++i) {
128             Location location = mLocations.get(i);
129             if (Flags.locationValidation()) {
130                 if (location.getLatitude() < -90.0
131                         || location.getLatitude() > 90.0
132                         || location.getLongitude() < -180.0
133                         || location.getLongitude() > 180.0
134                         || Double.isNaN(location.getLatitude())
135                         || Double.isNaN(location.getLongitude())) {
136                     throw new BadLocationException("location must have valid lat/lng");
137                 }
138                 if (!location.hasAccuracy()) {
139                     throw new BadLocationException("location must have accuracy");
140                 }
141                 if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
142                     throw new BadLocationException("location must have reasonable accuracy");
143                 }
144                 if (location.getTime() < 0) {
145                     throw new BadLocationException("location must have valid time");
146                 }
147                 if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
148                     throw new BadLocationException(
149                             "location must have valid monotonically increasing realtime");
150                 }
151                 if (location.getElapsedRealtimeNanos()
152                         > SystemClock.elapsedRealtimeNanos()) {
153                     throw new BadLocationException("location must not have realtime in the future");
154                 }
155                 if (!location.isMock()) {
156                     if (location.getProvider() == null) {
157                         throw new BadLocationException("location must have valid provider");
158                     }
159                     if (location.getLatitude() == 0 && location.getLongitude() == 0) {
160                         throw new BadLocationException("location must not be at 0,0");
161                     }
162                 }
163 
164                 if (location.hasSpeed() && (location.getSpeed() < 0
165                         || location.getSpeed() > MAX_SPEED_MPS)) {
166                     Log.w(TAG, "removed bad location speed: " + location.getSpeed());
167                     location.removeSpeed();
168                 }
169             } else {
170                 if (!location.isComplete()) {
171                     throw new IllegalArgumentException(
172                             "incomplete location at index " + i + ": " + mLocations);
173                 }
174                 if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
175                     throw new IllegalArgumentException(
176                             "incorrectly ordered location at index " + i + ": " + mLocations);
177                 }
178             }
179             prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
180         }
181 
182         return this;
183     }
184 
185     /**
186      * Returns the latest location in this location result, ie, the location at the highest index.
187      */
getLastLocation()188     public @NonNull Location getLastLocation() {
189         return mLocations.get(mLocations.size() - 1);
190     }
191 
192     /**
193      * Returns the number of locations in this location result.
194      */
size()195     public @IntRange(from = 1) int size() {
196         return mLocations.size();
197     }
198 
199     /**
200      * Returns the location at the given index, from 0 to {@link #size()} - 1. Locations at lower
201      * indices are from earlier in time than location at higher indices.
202      */
get(@ntRangefrom = 0) int i)203     public @NonNull Location get(@IntRange(from = 0) int i) {
204         return mLocations.get(i);
205     }
206 
207     /**
208      * Returns an unmodifiable list of locations in this location result.
209      */
asList()210     public @NonNull List<Location> asList() {
211         return Collections.unmodifiableList(mLocations);
212     }
213 
214     /**
215      * Returns a deep copy of this LocationResult.
216      *
217      * @hide
218      */
deepCopy()219     public @NonNull LocationResult deepCopy() {
220         final int size = mLocations.size();
221         ArrayList<Location> copy = new ArrayList<>(size);
222         for (int i = 0; i < size; i++) {
223             copy.add(new Location(mLocations.get(i)));
224         }
225         return new LocationResult(copy);
226     }
227 
228     /**
229      * Returns a LocationResult with only the last location from this location result.
230      *
231      * @hide
232      */
asLastLocationResult()233     public @NonNull LocationResult asLastLocationResult() {
234         if (mLocations.size() == 1) {
235             return this;
236         } else {
237             return LocationResult.wrap(getLastLocation());
238         }
239     }
240 
241     /**
242      * Returns a LocationResult with only locations that pass the given predicate. This
243      * implementation will avoid allocations when no locations are filtered out. The predicate is
244      * guaranteed to be invoked once per location, in order from earliest to latest. If all
245      * locations are filtered out a null value is returned.
246      *
247      * @hide
248      */
filter(Predicate<Location> predicate)249     public @Nullable LocationResult filter(Predicate<Location> predicate) {
250         ArrayList<Location> filtered = mLocations;
251         final int size = mLocations.size();
252         for (int i = 0; i < size; ++i) {
253             if (!predicate.test(mLocations.get(i))) {
254                 if (filtered == mLocations) {
255                     filtered = new ArrayList<>(mLocations.size() - 1);
256                     for (int j = 0; j < i; ++j) {
257                         filtered.add(mLocations.get(j));
258                     }
259                 }
260             } else if (filtered != mLocations) {
261                 filtered.add(mLocations.get(i));
262             }
263         }
264 
265         if (filtered == mLocations) {
266             return this;
267         } else if (filtered.isEmpty()) {
268             return null;
269         } else {
270             return new LocationResult(filtered);
271         }
272     }
273 
274     /**
275      * Returns a LocationResult with locations mapped to other locations. This implementation will
276      * avoid allocations when all locations are mapped to the same location. The function is
277      * guaranteed to be invoked once per location, in order from earliest to latest.
278      *
279      * @hide
280      */
map(Function<Location, Location> function)281     public @NonNull LocationResult map(Function<Location, Location> function) {
282         ArrayList<Location> mapped = mLocations;
283         final int size = mLocations.size();
284         for (int i = 0; i < size; ++i) {
285             Location location = mLocations.get(i);
286             Location newLocation = function.apply(location);
287             if (mapped != mLocations) {
288                 mapped.add(newLocation);
289             } else if (newLocation != location) {
290                 mapped = new ArrayList<>(mLocations.size());
291                 for (int j = 0; j < i; ++j) {
292                     mapped.add(mLocations.get(j));
293                 }
294                 mapped.add(newLocation);
295             }
296         }
297 
298         if (mapped == mLocations) {
299             return this;
300         } else {
301             return new LocationResult(mapped);
302         }
303     }
304 
305     public static final @NonNull Parcelable.Creator<LocationResult> CREATOR =
306             new Parcelable.Creator<LocationResult>() {
307                 @Override
308                 public LocationResult createFromParcel(Parcel in) {
309                     return new LocationResult(
310                             Objects.requireNonNull(in.createTypedArrayList(Location.CREATOR)));
311                 }
312 
313                 @Override
314                 public LocationResult[] newArray(int size) {
315                     return new LocationResult[size];
316                 }
317             };
318 
319     @Override
describeContents()320     public int describeContents() {
321         return 0;
322     }
323 
324     @Override
writeToParcel(@onNull Parcel parcel, int flags)325     public void writeToParcel(@NonNull Parcel parcel, int flags) {
326         parcel.writeTypedList(mLocations);
327     }
328 
329     @Override
equals(Object o)330     public boolean equals(Object o) {
331         if (this == o) {
332             return true;
333         }
334         if (o == null || getClass() != o.getClass()) {
335             return false;
336         }
337 
338         LocationResult that = (LocationResult) o;
339         return mLocations.equals(that.mLocations);
340     }
341 
342     @Override
hashCode()343     public int hashCode() {
344         return Objects.hash(mLocations);
345     }
346 
347     @Override
toString()348     public String toString() {
349         return mLocations.toString();
350     }
351 }
352