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.os.Parcel; 23 import android.os.Parcelable; 24 25 import com.android.internal.util.Preconditions; 26 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Objects; 31 import java.util.function.Function; 32 import java.util.function.Predicate; 33 34 /** 35 * A location result representing a list of locations, ordered from earliest to latest. 36 * 37 * @hide 38 */ 39 public final class LocationResult implements Parcelable { 40 41 /** 42 * Creates a new LocationResult from the given locations, making a copy of each location. 43 * Locations must be ordered in the same order they were derived (earliest to latest). 44 */ create(@onNull List<Location> locations)45 public static @NonNull LocationResult create(@NonNull List<Location> locations) { 46 Preconditions.checkArgument(!locations.isEmpty()); 47 ArrayList<Location> locationsCopy = new ArrayList<>(locations.size()); 48 for (Location location : locations) { 49 locationsCopy.add(new Location(Objects.requireNonNull(location))); 50 } 51 return new LocationResult(locationsCopy); 52 } 53 54 /** 55 * Creates a new LocationResult from the given locations, making a copy of each location. 56 * Locations must be ordered in the same order they were derived (earliest to latest). 57 */ create(@onNull Location... locations)58 public static @NonNull LocationResult create(@NonNull Location... locations) { 59 Preconditions.checkArgument(locations.length > 0); 60 ArrayList<Location> locationsCopy = new ArrayList<>(locations.length); 61 for (Location location : locations) { 62 locationsCopy.add(new Location(Objects.requireNonNull(location))); 63 } 64 return new LocationResult(locationsCopy); 65 } 66 67 /** 68 * Creates a new LocationResult that takes ownership of the given locations without copying 69 * them. Callers must ensure the given locations are never mutated after this method is called. 70 * Locations must be ordered in the same order they were derived (earliest to latest). 71 */ wrap(@onNull List<Location> locations)72 public static @NonNull LocationResult wrap(@NonNull List<Location> locations) { 73 Preconditions.checkArgument(!locations.isEmpty()); 74 return new LocationResult(new ArrayList<>(locations)); 75 } 76 77 /** 78 * Creates a new LocationResult that takes ownership of the given locations without copying 79 * them. Callers must ensure the given locations are never mutated after this method is called. 80 * Locations must be ordered in the same order they were derived (earliest to latest). 81 */ wrap(@onNull Location... locations)82 public static @NonNull LocationResult wrap(@NonNull Location... locations) { 83 Preconditions.checkArgument(locations.length > 0); 84 ArrayList<Location> newLocations = new ArrayList<>(locations.length); 85 for (Location location : locations) { 86 newLocations.add(Objects.requireNonNull(location)); 87 } 88 return new LocationResult(newLocations); 89 } 90 91 private final ArrayList<Location> mLocations; 92 LocationResult(ArrayList<Location> locations)93 private LocationResult(ArrayList<Location> locations) { 94 Preconditions.checkArgument(!locations.isEmpty()); 95 mLocations = locations; 96 } 97 98 /** 99 * Throws an IllegalArgumentException if the ordering of locations does not appear to generally 100 * be from earliest to latest, or if any individual location is incomplete. 101 * 102 * @hide 103 */ validate()104 public @NonNull LocationResult validate() { 105 long prevElapsedRealtimeNs = 0; 106 final int size = mLocations.size(); 107 for (int i = 0; i < size; ++i) { 108 Location location = mLocations.get(i); 109 if (!location.isComplete()) { 110 throw new IllegalArgumentException( 111 "incomplete location at index " + i + ": " + mLocations); 112 } 113 if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) { 114 throw new IllegalArgumentException( 115 "incorrectly ordered location at index " + i + ": " + mLocations); 116 } 117 prevElapsedRealtimeNs = location.getElapsedRealtimeNanos(); 118 } 119 120 return this; 121 } 122 123 /** 124 * Returns the latest location in this location result, ie, the location at the highest index. 125 */ getLastLocation()126 public @NonNull Location getLastLocation() { 127 return mLocations.get(mLocations.size() - 1); 128 } 129 130 /** 131 * Returns the number of locations in this location result. 132 */ size()133 public @IntRange(from = 1) int size() { 134 return mLocations.size(); 135 } 136 137 /** 138 * Returns the location at the given index, from 0 to {@link #size()} - 1. Locations at lower 139 * indices are from earlier in time than location at higher indices. 140 */ get(@ntRangefrom = 0) int i)141 public @NonNull Location get(@IntRange(from = 0) int i) { 142 return mLocations.get(i); 143 } 144 145 /** 146 * Returns an unmodifiable list of locations in this location result. 147 */ asList()148 public @NonNull List<Location> asList() { 149 return Collections.unmodifiableList(mLocations); 150 } 151 152 /** 153 * Returns a deep copy of this LocationResult. 154 * 155 * @hide 156 */ deepCopy()157 public @NonNull LocationResult deepCopy() { 158 final int size = mLocations.size(); 159 ArrayList<Location> copy = new ArrayList<>(size); 160 for (int i = 0; i < size; i++) { 161 copy.add(new Location(mLocations.get(i))); 162 } 163 return new LocationResult(copy); 164 } 165 166 /** 167 * Returns a LocationResult with only the last location from this location result. 168 * 169 * @hide 170 */ asLastLocationResult()171 public @NonNull LocationResult asLastLocationResult() { 172 if (mLocations.size() == 1) { 173 return this; 174 } else { 175 return LocationResult.wrap(getLastLocation()); 176 } 177 } 178 179 /** 180 * Returns a LocationResult with only locations that pass the given predicate. This 181 * implementation will avoid allocations when no locations are filtered out. The predicate is 182 * guaranteed to be invoked once per location, in order from earliest to latest. If all 183 * locations are filtered out a null value is returned. 184 * 185 * @hide 186 */ filter(Predicate<Location> predicate)187 public @Nullable LocationResult filter(Predicate<Location> predicate) { 188 ArrayList<Location> filtered = mLocations; 189 final int size = mLocations.size(); 190 for (int i = 0; i < size; ++i) { 191 if (!predicate.test(mLocations.get(i))) { 192 if (filtered == mLocations) { 193 filtered = new ArrayList<>(mLocations.size() - 1); 194 for (int j = 0; j < i; ++j) { 195 filtered.add(mLocations.get(j)); 196 } 197 } 198 } else if (filtered != mLocations) { 199 filtered.add(mLocations.get(i)); 200 } 201 } 202 203 if (filtered == mLocations) { 204 return this; 205 } else if (filtered.isEmpty()) { 206 return null; 207 } else { 208 return new LocationResult(filtered); 209 } 210 } 211 212 /** 213 * Returns a LocationResult with locations mapped to other locations. This implementation will 214 * avoid allocations when all locations are mapped to the same location. The function is 215 * guaranteed to be invoked once per location, in order from earliest to latest. 216 * 217 * @hide 218 */ map(Function<Location, Location> function)219 public @NonNull LocationResult map(Function<Location, Location> function) { 220 ArrayList<Location> mapped = mLocations; 221 final int size = mLocations.size(); 222 for (int i = 0; i < size; ++i) { 223 Location location = mLocations.get(i); 224 Location newLocation = function.apply(location); 225 if (mapped != mLocations) { 226 mapped.add(newLocation); 227 } else if (newLocation != location) { 228 mapped = new ArrayList<>(mLocations.size()); 229 for (int j = 0; j < i; ++j) { 230 mapped.add(mLocations.get(j)); 231 } 232 mapped.add(newLocation); 233 } 234 } 235 236 if (mapped == mLocations) { 237 return this; 238 } else { 239 return new LocationResult(mapped); 240 } 241 } 242 243 public static final @NonNull Parcelable.Creator<LocationResult> CREATOR = 244 new Parcelable.Creator<LocationResult>() { 245 @Override 246 public LocationResult createFromParcel(Parcel in) { 247 return new LocationResult( 248 Objects.requireNonNull(in.createTypedArrayList(Location.CREATOR))); 249 } 250 251 @Override 252 public LocationResult[] newArray(int size) { 253 return new LocationResult[size]; 254 } 255 }; 256 257 @Override describeContents()258 public int describeContents() { 259 return 0; 260 } 261 262 @Override writeToParcel(@onNull Parcel parcel, int flags)263 public void writeToParcel(@NonNull Parcel parcel, int flags) { 264 parcel.writeTypedList(mLocations); 265 } 266 267 @Override equals(Object o)268 public boolean equals(Object o) { 269 if (this == o) { 270 return true; 271 } 272 if (o == null || getClass() != o.getClass()) { 273 return false; 274 } 275 276 LocationResult that = (LocationResult) o; 277 return mLocations.equals(that.mLocations); 278 } 279 280 @Override hashCode()281 public int hashCode() { 282 return Objects.hash(mLocations); 283 } 284 285 @Override toString()286 public String toString() { 287 return mLocations.toString(); 288 } 289 } 290