| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.location; |
| |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| /** |
| * A location result representing a list of locations, ordered from earliest to latest. |
| * |
| * @hide |
| */ |
| public final class LocationResult implements Parcelable { |
| |
| /** |
| * Creates a new LocationResult from the given locations, making a copy of each location. |
| * Locations must be ordered in the same order they were derived (earliest to latest). |
| */ |
| public static @NonNull LocationResult create(@NonNull List<Location> locations) { |
| Preconditions.checkArgument(!locations.isEmpty()); |
| ArrayList<Location> locationsCopy = new ArrayList<>(locations.size()); |
| for (Location location : locations) { |
| locationsCopy.add(new Location(Objects.requireNonNull(location))); |
| } |
| return new LocationResult(locationsCopy); |
| } |
| |
| /** |
| * Creates a new LocationResult from the given locations, making a copy of each location. |
| * Locations must be ordered in the same order they were derived (earliest to latest). |
| */ |
| public static @NonNull LocationResult create(@NonNull Location... locations) { |
| Preconditions.checkArgument(locations.length > 0); |
| ArrayList<Location> locationsCopy = new ArrayList<>(locations.length); |
| for (Location location : locations) { |
| locationsCopy.add(new Location(Objects.requireNonNull(location))); |
| } |
| return new LocationResult(locationsCopy); |
| } |
| |
| /** |
| * Creates a new LocationResult that takes ownership of the given locations without copying |
| * them. Callers must ensure the given locations are never mutated after this method is called. |
| * Locations must be ordered in the same order they were derived (earliest to latest). |
| */ |
| public static @NonNull LocationResult wrap(@NonNull List<Location> locations) { |
| Preconditions.checkArgument(!locations.isEmpty()); |
| return new LocationResult(new ArrayList<>(locations)); |
| } |
| |
| /** |
| * Creates a new LocationResult that takes ownership of the given locations without copying |
| * them. Callers must ensure the given locations are never mutated after this method is called. |
| * Locations must be ordered in the same order they were derived (earliest to latest). |
| */ |
| public static @NonNull LocationResult wrap(@NonNull Location... locations) { |
| Preconditions.checkArgument(locations.length > 0); |
| ArrayList<Location> newLocations = new ArrayList<>(locations.length); |
| for (Location location : locations) { |
| newLocations.add(Objects.requireNonNull(location)); |
| } |
| return new LocationResult(newLocations); |
| } |
| |
| private final ArrayList<Location> mLocations; |
| |
| private LocationResult(ArrayList<Location> locations) { |
| Preconditions.checkArgument(!locations.isEmpty()); |
| mLocations = locations; |
| } |
| |
| /** |
| * Throws an IllegalArgumentException if the ordering of locations does not appear to generally |
| * be from earliest to latest, or if any individual location is incomplete. |
| * |
| * @hide |
| */ |
| public @NonNull LocationResult validate() { |
| long prevElapsedRealtimeNs = 0; |
| final int size = mLocations.size(); |
| for (int i = 0; i < size; ++i) { |
| Location location = mLocations.get(i); |
| if (!location.isComplete()) { |
| throw new IllegalArgumentException( |
| "incomplete location at index " + i + ": " + mLocations); |
| } |
| if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) { |
| throw new IllegalArgumentException( |
| "incorrectly ordered location at index " + i + ": " + mLocations); |
| } |
| prevElapsedRealtimeNs = location.getElapsedRealtimeNanos(); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Returns the latest location in this location result, ie, the location at the highest index. |
| */ |
| public @NonNull Location getLastLocation() { |
| return mLocations.get(mLocations.size() - 1); |
| } |
| |
| /** |
| * Returns the number of locations in this location result. |
| */ |
| public @IntRange(from = 1) int size() { |
| return mLocations.size(); |
| } |
| |
| /** |
| * Returns the location at the given index, from 0 to {@link #size()} - 1. Locations at lower |
| * indices are from earlier in time than location at higher indices. |
| */ |
| public @NonNull Location get(@IntRange(from = 0) int i) { |
| return mLocations.get(i); |
| } |
| |
| /** |
| * Returns an unmodifiable list of locations in this location result. |
| */ |
| public @NonNull List<Location> asList() { |
| return Collections.unmodifiableList(mLocations); |
| } |
| |
| /** |
| * Returns a deep copy of this LocationResult. |
| * |
| * @hide |
| */ |
| public @NonNull LocationResult deepCopy() { |
| final int size = mLocations.size(); |
| ArrayList<Location> copy = new ArrayList<>(size); |
| for (int i = 0; i < size; i++) { |
| copy.add(new Location(mLocations.get(i))); |
| } |
| return new LocationResult(copy); |
| } |
| |
| /** |
| * Returns a LocationResult with only the last location from this location result. |
| * |
| * @hide |
| */ |
| public @NonNull LocationResult asLastLocationResult() { |
| if (mLocations.size() == 1) { |
| return this; |
| } else { |
| return LocationResult.wrap(getLastLocation()); |
| } |
| } |
| |
| /** |
| * Returns a LocationResult with only locations that pass the given predicate. This |
| * implementation will avoid allocations when no locations are filtered out. The predicate is |
| * guaranteed to be invoked once per location, in order from earliest to latest. If all |
| * locations are filtered out a null value is returned. |
| * |
| * @hide |
| */ |
| public @Nullable LocationResult filter(Predicate<Location> predicate) { |
| ArrayList<Location> filtered = mLocations; |
| final int size = mLocations.size(); |
| for (int i = 0; i < size; ++i) { |
| if (!predicate.test(mLocations.get(i))) { |
| if (filtered == mLocations) { |
| filtered = new ArrayList<>(mLocations.size() - 1); |
| for (int j = 0; j < i; ++j) { |
| filtered.add(mLocations.get(j)); |
| } |
| } |
| } else if (filtered != mLocations) { |
| filtered.add(mLocations.get(i)); |
| } |
| } |
| |
| if (filtered == mLocations) { |
| return this; |
| } else if (filtered.isEmpty()) { |
| return null; |
| } else { |
| return new LocationResult(filtered); |
| } |
| } |
| |
| /** |
| * Returns a LocationResult with locations mapped to other locations. This implementation will |
| * avoid allocations when all locations are mapped to the same location. The function is |
| * guaranteed to be invoked once per location, in order from earliest to latest. |
| * |
| * @hide |
| */ |
| public @NonNull LocationResult map(Function<Location, Location> function) { |
| ArrayList<Location> mapped = mLocations; |
| final int size = mLocations.size(); |
| for (int i = 0; i < size; ++i) { |
| Location location = mLocations.get(i); |
| Location newLocation = function.apply(location); |
| if (mapped != mLocations) { |
| mapped.add(newLocation); |
| } else if (newLocation != location) { |
| mapped = new ArrayList<>(mLocations.size()); |
| for (int j = 0; j < i; ++j) { |
| mapped.add(mLocations.get(j)); |
| } |
| mapped.add(newLocation); |
| } |
| } |
| |
| if (mapped == mLocations) { |
| return this; |
| } else { |
| return new LocationResult(mapped); |
| } |
| } |
| |
| public static final @NonNull Parcelable.Creator<LocationResult> CREATOR = |
| new Parcelable.Creator<LocationResult>() { |
| @Override |
| public LocationResult createFromParcel(Parcel in) { |
| return new LocationResult( |
| Objects.requireNonNull(in.createTypedArrayList(Location.CREATOR))); |
| } |
| |
| @Override |
| public LocationResult[] newArray(int size) { |
| return new LocationResult[size]; |
| } |
| }; |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(@NonNull Parcel parcel, int flags) { |
| parcel.writeTypedList(mLocations); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| LocationResult that = (LocationResult) o; |
| return mLocations.equals(that.mLocations); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mLocations); |
| } |
| |
| @Override |
| public String toString() { |
| return mLocations.toString(); |
| } |
| } |