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